Inspecting Heap Objects with LLDB

When a new object is created in Objective-C, a chunk of space is allocated on heap for the object structure and a pointer saved to that structure on the stack.

NSObject *myobj1 = [NSObject alloc] init];  

Even though things seem to change slightly from version to version the structure of an object is roughly still:

typedef struct objc_class *Class;

typedef struct objc_object {  
    Class isa;
} *id;

struct objc_class {  
    struct objc_class *isa; 
    struct objc_class *super_class; 
    const char *name;       
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;

When [NSObject alloc] is called a chunk of space is allocated on the heap for the object (NSObject) and then initialized. The internal mechanism for this process is a simple call to malloc(), which returns the aforementioned pointer. We can easily print out the pointer address in our console:

NSLog(@"%@", myobj1);  
2015-11-19 12:09:52.895 HeapObjProj[45712:1625591] <MyObject: 0x100303c80>  
2015-11-19 12:09:52.896 HeapObjProj[45712:1625591] <MyObject: 0x100304840>  
2015-11-19 12:09:52.896 HeapObjProj[45712:1625591] <MyObject: 0x100304850>  
2015-11-19 12:09:52.897 HeapObjProj[45712:1625591] [*] Welcome foobar  

Let's load up our target binary into LLDB and set a breakpoint on main :

(lldb) target create "HeapObjProj"
Current executable set to 'HeapObjProj' (x86_64).  
(lldb) b main
Breakpoint 1: 13 locations.  
(lldb) r
Process 3149 launched: '/Users/benjaminwatson/Desktop/HeapObjProj 2015-11-19 10-25-14/Products/usr/local/bin/HeapObjProj' (x86_64)  
* thread #1: tid = 0x1463b, 0x000000010000142e HeapObjProj main(argc=1, argv=0x00007fff5fbff4a8) + 4 at main.m:29, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1

Next let's set a breakpoint at an address right after one of our allocated objects is printed to the console:

...
..
.
0x100001494 <+106>: callq  *%rbx  
    0x100001496 <+108>: movq   0x3053(%rip), %rsi        ; "init"
    0x10000149d <+115>: movq   %rax, %rdi
    0x1000014a0 <+118>: callq  *%rbx
    0x1000014a2 <+120>: movq   %rax, %r15
    0x1000014a5 <+123>: leaq   0x2e04(%rip), %r13        ; @"%@"
    0x1000014ac <+130>: xorl   %eax, %eax
    0x1000014ae <+132>: movq   %r13, %rdi
    0x1000014b1 <+135>: movq   %r12, %rsi
    0x1000014b4 <+138>: callq  0x10000352c               ; symbol stub for: NSLog
    0x1000014b9 <+143>: xorl   %eax, %eax
    0x1000014bb <+145>: movq   %r13, %rdi
    0x1000014be <+148>: movq   %r14, %rsi
...
..
.
(lldb) b 0x1000014be
Breakpoint 2: where = HeapObjProj main + 148 at main.m:36, address = 0x00000001000014be  

After we continue execution, we can see the output from the call to NSLog() :

(lldb) c
Process 3149 resuming  
2015-11-19 17:09:43.479 HeapObjProj[3149:83515] <MyObject: 0x100200100>  

LLDB comes with a handy script that we can load in order to inspect addresses returned from malloc():

(lldb) command script import lldb.macosx.heap
(lldb) malloc_info --stack-history 0x100200100
0x0000000100200100: malloc(    16) -> 0x100200100 MyObject.NSObject.isa  

By passing the address we got back in our console to the malloc_info command we get pertinent information about the address. So let's say we wanted to inspect memory allocation where this instance exists:

(lldb) memory read --size 8 --format A --count 10 0x0000000100200100
0x100200100: 0x0000000100004630 (void *)0x0000000100004608  
0x100200108: 0x0000000000000000  
0x100200110: 0x00007fff879d7a12  
0x100200118: 0x00007fff944f1148 libobjc.A.dylib -[NSObject class]  
0x100200120: 0x00007fff879d7246  
0x100200128: 0x00007fff944f1d9e libobjc.A.dylib -[NSObject respondsToSelector:]  
0x100200130: 0x00007fff879d6fd2  
0x100200138: 0x00007fff944f0ee6 libobjc.A.dylib -[NSObject init]  
0x100200140: 0x0000000000000000  
0x100200148: 0x0000000000000000  

At address 0x10020010 exists another pointer. This should be our MyObject so let's get the object's description:

(lldb) expr -o -- 0x0000000100004630
MyObject  

Awesome, now we can read the memory at this location, which should be laid out based on the structure we covered above.

(lldb) memory read --size 8 --format A --count 10 0x0000000100004630
0x100004630: 0x0000000100004608 (void *)0x00007fff771da118: NSObject  
0x100004638: 0x00007fff771da0f0 (void *)0x00007fff771da118: NSObject  
0x100004640: 0x00000001002001d0  
0x100004648: 0x0000000500000007  
0x100004650: 0x0000000100200042  
0x100004658: 0x0000000100004658 (void *)0x0000000100004658  
0x100004660: 0x0000000100004680 (void *)0x0000000100004658  
...
..
.

The first two addresses are are the isa and super_class pointers of MyObject . Hopefully this was informative and useful, and be sure to let me know if I fucked this up in anyway. Cheers!