1

I am using dtrace to print all the objc_msgSend in my code. With what I've done so far I can see the selector's name but I cannot get the correct class name.

This is my dtrace script:

#!/usr/sbin/dtrace -qs

pid$target::objc_msgSend:entry
{
    self->isa = *(long *)copyin(arg0, 8);
    printf("-[%s %s]\n",
    copyinstr(*(long *)copyin(self->isa + 16, 8)),
    copyinstr(arg1));
}

and I am assuming that the the id receiver object is of the following struct:

typedef struct objc_class {
    struct objc_class *isa;
    struct objc_class *super_class;
    char *name;
    ...
}

In my head, in order to reach the name the pointer has to be moved 2 * sizeof(objc_class*) which makes 16, and we get the pointer of the name of size 8. Therefore I expected to see the class name but I get some garbage printed instead.

Any ideas of what I am doing wrong?

My system is Mavericks x64.

Vame
  • 2,033
  • 2
  • 18
  • 29

1 Answers1

3

After poking around the Obj-C runtime source code for 64 bit architecture and the "objc-private.h" file, this is the "formula" to get the class name from a Class pointer:

#define RW_REALIZED (1<<31)
#define RW_FUTURE (1<<30)
#define CLASS_FAST_FLAG_MASK  3
#define TAG_MASK 1
#define TAG_SLOT_SHIFT 0
#define TAG_SLOT_MASK 0xf

extern "C" Class objc_debug_taggedpointer_classes[];  // Available in 10.9 for tagged pointers decoding

static const char* ClassNameFromInstance(id instance) {
  char* ptr0 = (char*)instance;

  char* ptr1;
  if ((long)ptr0 & TAG_MASK) {
    long slot = ((long)ptr0 >> TAG_SLOT_SHIFT) & TAG_SLOT_MASK;
    ptr1 = (char*)objc_debug_taggedpointer_classes[slot];  // struct objc_class pointer
  } else {
    ptr1 = *(char**)ptr0;  // struct objc_class pointer i.e. instance ISA
  }

  char* ptr2 = *((char**)(((long)ptr1 + 32) & ~CLASS_FAST_FLAG_MASK));  // struct class_ro_t or struct class_rw_t pointer

  uint32_t flags = *((uint32_t*)ptr2);  // struct class_ro_t or struct class_rw_t flags
  char* ptr3;
  if ((flags & RW_REALIZED) || (flags & RW_FUTURE)) {
    ptr3 = *((char**)((long)ptr2 + 8));  // struct class_ro_t pointer from struct class_rw_t pointer
  } else {
    ptr3 = ptr2;  // struct class_ro_t pointer same as struct class_rw_t pointer
  }

  const char* name = *((char**)((long)ptr3 + 24));  // Name string pointer from struct class_ro_t pointer

  return name;
}

Which in dtrace becomes in this example script that logs Obj-C objects creation & destruction:

#!/usr/bin/env dtrace -s
#pragma D option quiet

pid$target:libobjc.A.dylib:class_createInstance:entry
{
  ptr1 = *(long*)copyin(arg0, 8);  /* arg0 is Class pointer */
  ptr2 = *(long*)copyin((ptr1 + 32) & ~3, 8);
  flags = *(int*)copyin(ptr2, 4);
  ptr3 = (flags & (1 << 31)) || (flags & (1 << 30)) ? *(long*)copyin(ptr2 + 8, 8) : ptr2;
  ptr4 = *(long*)copyin(ptr3 + 24, 8);
  self->class = copyinstr(ptr4);
}

pid$target:libobjc.A.dylib:class_createInstance:return
{
  printf("[+] %s = %p\n", self->class, arg1);  /* arg1 is instance pointer */
  self->class = 0;
}

pid$target:libobjc.A.dylib:object_dispose:entry
/arg0 != 0/
{
  ptr0 = *(long*)copyin(arg0, 8);  /* arg0 is instance pointer */
  ptr1 = *(long*)copyin(ptr0, 8);  /* TODO: Handle tagged pointers */
  ptr2 = *(long*)copyin((ptr1 + 32) & ~3, 8);
  ptr3 = (flags & (1 << 31)) || (flags & (1 << 30)) ? *(long*)copyin(ptr2 + 8, 8) : ptr2;
  ptr4 = *(long*)copyin(ptr3 + 24, 8);
  class = copyinstr(ptr4);

  printf("[-] %s = %p\n", class, arg0);
}

IMPORTANT This dtrace script does not handle tagged pointers. Be also sure to set the environment variable "DYLD_SHARED_REGION=avoid" when using this script as explained here.

Community
  • 1
  • 1
Pol
  • 3,848
  • 1
  • 38
  • 55