Hello Chris,
I had some fun with Go assembly, and got something similar to an assert.
breakpoint_arm64.s
TEXT ·breakpoint(SB),$0
WORD $0xd4200000 // brk #0
RET
Without debugger it aborts with SIGTRAP and no additional stack frames:
% go run .
SIGTRAP: trace trap
PC=0x1023749d0 m=0 sigcode=0
goroutine 1 [running]:
main.trap()
/Users/dottedmag/tmp/a/trap_arm64.s:2 fp=0x1400006ef60 sp=0x1400006ef60 pc=0x1023749d0
main.main()
/Users/dottedmag/tmp/a/a.go:6 +0x1c fp=0x1400006ef70 sp=0x1400006ef60 pc=0x1023749ac
<...>
Under delve it stops at assertion:
% dlv debug .
Type 'help' for list of commands.
(dlv) r
Process restarted with PID 64186
(dlv) c
> [hardcoded-breakpoint] main.trap() ./trap_arm64.s:3 (hits total:0) (PC: 0x100ce7c54)
1: TEXT ·trap(SB),$0
2: WORD $0xd4200000 // brk #0
=> 3: RET
(dlv) bt
0 0x0000000100ce7c54 in main.trap
at ./trap_arm64.s:3
1 0x0000000100ce7c2c in main.main
at ./a.go:6
2 0x0000000100cc0a40 in runtime.main
at /opt/homebrew/Cellar/go/1.19.5/libexec/src/runtime/proc.go:250
3 0x0000000100ce5cb4 in runtime.goexit
at /opt/homebrew/Cellar/go/1.19.5/libexec/src/runtime/asm_arm64.s:1172
(dlv)
Best,
Mikhail.
Fascinating, Mikhail! This works pretty nicely. Not only does this not
unwind the stack, it's continuable. I wondered if the RET was necessary,
so I tried removing it, and it seems the Go runtime gets lost. I suspect
it's because the instruction pointer lands just beyond the break outside
the function. Regardless, it's what makes continuation work anyway.
I also tried two versions for amd64, ud2 and int3:
TEXT ·breakpoint(SB),$0
WORD $0x0b0f // ud2
RET
TEXT ·breakpoint(SB),$0
BYTE $0xcc // int3
RET
The former doesn't really work at all, but the latter behaves exactly like
your arm64 breakpoint.
However, my one little complaint is that breaks in the assembly function,
a stack frame off from the target. That's not so bad, and it's better than
where delve breaks after a panic (deep inside the runtime), but I wondered
if we could do better. Since gc Go can't inline assembly functions, I
wondered if I could add a custom breakpoint intrinsic to the compiler so
that the break instruction is inlined at the "call" site.
While trying to figure this out, I stumbled across the already existing
solution: runtime.Breakpoint. This is just like your breakpoint, but delve
stops at the right place, despite it compiling to a function call, and
it's portable. This will be handy.
Thanks for the cool trick and leading me to runtime.Breakpoint!
Hey Kris,
On 14 Feb 2023, at 5:07, Christopher Wellons wrote:
> While trying to figure this out, I stumbled across the already existing solution: runtime.Breakpoint. This is just like your breakpoint, but delve stops at the right place, despite it compiling to a function call, and it's portable. This will be handy.
Very nice, TIL.
Best,
Mikhail.