Stepping over a `brk` instruction on ARM64

I posted this question on Stack Overflow, but I’m afraid SO might not be the right forum. Please forgive me for cross posting.

I’m working on a JIT compiler on the ARM64 platform. In order to debug the machine code it emits, sometimes I will have the compiler emit a brk instruction so that I can inspect the state of things using lldb.

Unlike the x86_64 platform, the PC register is at the instruction (not after it):

Process 61859 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1008e8020)
    frame #0: 0x00000001008e8020
->  0x1008e8020: brk    #0x1
    0x1008e8024: ldur   x9, [x9]
    0x1008e8028: mov    x10, #0x4000
    0x1008e802c: movk   x10, #0x8e, lsl #16
Target 0: (ruby) stopped.
(lldb) p/x $pc
(unsigned long) $3 = 0x00000001008e8020
(lldb)

This means I cannot use si in lldb to step over the brk instruction because the brk instruction will just be evaluated over and over:

Process 61859 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1008e8020)
    frame #0: 0x00000001008e8020
->  0x1008e8020: brk    #0x1
    0x1008e8024: ldur   x9, [x9]
    0x1008e8028: mov    x10, #0x4000
    0x1008e802c: movk   x10, #0x8e, lsl #16
Target 0: (ruby) stopped.
(lldb) p/x $pc
(unsigned long) $3 = 0x00000001008e8020
(lldb) si
Process 61859 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1008e8020)
    frame #0: 0x00000001008e8020
->  0x1008e8020: brk    #0x1
    0x1008e8024: ldur   x9, [x9]
    0x1008e8028: mov    x10, #0x4000
    0x1008e802c: movk   x10, #0x8e, lsl #16
Target 0: (ruby) stopped.
(lldb) si
Process 61859 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1008e8020)
    frame #0: 0x00000001008e8020
->  0x1008e8020: brk    #0x1
    0x1008e8024: ldur   x9, [x9]
    0x1008e8028: mov    x10, #0x4000
    0x1008e802c: movk   x10, #0x8e, lsl #16
Target 0: (ruby) stopped.
(lldb)

I know that I can directly write to the $pc register to step over the instruction, but I don’t want to copy / paste the next address every time. I used to use this command:

register write $pc `$pc + 4`

But something changed on macOS such that the above command doesn’t work anymore. I get the following error:

error: Failed to write register 'pc' with value '$pc + 4': '$pc + 4' is not a valid unsigned integer string value

Is there a better or more supported way to step over brk instructions on ARM64? I’m currently using this version of lldb:

$ lldb -v
lldb-1403.0.17.64
Apple Swift version 5.8 (swiftlang-5.8.0.124.2 clang-1403.0.22.11.100)

Thanks, and again I’m really sorry for cross posting. I am not sure where to ask this question. :pensive:

Yeah it looks like a regression or unintended side effect of something between Xcode 14.2 (lldb-1400) and Xcode 14.3 (lldb-1403), but does not happen on GitHub top of tree sources.

(lldb) p/x $pc
(unsigned long) $0 = 0x0000000100003e78
(lldb) reg write pc `$pc+4`
(lldb) p/x $pc
(unsigned long) $2 = 0x0000000100003e7c
(lldb) reg write pc `0x0000000100003e7c`
(lldb) reg write pc `0x0000000100003e7cU`

versus lldb-1403 in Xcode 14.3:

(lldb) reg write pc `$pc+4`
error: Failed to write register 'pc' with value '$pc+4': '$pc+4' is not a valid unsigned integer string value
(lldb) reg write pc `0x100003e80UL`
error: Failed to write register 'pc' with value '0x100003e80UL': '0x100003e80UL' is not a valid unsigned integer string value

Interesting. It seems to be an issue with the Xcode lldb, I would file a bug report with feedbackassistant.apple.com .

1 Like

Great, thank you so much for the help. I’ll file an issue and in the mean time build from source.

Thank you so much!

This was fixed in:

75ca15fcbb2e1b3671e41f971a000c6d59f5e5ae

Jim

On Apr 4, 2023, at 5:52 PM, Aaron Patterson via LLVM Discussion Forums notifications@llvm.discoursemail.com wrote:

tenderlove
April 5

Great, thank you so much for the help. I’ll file an issue and in the mean time build from source.

Thank you so much!


Visit Topic or reply to this email to respond.

To unsubscribe from these emails, click here.

Thank you for fixing it! I really appreciate it.

Reviving this very long thread. I’m hitting this same issue on graviton ARM, so I’d like to apply a similar fix as @jingham 's, but 75ca15fcbb2e1b3671e41f971a000c6d59f5e5ae doesn’t actually point to the fix. Could the actual fix be shared here?

Thanks!

Fix backtick handling in parsed commands. · llvm/llvm-project@75ca15f · GitHub fixes the workaround the original poster was using. I’ve confirmed that myself on an AArch64 machine.

I didn’t use a Graviton for my check, I could, but I don’t know of any difference that should prevent this from working there.

Are you not seeing the same thing? Or when you say “actual” do you mean another way of fixing it?

One thing this topic never touched on was why there was a raw BRK instruction in the program. For what it’s worth in 2020 I added support in debugserver that if it sees a brk #0xf000 on aarch64, it silently advances the PC past this. This pattern, at least on darwin, is emitted by __builtin_debugtrap() and if we don’t advance past it silently, the user will just hit the BRK over and over. I did this in

commit 92b036dea24b6e7ebfd950facdbf5543135eda05
Author: Jason Molenda <jason@molenda.com>
Date:   Thu Nov 12 23:28:24 2020 -0800

    debugserver should advance pc past builtin_debugtrap insn

https://reviews.llvm.org/D91238

The original description shows a brk #0x1 so that’s not going to match the imm value I check here, but I bet they were inserting a breakpoint to gain control in the debugger manually instead of using the builtin.

Thank you for all the info. I’ll let you know once I debug this issue again and try to fix it.

I did mention why there’s a raw BRK in the OP.

I’m working on a JIT compiler on the ARM64 platform. In order to debug the machine code it emits, sometimes I will have the compiler emit a brk instruction so that I can inspect the state of things using lldb.

I’ll just use brk #0xf000 in my JIT compiler (which has its own assembler, maybe that wasn’t clear). Thanks! :smiley: