From f50bc6cf338689a63bb6d1fe46a04991b4160419 Mon Sep 17 00:00:00 2001 From: theraven Date: Sat, 29 Jun 2013 11:05:31 +0000 Subject: [PATCH] Added support for tracing message sends to the runtime. Currently x86-64 only. --- ANNOUNCE | 3 +- CMakeLists.txt | 6 +- Test/CMakeLists.txt | 1 + Test/Test.h | 3 + Test/msgInterpose.m | 85 ++++++++++++++++++++++++++ caps.c | 3 + dtable.c | 82 +++++++++++++++++++++++++ hooks.c | 1 + objc/capabilities.h | 4 ++ objc/hooks.h | 27 ++++++++ objc_msgSend.x86-64.S | 139 +++++++++++++++++++++++++++++++++++++++++- 11 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 Test/msgInterpose.m diff --git a/ANNOUNCE b/ANNOUNCE index b3e62df..5870783 100644 --- a/ANNOUNCE +++ b/ANNOUNCE @@ -6,7 +6,8 @@ Objective-C runtime (a.k.a. libobjc2). This runtime was designed to support the features of modern dialects of Objective-C for use with GNUstep and other Objective-C programs. Highlights of this release include: -- +- Added API for tracing, allowing interposition on all message sends matching a + given selector. You may obtain the code for this release from subversion at the following subversion branch: diff --git a/CMakeLists.txt b/CMakeLists.txt index 56cd247..98c2c48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -85,7 +85,11 @@ set(TYPE_DEPENDENT_DISPATCH TRUE CACHE BOOL if (TYPE_DEPENDENT_DISPATCH) add_definitions(-DTYPE_DEPENDENT_DISPATCH) endif () - +set(ENABLE_TRACING FALSE CACHE BOOL + "Enable tracing support (slower, not recommended for deployment)") +if (ENABLE_TRACING) + add_definitions(-DWITH_TRACING=1) +endif (ENABLE_TRACING) set(LOW_MEMORY FALSE CACHE BOOL "Enable low-memory profile *HIGHLY EXPERIMENTAL*") if (LOW_MEMORY) diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index caa3d5a..b94979f 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -20,6 +20,7 @@ set(TESTS ProtocolCreation.m RuntimeTest.m objc_msgSend.m + msgInterpose.m ) # Function for adding a test. This takes the name of the test and the list of diff --git a/Test/Test.h b/Test/Test.h index d46cfdd..1288573 100644 --- a/Test/Test.h +++ b/Test/Test.h @@ -1,4 +1,7 @@ #import "../objc/runtime.h" +#ifdef NDEBUG +#undef NDEBUG +#endif #include #ifndef __has_attribute diff --git a/Test/msgInterpose.m b/Test/msgInterpose.m new file mode 100644 index 0000000..c1184e1 --- /dev/null +++ b/Test/msgInterpose.m @@ -0,0 +1,85 @@ +#include +#include +#include +#include "Test.h" +#include "objc/hooks.h" +#include "objc/capabilities.h" + + +int count; +static const int loops = 50000; +static const int depth = 42; +static const int calls = loops * (depth+1); +static int d = depth; +Class TestCls; +int tracecount; + +@implementation Test (Nothing) ++ nothing +{ + count++; + if (d > 0) + { + d--; + [self nothing]; + d++; + } + return 0; +} +@end +__thread IMP real; + +static int interposecount; +id interpose(id self, SEL _cmd) { + interposecount++; + return real(self, _cmd); +} + +//IMP hook(id object, SEL selector, IMP method, int isReturn, void *wordReturn) { tracecount++; return (IMP)logExit; } +IMP hook(id object, SEL selector, IMP method, int isReturn, void *wordReturn) { tracecount++; real = method; return (IMP)0; } +IMP hook0(id object, SEL selector, IMP method, int isReturn, void *wordReturn) { tracecount++; real = method; return (IMP)1; } +IMP hook1(id object, SEL selector, IMP method, int isReturn, void *wordReturn) { tracecount++; real = method; return (IMP)interpose; } + +int main(void) +{ + if (!objc_test_capability(OBJC_CAP_TRACING)) + { + fprintf(stderr, "Tracing support not compiled into runtime\n"); + return 0; + } + TestCls = objc_getClass("Test"); + objc_registerTracingHook(@selector(nothing), hook); + interposecount = 0; + count = 0; + tracecount = 0; + for (int i=0 ; i #include #include "objc/runtime.h" +#include "objc/hooks.h" #include "sarray2.h" #include "selector.h" #include "class.h" @@ -11,8 +12,12 @@ #include "slot_pool.h" #include "dtable.h" #include "visibility.h" +#include "errno.h" PRIVATE dtable_t uninstalled_dtable; +#if defined(WITH_TRACING) && defined (__x86_64) +PRIVATE dtable_t tracing_dtable; +#endif /** Head of the list of temporary dtables. Protected by initialize_lock. */ PRIVATE InitializingDtable *temporary_dtables; @@ -358,6 +363,82 @@ PRIVATE void init_dispatch_tables () { INIT_LOCK(initialize_lock); uninstalled_dtable = SparseArrayNewWithDepth(dtable_depth); +#if defined(WITH_TRACING) && defined (__x86_64) + tracing_dtable = SparseArrayNewWithDepth(dtable_depth); +#endif +} + +#if defined(WITH_TRACING) && defined (__x86_64) +static int init; + +static void free_thread_stack(void* x) +{ + free(*(void**)x); +} +static pthread_key_t thread_stack_key; +static void alloc_thread_stack(void) +{ + pthread_key_create(&thread_stack_key, free_thread_stack); + init = 1; +} + +PRIVATE void* pushTraceReturnStack(void) +{ + static pthread_once_t once_control = PTHREAD_ONCE_INIT; + if (!init) + { + pthread_once(&once_control, alloc_thread_stack); + } + void **stack = pthread_getspecific(thread_stack_key); + if (stack == 0) + { + stack = malloc(4096*sizeof(void*)); + } + pthread_setspecific(thread_stack_key, stack + 5); + return stack; +} + +PRIVATE void* popTraceReturnStack(void) +{ + void **stack = pthread_getspecific(thread_stack_key); + stack -= 5; + pthread_setspecific(thread_stack_key, stack); + return stack; +} +#endif + +int objc_registerTracingHook(SEL aSel, objc_tracing_hook aHook) +{ +#if defined(WITH_TRACING) && defined (__x86_64) + // If this is an untyped selector, register it for every typed variant + if (sel_getType_np(aSel) == 0) + { + SEL buffer[16]; + SEL *overflow = 0; + int count = sel_copyTypedSelectors_np(sel_getName(aSel), buffer, 16); + if (count > 16) + { + overflow = calloc(count, sizeof(SEL)); + sel_copyTypedSelectors_np(sel_getName(aSel), buffer, 16); + for (int i=0 ; iindex, aHook); + } + free(overflow); + } + else + { + for (int i=0 ; iindex, aHook); + } + } + } + SparseArrayInsert(tracing_dtable, aSel->index, aHook); + return 0; +#else + return ENOTSUP; +#endif } static BOOL installMethodInDtable(Class class, @@ -762,3 +843,4 @@ PRIVATE void objc_send_initialize(id object) // because we will clean it up after this function. initializeSlot->method((id)class, initializeSel); } + diff --git a/hooks.c b/hooks.c index 8def49b..ff085d5 100644 --- a/hooks.c +++ b/hooks.c @@ -1,3 +1,4 @@ #include "objc/runtime.h" #define OBJC_HOOK #include "objc/hooks.h" + diff --git a/objc/capabilities.h b/objc/capabilities.h index dc4e046..07c96cb 100644 --- a/objc/capabilities.h +++ b/objc/capabilities.h @@ -112,6 +112,10 @@ extern "C" { * The runtime provides APIs for debugging ARC-managed autorelease pools. */ #define OBJC_ARC_AUTORELEASE_DEBUG 16 +/** + * The runtime provides support for tracing message sends. + */ +#define OBJC_CAP_TRACING 17 /** * Macro used to require the existence of a specific capability. This creates diff --git a/objc/hooks.h b/objc/hooks.h index 8ec6b70..42c67ed 100644 --- a/objc/hooks.h +++ b/objc/hooks.h @@ -76,3 +76,30 @@ extern struct objc_slot* (*_objc_selector_type_mismatch)(Class cls, * This hook must be set for weak references to work with automatic reference counting. */ OBJC_HOOK id (*_objc_weak_load)(id object); + +/** + * Type for a tracing hook. These are registered to be called before and after + * each message send. The parameters are the receiver and selector for the + * message, the method to be called, a flag indicating the direction, and the + * return value. The flag is 0 when the message is being sent and 1 when it + * returns, the return value is only defined for word-sized scalar returns. + * + * If the hook returns (IMP)0, when invoked in the sending direction, then it + * will not be invoked on return. This is significantly faster. + * + * If the hook returns (IMP)1, when invoked in the sending direction, then it + * will be invoked again on return. The return value is ignored on the second + * call. + * + * If it returns any other value, then the result will be called instead of the + * correct IMP. This allows all messages with a given selector to be + * interposed. + */ +typedef IMP (*objc_tracing_hook)(id, SEL, IMP, int, void*); + +/** + * Registers a tracing hook for a specified selector. + */ +int objc_registerTracingHook(SEL, objc_tracing_hook); + + diff --git a/objc_msgSend.x86-64.S b/objc_msgSend.x86-64.S index 6d70df4..f519bf3 100644 --- a/objc_msgSend.x86-64.S +++ b/objc_msgSend.x86-64.S @@ -56,6 +56,143 @@ test %r10, %r10 jz 5f # Nil slot - invoke some kind of forwarding mechanism mov SLOT_OFFSET(%r10), %r10 + +7: +#ifdef WITH_TRACING + + push %r12 + push %r13 + push %r10 + + mov (\sel), %r11 # Load the selector index + lea tracing_dtable(%rip), %r10 + mov (%r10), %r10 + + mov SHIFT_OFFSET(%r10), %r13 # Load the shift (dtable size) + mov DATA_OFFSET(%r10), %r12 # load the address of the start of the array + pop %r10 + cmpl $8, %r13d # If this is a small dtable, jump to the small dtable handlers + je 10f + cmpl $0, %r13d + je 11f + + mov %r11, %r13 + and $0xff0000, %r13 + shrl $13, %r13d # Right shift 16, but then left shift by 3 *sizeof(void*) + add %r13, %r12 + mov (%r12), %r12 + mov DATA_OFFSET(%r12), %r12 +10: # dtable16: + mov %r11, %r13 + and $0xff00, %r13 + shrl $5, %r13d + add %r13, %r12 + mov (%r12), %r12 + mov DATA_OFFSET(%r12), %r12 +11: # dtable8: + mov %r11, %r13 + and $0xff, %r13 + shll $3, %r13d + add %r13, %r12 + mov (%r12), %r11 + pop %r13 + pop %r12 + test %r11, %r11 + jz 12f + + push %rax # We need to preserve all registers that may contain arguments: + push %rdi + push %rsi + push %rdx + push %rcx + push %r8 + push %r9 + push %r10 + push %r11 + mov \receiver, %rdi # Arg 0 is receiver + mov \sel, %rsi # Arg 1 is selector + mov %r10, %rdx # Arg 2 is IMP + mov $0, %rcx # Arg 3 is entry / exit (0/1) + mov $0, %r8 # Arg 4 is return value (0 on entry) + + call *%r11 # Call the tracing function + cmpq $0, %rax + jz 13f # If it returns 0, don't call the end-tracing function. + cmpq $1, %rax # If it returns 1, do call the tracing function + jne 14f # Any other value is an interposition + # function to call instead of the method + + call pushTraceReturnStack # rax now contains a thread-local buffer for storing returns + + pop %r11 # Restore all of the argument registers + pop %r10 # except rax, which we'll need before the call + pop %r9 + pop %r8 + pop %rcx + pop %rdx + pop %rsi + pop %rdi + + mov \receiver, (%rax) # Store the receiver in TLS + mov \sel, 8(%rax) # Store the selector in TLS + mov %r10, 16(%rax) # Store the method in TLS + mov %r11, 24(%rax) # Store the tracing function in TLS + mov 8(%rsp), %r11 # r11 now contains the return address + mov %r11, 32(%rax) # Store the method-return address in TLS + + pop %rax + pop %r11 # r11 now contains the return address, but we don't care + + call *%r10 # Call the IMP. The stack should now be in the same state + # that it was on entry into this function + + push %rax # Now we are free to clobber argument + push %rdx # registers, but we must preserve return registers... + + call popTraceReturnStack # rax now contains a thread-local buffer for storing returns + + push %rax # save the return value, because we'll need it after the tracing function call + mov (%rax), %rdi # Load the receiver into arg 0 + mov 8(%rax), %rsi # Load the selector into arg 1 + mov 16(%rax), %rdx # Load the IMP into arg 3 + mov $1, %rcx # Arg 4 is 1 (tracing on exit) + mov %rax, %r8 # Arg 5 is the return result + + mov 24(%rax), %r11 # Reload the address of the tracing function + + call *%r11 # Call the tracing function + pop %rax # Reload the real return address + mov 32(%rax), %r11 + pop %rdx # Reload saved values + pop %rax + jmp *%r11 # Simulate a return by jumping to the cached return address + +13: # Skip tracing on exit and just tail-call the method + pop %r11 + pop %r10 + pop %r9 + pop %r8 + pop %rcx + pop %rdx + pop %rsi + pop %rdi + pop %rax + jmp *%r10 + +14: + mov %rax, %r10 + pop %r9 + pop %r9 + pop %r9 + pop %r8 + pop %rcx + pop %rdx + pop %rsi + pop %rdi + pop %rax + +12: +#endif // WITH_TRACING jmp *%r10 4: # returnNil: # Both of the return registers are @@ -119,7 +256,7 @@ pop %rcx pop %rbx pop %rax - jmp *%r10 + jmp 7b 6: # smallObject: and \receiver, %r10 # Find the small int type shll $3, %r10d