Added support for tracing message sends to the runtime.

Currently x86-64 only.
main
theraven 13 years ago
parent 3e2176a824
commit f50bc6cf33

@ -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:

@ -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)

@ -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

@ -1,4 +1,7 @@
#import "../objc/runtime.h"
#ifdef NDEBUG
#undef NDEBUG
#endif
#include <assert.h>
#ifndef __has_attribute

@ -0,0 +1,85 @@
#include <locale.h>
#include <time.h>
#include <stdio.h>
#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<loops ; i++)
{
[TestCls nothing];
}
assert(count == calls);
assert(tracecount == calls);
assert(interposecount == 0);
objc_registerTracingHook(@selector(nothing), hook0);
interposecount = 0;
count = 0;
tracecount = 0;
for (int i=0 ; i<loops ; i++)
{
[TestCls nothing];
}
assert(count == calls);
assert(tracecount == calls * 2);
assert(interposecount == 0);
objc_registerTracingHook(@selector(nothing), hook1);
interposecount = 0;
count = 0;
tracecount = 0;
for (int i=0 ; i<loops ; i++)
{
[TestCls nothing];
}
assert(count == calls);
assert(tracecount == calls);
assert(interposecount == calls);
return 0;
}

@ -28,6 +28,9 @@ static const int32_t caps =
#endif
#ifdef ENABLE_GC
(1<<OBJC_CAP_GARBAGE_COLLECTION) |
#endif
#if defined(WITH_TRACING) && defined (__x86_64)
(1<<OBJC_CAP_TRACING) |
#endif
0;

@ -3,6 +3,7 @@
#include <stdlib.h>
#include <assert.h>
#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 ; i<count ; i++)
{
SparseArrayInsert(tracing_dtable, overflow[i]->index, aHook);
}
free(overflow);
}
else
{
for (int i=0 ; i<count ; i++)
{
SparseArrayInsert(tracing_dtable, buffer[i]->index, 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);
}

@ -1,3 +1,4 @@
#include "objc/runtime.h"
#define OBJC_HOOK
#include "objc/hooks.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

@ -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);

@ -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

Loading…
Cancel
Save