//SB: This is my session of running hello-loop.c in LLDB. My annotations // start with "//" or "<<--". Extra newlines added for readability. // PC stands for program counter, FP for frame pointer, SP for stack pointer. % gcc -Wall -o hello hello-loop.c % lldb hello (lldb) target create "hello" Current executable set to '/Users/user/cs59/cosc59.gitlab.io/c-and-armv8/hello' (arm64). // Let's look at main(). The first three instructions are standard preamble, // making space for another notional function activation record (a.k.a. // stack frame) and saving the frame pointer x29 and the link register // x30 into that frame. x30 at this point contains the address of the // next instruction to execute after main() returns. The code that calls // main() is a part of the standard OS library, with the entry point called // _start. (lldb) disas -n main hello`main: hello[0x100003f34] <+0>: sub sp, sp, #0x20 ; =0x20 hello[0x100003f38] <+4>: stp x29, x30, [sp, #0x10] hello[0x100003f3c] <+8>: add x29, sp, #0x10 ; =0x10 hello[0x100003f40] <+12>: stur wzr, [x29, #-0x4] <<-- zeroes out 4 bytes at FP-4, but doesn't seem necessary, since we never use these four bytes hello[0x100003f44] <+16>: mov w8, #0xa <<-- initial value of i, 10 hello[0x100003f48] <+20>: str w8, [sp, #0x8] <<-- i will live in memory, at the offset SP+8 (equivalently, FP-8). Store it there. // Loop starts here: hello[0x100003f4c] <+24>: ldr w8, [sp, #0x8] <<-- load i right back hello[0x100003f50] <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> <<-- test 31st bit of w8, i.e., the most significant bit. For negative signed integers it will be 1. // The next three instructions do "i = i - 1;" hello[0x100003f54] <+32>: ldr w8, [sp, #0x8] hello[0x100003f58] <+36>: subs w8, w8, #0x1 ; =0x1 hello[0x100003f5c] <+40>: str w8, [sp, #0x8] // 64-bit addresses of constant strings are too long to store as immediates, so // the next two instructions construct the address of "Hello": hello[0x100003f60] <+44>: adrp x0, 0 <<-- This loads x0 with the current value of PC, with the lowest 12 bits ("page offset") zeroed out. So, x0 gets set to 0x0000000100003000 hello[0x100003f64] <+48>: add x0, x0, #0xfb0 ; =0xfb0 <<-- Now x0 has 0x0000000100003fb0 , just where the string "Hello\0" starts. See below. // x0 serves as the first argument to any function, as per armv8 calling convention hello[0x100003f68] <+52>: bl 0x100003f80 ; symbol stub for: puts hello[0x100003f6c] <+56>: b 0x100003f4c ; <+24> <<-- jump to loop's start hello[0x100003f70] <+60>: mov w0, #0x2a <<-- return value of 42. It's an int, so w0 rather than x0 is used. Top part of x0 will be zeroed out. // Post-amble. Restore the old values of x30 and x29. We'll need the older value of x30 that we held at the start of this function for "ret" below ("ret" is only and exactly "b x30") hello[0x100003f74] <+64>: ldp x29, x30, [sp, #0x10] hello[0x100003f78] <+68>: add sp, sp, #0x20 ; =0x20 hello[0x100003f7c] <+72>: ret // Set breakpoint at main(). It's handy to stop and set other breakpoints after // the program has been fully loaded and started running. (lldb) b main Breakpoint 1: where = hello`main, address = 0x0000000100003f34 (lldb) r <<-- run Process 96089 launched: '/Users/user/cs59/cosc59.gitlab.io/c-and-armv8/hello' (arm64) Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100003f34 hello`main hello`main: // NOTE: the arrow points at the NEXT instruction to be executed! -> 0x100003f34 <+0>: sub sp, sp, #0x20 ; =0x20 0x100003f38 <+4>: stp x29, x30, [sp, #0x10] 0x100003f3c <+8>: add x29, sp, #0x10 ; =0x10 0x100003f40 <+12>: stur wzr, [x29, #-0x4] Target 0: (hello) stopped. // We are now at the start of the preamble that allocates a new stack frame. // Observe SP: (lldb) reg r $sp sp = 0x000000016fdff660 // We are going to execute this code by stepping through its instructions // one by one with the "si" command (or letting them run until a breakpoint / with "c", continue). What is "si" in LLDB? In GDB, it abbreviates "stepi", // step one instruction then stop. In LLDB, it's a little more expressive: (lldb) help si Instruction level single step, stepping into calls. Defaults to current thread unless specified. Syntax: si [] Command Options Usage: si [-c ] [-e ] [-m ] [-a ] [-t ] [-A ] [-r ] [] -A ( --step-out-avoids-no-debug ) A boolean value, if true stepping out of functions will continue to step out till it hits a function with debug information. -a ( --step-in-avoids-no-debug ) A boolean value that sets whether stepping into functions will step over functions with no debug information. -c ( --count ) How many times to perform the stepping operation - currently only supported for step-inst and next-inst. -e ( --end-linenumber ) The line at which to stop stepping - defaults to the next line and only supported for step-in and step-over. You can also pass the string 'block' to step to the end of the current block. This is particularly use in conjunction with --step-target to step through a complex calling sequence. -m ( --run-mode ) Determine how to run other threads while stepping the current thread. Values: this-thread | all-threads | while-stepping -r ( --step-over-regexp ) A regular expression that defines function names to not to stop at when stepping in. -t ( --step-in-target ) The name of the directly called function step in should stop at when stepping into. This command takes options and free-form arguments. If your arguments resemble option specifiers (i.e., they start with a - or --), you must use ' -- ' between the end of the command options and the beginning of the arguments. 'si' is an abbreviation for 'thread step-inst' (lldb) si <<-- So, step one instruction Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f38 hello`main + 4 hello`main: -> 0x100003f38 <+4>: stp x29, x30, [sp, #0x10] 0x100003f3c <+8>: add x29, sp, #0x10 ; =0x10 0x100003f40 <+12>: stur wzr, [x29, #-0x4] 0x100003f44 <+16>: mov w8, #0xa Target 0: (hello) stopped. // SP has been reduced by 0x20 i.e. 32: (lldb) reg r $sp sp = 0x000000016fdff640 // Let's see (x is for "examine") the memory at SP. By default it's shown as bytes: (lldb) x $sp 0x16fdff640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x16fdff650: 70 f6 df 6f 01 00 00 00 38 10 01 00 01 00 79 88 p..o....8.....y. // ..but we can ask for it to be shown as 64-bit words (double words) (lldb) x/10g $sp 0x16fdff640: 0x0000000000000000 0x0000000000000000 0x16fdff650: 0x000000016fdff670 0x8879000100011038 <<-- x29 and x30 will go here 0x16fdff660: 0x000000018ca89430 0x0000000000000000 0x16fdff670: 0x0000000000000000 0x0000000000000000 0x16fdff680: 0x0000000000000000 0x0000000100000000 (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f3c hello`main + 8 hello`main: -> 0x100003f3c <+8>: add x29, sp, #0x10 ; =0x10 0x100003f40 <+12>: stur wzr, [x29, #-0x4] 0x100003f44 <+16>: mov w8, #0xa 0x100003f48 <+20>: str w8, [sp, #0x8] Target 0: (hello) stopped. (lldb) x/10g $sp 0x16fdff640: 0x0000000000000000 0x0000000000000000 0x16fdff650: 0x000000016fdff670 0x000000018ca89430 <<-- indeed, here they are! 0x16fdff660: 0x000000018ca89430 0x0000000000000000 0x16fdff670: 0x0000000000000000 0x0000000000000000 0x16fdff680: 0x0000000000000000 0x0000000100000000 // This value will be changed by the next instruction: (lldb) reg r x29 fp = 0x000000016fdff670 (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f40 hello`main + 12 hello`main: -> 0x100003f40 <+12>: stur wzr, [x29, #-0x4] 0x100003f44 <+16>: mov w8, #0xa 0x100003f48 <+20>: str w8, [sp, #0x8] 0x100003f4c <+24>: ldr w8, [sp, #0x8] Target 0: (hello) stopped. // FP is now SP+0x10 (i.e., SP+16) (lldb) reg r x29 fp = 0x000000016fdff650 (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f44 hello`main + 16 hello`main: -> 0x100003f44 <+16>: mov w8, #0xa 0x100003f48 <+20>: str w8, [sp, #0x8] 0x100003f4c <+24>: ldr w8, [sp, #0x8] 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> Target 0: (hello) stopped. // w0 holds something, probably a lower part of some address (lldb) reg r w8 w8 = 0xad83009e (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f48 hello`main + 20 hello`main: -> 0x100003f48 <+20>: str w8, [sp, #0x8] 0x100003f4c <+24>: ldr w8, [sp, #0x8] 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] Target 0: (hello) stopped. // ...and now it holds 10: (lldb) reg r w8 w8 = 0x0000000a // This is the stack before "i" is stored there: (lldb) x $sp 0x16fdff640: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0x16fdff650: 70 f6 df 6f 01 00 00 00 30 94 a8 8c 01 00 00 00 p..o....0....... (lldb) x/10g $sp 0x16fdff640: 0x0000000000000000 0x0000000000000000 0x16fdff650: 0x000000016fdff670 0x000000018ca89430 0x16fdff660: 0x000000018ca89430 0x0000000000000000 0x16fdff670: 0x0000000000000000 0x0000000000000000 0x16fdff680: 0x0000000000000000 0x0000000100000000 (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f4c hello`main + 24 hello`main: -> 0x100003f4c <+24>: ldr w8, [sp, #0x8] 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 Target 0: (hello) stopped. // This is the stack with "i" stored into its allocated offset in it, SP+8: (lldb) x/10g $sp 0x16fdff640: 0x0000000000000000 0x000000000000000a <<-- right here :) 0x16fdff650: 0x000000016fdff670 0x000000018ca89430 0x16fdff660: 0x000000018ca89430 0x0000000000000000 0x16fdff670: 0x0000000000000000 0x0000000000000000 0x16fdff680: 0x0000000000000000 0x0000000100000000 // The right granularity to look for integers would be "x/w $sp+8". I forgot to do it, so here is the memory as bytes, with i's bytes underlined: (lldb) x $sp 0x16fdff640: 00 00 00 00 00 00 00 00 0a 00 00 00 00 00 00 00 ................ // ^^^^^^^^^^^ 0x16fdff650: 70 f6 df 6f 01 00 00 00 30 94 a8 8c 01 00 00 00 p..o....0....... // Note the little-endian order above, least significant byte of a 40-byte int comes first (lldb) disas -n main hello`main: 0x100003f34 <+0>: sub sp, sp, #0x20 ; =0x20 0x100003f38 <+4>: stp x29, x30, [sp, #0x10] 0x100003f3c <+8>: add x29, sp, #0x10 ; =0x10 0x100003f40 <+12>: stur wzr, [x29, #-0x4] 0x100003f44 <+16>: mov w8, #0xa 0x100003f48 <+20>: str w8, [sp, #0x8] -> 0x100003f4c <+24>: ldr w8, [sp, #0x8] <<-- we are here 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] <<-- load i into w8 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] <<-- decremented value of i stored back 0x100003f60 <+44>: adrp x0, 0 0x100003f64 <+48>: add x0, x0, #0xfb0 ; =0xfb0 0x100003f68 <+52>: bl 0x100003f80 ; symbol stub for: puts 0x100003f6c <+56>: b 0x100003f4c ; <+24> 0x100003f70 <+60>: mov w0, #0x2a 0x100003f74 <+64>: ldp x29, x30, [sp, #0x10] 0x100003f78 <+68>: add sp, sp, #0x20 ; =0x20 0x100003f7c <+72>: ret // Breakpoint at where the decremented value of i is stored back: (lldb) b 0x100003f5c Breakpoint 2: where = hello`main + 40, address = 0x0000000100003f5c (lldb) c <<-- continue, i.e., run till next breakpoint Process 96089 resuming Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1 frame #0: 0x0000000100003f5c hello`main + 40 hello`main: -> 0x100003f5c <+40>: str w8, [sp, #0x8] 0x100003f60 <+44>: adrp x0, 0 0x100003f64 <+48>: add x0, x0, #0xfb0 ; =0xfb0 0x100003f68 <+52>: bl 0x100003f80 ; symbol stub for: puts Target 0: (hello) stopped. // i has been decremented: (lldb) reg r w8 w8 = 0x00000009 // Upper half of x8 is zeroes, zeroed out when we loaded a four-byte value into w8. (lldb) reg r x8 x8 = 0x0000000000000009 // Stack before storing the new value of i onto its stack slot at SP+8: (lldb) x $sp+8 0x16fdff648: 0a 00 00 00 00 00 00 00 70 f6 df 6f 01 00 00 00 ........p..o.... 0x16fdff658: 30 94 a8 8c 01 00 00 00 30 94 a8 8c 01 00 00 00 0.......0....... // The exact slot as an int: (lldb) x/w $sp+8 0x16fdff648: 0x0000000a (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f60 hello`main + 44 hello`main: -> 0x100003f60 <+44>: adrp x0, 0 0x100003f64 <+48>: add x0, x0, #0xfb0 ; =0xfb0 0x100003f68 <+52>: bl 0x100003f80 ; symbol stub for: puts 0x100003f6c <+56>: b 0x100003f4c ; <+24> Target 0: (hello) stopped. // .. and now it's been stored. (lldb) x/w $sp+8 0x16fdff648: 0x00000009 // Where are we now? (lldb) disas -n main hello`main: 0x100003f34 <+0>: sub sp, sp, #0x20 ; =0x20 0x100003f38 <+4>: stp x29, x30, [sp, #0x10] 0x100003f3c <+8>: add x29, sp, #0x10 ; =0x10 0x100003f40 <+12>: stur wzr, [x29, #-0x4] 0x100003f44 <+16>: mov w8, #0xa 0x100003f48 <+20>: str w8, [sp, #0x8] 0x100003f4c <+24>: ldr w8, [sp, #0x8] 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] -> 0x100003f60 <+44>: adrp x0, 0 0x100003f64 <+48>: add x0, x0, #0xfb0 ; =0xfb0 0x100003f68 <+52>: bl 0x100003f80 ; symbol stub for: puts 0x100003f6c <+56>: b 0x100003f4c ; <+24> 0x100003f70 <+60>: mov w0, #0x2a 0x100003f74 <+64>: ldp x29, x30, [sp, #0x10] 0x100003f78 <+68>: add sp, sp, #0x20 ; =0x20 0x100003f7c <+72>: ret // Let's see how the address of "Hello" is constructed into x0, to serve // as an argument for puts. x0 has some value from prior code now: (lldb) reg r x0 x0 = 0x0000000000000001 (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f64 hello`main + 48 hello`main: -> 0x100003f64 <+48>: add x0, x0, #0xfb0 ; =0xfb0 0x100003f68 <+52>: bl 0x100003f80 ; symbol stub for: puts 0x100003f6c <+56>: b 0x100003f4c ; <+24> 0x100003f70 <+60>: mov w0, #0x2a Target 0: (hello) stopped. // The string "Hello" happens to be located within the same page as the PC // is pointing to, as 0 in "addp" tells us. The string itself is at the offset // 0xfb0 from the page start, which the next instruction will add. // Recall that "page" just means a 4K chuck, the lower 12 bits of an address. // Addp loaded the "page" part of the current PC into x0: (lldb) reg r x0 x0 = 0x0000000100003000 hello`_mh_execute_header + 12288 (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f68 hello`main + 52 hello`main: -> 0x100003f68 <+52>: bl 0x100003f80 ; symbol stub for: puts 0x100003f6c <+56>: b 0x100003f4c ; <+24> 0x100003f70 <+60>: mov w0, #0x2a 0x100003f74 <+64>: ldp x29, x30, [sp, #0x10] Target 0: (hello) stopped. // And now we have the address for "Hello", as the debugger conveniently tells us. (lldb) reg r x0 x0 = 0x0000000100003fb0 "Hello" // Double-check with memory examine. (lldb) x/s $x0 0x100003fb0: "Hello" // This is what it looks like as bytes. Notice the ending 0x00 of the string. (lldb) x $x0 0x100003fb0: 48 65 6c 6c 6f 00 00 00 01 00 00 00 1c 00 00 00 Hello........... 0x100003fc0: 00 00 00 00 1c 00 00 00 00 00 00 00 1c 00 00 00 ................ // We can ask for the value of PC at any point, of course. (lldb) reg r $pc pc = 0x0000000100003f68 hello`main + 52 (lldb) reg r pc pc = 0x0000000100003f68 hello`main + 52 (lldb) reg r pc x30 pc = 0x0000000100003f68 hello`main + 52 lr = 0x000000018ca89430 libdyld.dylib`start + 4 <<-- we'll return here after main() // I wanted to look at the stack. LLDB decided that since I wanted strings in // the previous "x" command, I still do now: (lldb) x/10g $sp+32 0x16fdff660: "0\xffffff94\xffffffa8\xffffff8c\x01" 0x16fdff666: "" 0x16fdff667: "" 0x16fdff668: "" 0x16fdff669: "" 0x16fdff66a: "" 0x16fdff66b: "" 0x16fdff66c: "" 0x16fdff66d: "" 0x16fdff66e: "" // But no, I want hex 64-bit words, to see addresses. So explicitly asking // for hex in the format, "10xg": (lldb) x/10xg $sp+0x20 0x16fdff660: 0x000000018ca89430 0x0000000000000000 0x16fdff670: 0x0000000000000000 0x0000000000000000 0x16fdff680: 0x0000000000000000 0x0000000100000000 0x16fdff690: 0x0000000000000001 0x000000016fdff880 0x16fdff6a0: 0x0000000000000000 0x000000016fdff8b4 // This next instruction is "bl". It should jump to the code for the C standard // library's puts function. In fact, it's a bit more complex: it goes into // a little code "stub" that then goes to the address of puts stored in // a special memory location by MacOS' dynamic loader "dyld" as it loads the // standard libraries. We'll look into the details of this linking next time. (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f80 hello`puts hello`puts: 0x100003f80 <+0>: nop 0x100003f84 <+4>: ldr x16, #0x407c ; (void *)0x0000000100003fa4 0x100003f88 <+8>: br x16 0x100003f8c: adr x17, #0x407c ; _dyld_private Target 0: (hello) stopped. // Let's look at x30 now. BL stored there the address in main() to return to, // right after the call to puts(): (lldb) reg r pc x30 pc = 0x0000000100003f80 hello`symbol stub for: puts lr = 0x0000000100003f6c hello`main + 56 // We are going to set a breakpoint there. (lldb) b 0x0000000100003f6c Breakpoint 3: where = hello`main + 56, address = 0x0000000100003f6c // Run freely until that (or another) breakpoint: (lldb) c Process 96089 resuming Hello Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1 frame #0: 0x0000000100003f6c hello`main + 56 hello`main: -> 0x100003f6c <+56>: b 0x100003f4c ; <+24> 0x100003f70 <+60>: mov w0, #0x2a 0x100003f74 <+64>: ldp x29, x30, [sp, #0x10] 0x100003f78 <+68>: add sp, sp, #0x20 ; =0x20 Target 0: (hello) stopped. (lldb) reg r pc x30 pc = 0x0000000100003f6c hello`main + 56 lr = 0x691d000100003f6c (0x0000000100003f6c) hello`main + 56 // We are back in main(), right after puts(): (lldb) disas -n main hello`main: 0x100003f34 <+0>: sub sp, sp, #0x20 ; =0x20 0x100003f38 <+4>: stp x29, x30, [sp, #0x10] 0x100003f3c <+8>: add x29, sp, #0x10 ; =0x10 0x100003f40 <+12>: stur wzr, [x29, #-0x4] 0x100003f44 <+16>: mov w8, #0xa 0x100003f48 <+20>: str w8, [sp, #0x8] 0x100003f4c <+24>: ldr w8, [sp, #0x8] 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] 0x100003f60 <+44>: adrp x0, 0 0x100003f64 <+48>: add x0, x0, #0xfb0 ; =0xfb0 0x100003f68 <+52>: bl 0x100003f80 ; symbol stub for: puts -> 0x100003f6c <+56>: b 0x100003f4c ; <+24> 0x100003f70 <+60>: mov w0, #0x2a 0x100003f74 <+64>: ldp x29, x30, [sp, #0x10] 0x100003f78 <+68>: add sp, sp, #0x20 ; =0x20 0x100003f7c <+72>: ret // List our breakpoints. We want to catch the loop, not stop after each puts(). (lldb) br l Current breakpoints: 1: name = 'main', locations = 1, resolved = 1, hit count = 1 1.1: where = hello`main, address = 0x0000000100003f34, resolved, hit count = 1 2: address = hello[0x0000000100003f5c], locations = 1, resolved = 1, hit count = 1 2.1: where = hello`main + 40, address = 0x0000000100003f5c, resolved, hit count = 1 3: address = hello[0x0000000100003f6c], locations = 1, resolved = 1, hit count = 1 3.1: where = hello`main + 56, address = 0x0000000100003f6c, resolved, hit count = 1 // Let's delete irrelevant breakpoints: (lldb) br del 2 1 breakpoints deleted; 0 breakpoint locations disabled. (lldb) br del 3 1 breakpoints deleted; 0 breakpoint locations disabled. // Instead, let's break on checking the loop condition: (lldb) b 0x100003f50 Breakpoint 4: where = hello`main + 28, address = 0x0000000100003f50 (lldb) c Process 96089 resuming Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1 frame #0: 0x0000000100003f50 hello`main + 28 hello`main: -> 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] Target 0: (hello) stopped. (lldb) reg r w8 w8 = 0x00000009 (lldb) c Process 96089 resuming Hello Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1 frame #0: 0x0000000100003f50 hello`main + 28 hello`main: -> 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] Target 0: (hello) stopped. // That's our next i when entering the loop: (lldb) reg r w8 w8 = 0x00000008 (lldb) c Process 96089 resuming Hello Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1 frame #0: 0x0000000100003f50 hello`main + 28 hello`main: -> 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] Target 0: (hello) stopped. // .. and the next: (lldb) reg r w8 w8 = 0x00000007 // Let's skip a few repetitions of this breakpoint until we count down to 0. // For that, we "modify" the breakpoint by adding a condition to check and // continue without breaking until it's true. (lldb) help br mod Modify the options on a breakpoint or set of breakpoints in the executable. If no breakpoint is specified, acts on the last created breakpoint. With the exception of -e, -d and -i, passing an empty argument clears the modification. Syntax: breakpoint modify [] Command Options Usage: breakpoint modify [-Dde] [-G ] [-c ] [-i ] [-o ] [-q ] [-t ] [-x ] [-T ] [] -D ( --dummy-breakpoints ) Act on Dummy breakpoints - i.e. breakpoints set before a file is provided, which prime new targets. -G ( --auto-continue ) The breakpoint will auto-continue after running its commands. -T ( --thread-name ) The breakpoint stops only for the thread whose thread name matches this argument. -c ( --condition ) The breakpoint stops only if this condition expression evaluates to true. -d ( --disable ) Disable the breakpoint. -e ( --enable ) Enable the breakpoint. -i ( --ignore-count ) Set the number of times this breakpoint is skipped before stopping. -o ( --one-shot ) The breakpoint is deleted the first time it stop causes a stop. -q ( --queue-name ) The breakpoint stops only for threads in the queue whose name is given by this argument. -t ( --thread-id ) The breakpoint stops only for the thread whose TID matches this argument. -x ( --thread-index ) The breakpoint stops only for the thread whose index matches this argument. This command takes options and free-form arguments. If your arguments resemble option specifiers (i.e., they start with a - or --), you must use ' -- ' between the end of the command options and the beginning of the arguments. (lldb) br l Current breakpoints: 1: name = 'main', locations = 1, resolved = 1, hit count = 1 1.1: where = hello`main, address = 0x0000000100003f34, resolved, hit count = 1 4: address = hello[0x0000000100003f50], locations = 1, resolved = 1, hit count = 3 4.1: where = hello`main + 28, address = 0x0000000100003f50, resolved, hit count = 3 // So let's set the condition that i's value in w0 is zero: (lldb) br mod -c '$w8 == 0' // ...and run till then: (lldb) c Process 96089 resuming Hello Hello Hello Hello Hello Hello Hello Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1 frame #0: 0x0000000100003f50 hello`main + 28 hello`main: -> 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] Target 0: (hello) stopped. // OK, we stopped just when the condition became true: (lldb) reg r w8 w8 = 0x00000000 // We have one more iteration to go. (lldb) si Process 96089 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f54 hello`main + 32 hello`main: -> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] 0x100003f60 <+44>: adrp x0, 0 Target 0: (hello) stopped. // BTW, what does a fired-on-condition breakpoint look like? The debugger provides // a hit count for it: (lldb) br l Current breakpoints: 1: name = 'main', locations = 1, resolved = 1, hit count = 1 1.1: where = hello`main, address = 0x0000000100003f34, resolved, hit count = 1 4: address = hello[0x0000000100003f50], locations = 1, resolved = 1, hit count = 4 Condition: $w8 == 0 4.1: where = hello`main + 28, address = 0x0000000100003f50, resolved, hit count = 4 // At this point, I let it run, and it finished, since there were no more breakpoints: (lldb) c Process 96089 resuming Hello Process 96089 exited with status = 42 (0x0000002a) // BUT, I then remembered that I wanted to see the "-1" in i! Too bad, the program // already finished, because I forgot to set a breakpoint. OK, start over. (lldb) r Process 96109 launched: '/Users/user/cs59/cosc59.gitlab.io/c-and-armv8/hello' (arm64) Process 96109 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100003f34 hello`main hello`main: -> 0x100003f34 <+0>: sub sp, sp, #0x20 ; =0x20 0x100003f38 <+4>: stp x29, x30, [sp, #0x10] 0x100003f3c <+8>: add x29, sp, #0x10 ; =0x10 0x100003f40 <+12>: stur wzr, [x29, #-0x4] Target 0: (hello) stopped. (lldb) br l Current breakpoints: 1: name = 'main', locations = 1, resolved = 1, hit count = 2 1.1: where = hello`main, address = 0x0000000100003f34, resolved, hit count = 2 4: address = hello[0x0000000100003f50], locations = 1, resolved = 1, hit count = 4 Condition: $w8 == 0 4.1: where = hello`main + 28, address = 0x0000000100003f50, resolved, hit count = 4 // Get to the point where we were: (lldb) br mod -c '$w8 == 0' (lldb) c Process 96109 resuming Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello Process 96109 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1 frame #0: 0x0000000100003f50 hello`main + 28 hello`main: -> 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] Target 0: (hello) stopped. (lldb) reg r w8 w8 = 0x00000000 (lldb) si Process 96109 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f54 hello`main + 32 hello`main: -> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] 0x100003f60 <+44>: adrp x0, 0 Target 0: (hello) stopped. // Note the updated hit counts: (lldb) br l Current breakpoints: 1: name = 'main', locations = 1, resolved = 1, hit count = 2 1.1: where = hello`main, address = 0x0000000100003f34, resolved, hit count = 2 4: address = hello[0x0000000100003f50], locations = 1, resolved = 1, hit count = 5 Condition: $w8 == 0 4.1: where = hello`main + 28, address = 0x0000000100003f50, resolved, hit count = 5 // We no longer need this breakpoint: (lldb) br del 4 1 breakpoints deleted; 0 breakpoint locations disabled. // Now let's set another breakpoint at the TBNZ loop condition check: (lldb) b 0x0000000100003f50 Breakpoint 5: where = hello`main + 28, address = 0x0000000100003f50 (lldb) c Process 96109 resuming Hello Process 96109 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 5.1 frame #0: 0x0000000100003f50 hello`main + 28 hello`main: -> 0x100003f50 <+28>: tbnz w8, #0x1f, 0x100003f70 ; <+60> 0x100003f54 <+32>: ldr w8, [sp, #0x8] 0x100003f58 <+36>: subs w8, w8, #0x1 ; =0x1 0x100003f5c <+40>: str w8, [sp, #0x8] Target 0: (hello) stopped. // Behold i being "-1" in two's complement: (lldb) reg r w8 w8 = 0xffffffff (lldb) si Process 96109 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f70 hello`main + 60 hello`main: -> 0x100003f70 <+60>: mov w0, #0x2a 0x100003f74 <+64>: ldp x29, x30, [sp, #0x10] 0x100003f78 <+68>: add sp, sp, #0x20 ; =0x20 0x100003f7c <+72>: ret Target 0: (hello) stopped. // We have now exited the loop. // Now it's time to set the return value for main(), in x0, as per armv8 calling // convention. There's something else in x0: (lldb) reg r w0 w0 = 0x0000000a (lldb) si Process 96109 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f74 hello`main + 64 hello`main: -> 0x100003f74 <+64>: ldp x29, x30, [sp, #0x10] 0x100003f78 <+68>: add sp, sp, #0x20 ; =0x20 0x100003f7c <+72>: ret hello`puts: 0x100003f80 <+0>: nop Target 0: (hello) stopped. // And now it's out "42" return value: (lldb) reg r w0 w0 = 0x0000002a (lldb) si Process 96109 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f78 hello`main + 68 hello`main: -> 0x100003f78 <+68>: add sp, sp, #0x20 ; =0x20 0x100003f7c <+72>: ret hello`puts: 0x100003f80 <+0>: nop 0x100003f84 <+4>: ldr x16, #0x407c ; (void *)0x000000018c97c004: puts Target 0: (hello) stopped. (lldb) reg r sp sp = 0x000000016fdff640 (lldb) si Process 96109 stopped * thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into frame #0: 0x0000000100003f7c hello`main + 72 hello`main: -> 0x100003f7c <+72>: ret hello`puts: 0x100003f80 <+0>: nop 0x100003f84 <+4>: ldr x16, #0x407c ; (void *)0x000000018c97c004: puts 0x100003f88 <+8>: br x16 Target 0: (hello) stopped. // The stack pointer is now restored to what it was when we entered main(): (lldb) reg r sp sp = 0x000000016fdff660 // We could examine the stack at this point and see the effects of the // post-amble. Do it. (lldb) c Process 96109 resuming Process 96109 exited with status = 42 (0x0000002a) // The program exist. To observe the effects of ret (really, "b x30") we'd want to set the breakpoint at x30 by "b $x30" or similar. Do it! :) (lldb) q