ARM64 call method which relative offset is out of range of CALL instruction.
AlphaLxy opened this issue · comments
ARM64 CALL
instruction (BL <label>
)
Branch with Link branches to a PC-relative offset, setting the register X30 to PC+4.
It provides a hint that this is a subroutine call.
<label>
Is the program label to be unconditionally branched to.
Its offset from the address of this instruction, in the range +/-128MB, is encoded as "imm26" times 4.
So go compiler use another JMP
instruction when calling method which relative offset is out of range of CALL instruction.
Here is my own program debug session breaking on a method call.
TEXT xxxx(SB) xxxx/xxxx.go
xxxx.go:11 0x11c0e71e0 900b40f9 MOVD 16(R28), R16
xxxx.go:11 0x11c0e71e4 ff6330eb CMP R16, RSP
xxxx.go:11 0x11c0e71e8 69050054 BLS 43(PC)
xxxx.go:11 0x11c0e71ec fe0f19f8 MOVD.W R30, -112(RSP)
xxxx.go:11 0x11c0e71f0 fd831ff8 MOVD R29, -8(RSP)
xxxx.go:11 0x11c0e71f4 fd2300d1 SUB $8, RSP, R29
xxxx.go:11 0x11c0e71f8 e03f00f9 MOVD R0, 120(RSP)
xxxx.go:12 0x11c0e71fc b5f09c97 CALL -6491979(PC)
xxxx.go:12 0x11c0e7200 e02300f9 MOVD R0, 64(RSP)
xxxx.go:12 0x11c0e7204 e12700f9 MOVD R1, 72(RSP)
xxxx.go:13 0x11c0e7208 60c300f0 ADRP 25620480(PC), R0
xxxx.go:13 0x11c0e720c 00000791 ADD $448, R0, R0
xxxx.go:13 0x11c0e7210 b85bfe97 CALL -107592(PC)
xxxx.go:13 0x11c0e7214 e01f00f9 MOVD R0, 56(RSP)
xxxx.go:13 0x11c0e7218 410480d2 MOVD $34, R1
xxxx.go:13 0x11c0e721c 010400f9 MOVD R1, 8(R0)
xxxx.go:13 0x11c0e7220 a1c40790 ADRP 260653056(PC), R1
xxxx.go:13 0x11c0e7224 21401191 ADD $1104, R1, R1
xxxx.go:13 0x11c0e7228 210040b9 MOVWU (R1), R1
xxxx.go:13 0x11c0e722c 41000034 CBZW R1, 2(PC)
xxxx.go:13 0x11c0e7230 05000014 JMP 5(PC)
xxxx.go:13 0x11c0e7234 e60c00b0 ADRP 1691648(PC), R6
xxxx.go:13 0x11c0e7238 c61c1091 ADD $1031, R6, R6
xxxx.go:13 0x11c0e723c 060000f9 MOVD R6, (R0)
xxxx.go:13 0x11c0e7240 06000014 JMP 6(PC)
xxxx.go:13 0x11c0e7244 e20300aa MOVD R0, R2
xxxx.go:13 0x11c0e7248 e30c00b0 ADRP 1691648(PC), R3
xxxx.go:13 0x11c0e724c 631c1091 ADD $1031, R3, R3
xxxx.go:13 0x11c0e7250 b87fff97 CALL -32840(PC)
xxxx.go:13 0x11c0e7254 01000014 JMP 1(PC)
xxxx.go:13 0x11c0e7258 e31f40f9 MOVD 56(RSP), R3
xxxx.go:13 0x11c0e725c 7b008039 MOVB (R3), R27
xxxx.go:13 0x11c0e7260 01000014 JMP 1(PC)
xxxx.go:13 0x11c0e7264 e32b00f9 MOVD R3, 80(RSP)
xxxx.go:13 0x11c0e7268 e50340b2 ORR $1, ZR, R5
xxxx.go:13 0x11c0e726c e52f00f9 MOVD R5, 88(RSP)
xxxx.go:13 0x11c0e7270 e53300f9 MOVD R5, 96(RSP)
=> xxxx.go:14 0x11c0e7274 e02340f9 MOVD 64(RSP), R0
xxxx.go:14 0x11c0e7278 e23f40f9 MOVD 120(RSP), R2
xxxx.go:14 0x11c0e727c e12740f9 MOVD 72(RSP), R1
xxxx.go:14 0x11c0e7280 e40305aa MOVD R5, R4
xxxx.go:14 0x11c0e7284 0b000094 CALL 11(PC)
xxxx.go:15 0x11c0e7288 fdfb7fa9 LDP -8(RSP), (R29, R30)
xxxx.go:15 0x11c0e728c ffc30191 ADD $112, RSP, RSP
xxxx.go:15 0x11c0e7290 c0035fd6 RET
xxxx.go:11 0x11c0e7294 e00700f9 MOVD R0, 8(RSP)
xxxx.go:11 0x11c0e7298 e3031eaa MOVD R30, R3
xxxx.go:11 0x11c0e729c c13fff97 CALL -49215(PC)
xxxx.go:11 0x11c0e72a0 e00740f9 MOVD 8(RSP), R0
xxxx.go:11 0x11c0e72a4 cfffff17 JMP xxxx
xxxx.go:11 0x11c0e72a8 00000000 ?
xxxx.go:11 0x11c0e72ac 00000000 ?
Note that last CALL instruction.
xxxx.go:14 0x11c0e7284 0b000094 CALL 11(PC)
Then I use step command, it will run to 0x11c0e72b0
and show no source available
.
(dlv) s
Stopped at: 0x11c0e72b0
=> 1: no source available
(dlv) s
Stopped at: 0x11c0e72b0
=> 1: no source available
Command failed: no source for PC 0x11c0e72b0
Then I disassemble 0x11c0e72b0
.
(dlv) disassemble -a 0x11c0e72b0 0x11c0e72c0
.:0 0x11c0e72b0 702af6f0 ADRP -329977856(PC), R16
.:0 0x11c0e72b4 10c20691 ADD $432, R16, R16
.:0 0x11c0e72b8 00021fd6 JMP (R16)
.:0 0x11c0e72bc 00000000 ?
Can Delve step into target method directly (or use another s
Command) in this situation ?
- What version of Delve are you using (
dlv version
)?
Delve Debugger
Version: 1.21.2
- What version of Go are you using? (
go version
)?
go version go1.20.10 darwin/arm64
- What operating system and processor architecture are you using?
MacOS 14.2.1 (23C71)
Apple M1 Pro
You can use step-instruction (si) three times. As for making delve do this, it looks complicated.
The GoLand UI is not something we develop.
I tried this, add three StepInstruction
after Step
when next three instructions is ADRP
ADD
JMP
. It seems ok.
pkg/proc/target_exec.go
err = grp.Continue()
if err != nil {
return err
}
if grp.Selected.Process.BinInfo().Arch.Name == "arm64" {
selg := grp.Selected.SelectedGoroutine()
curthread := grp.Selected.CurrentThread()
topframe, _, _ := topframe(grp.Selected, selg, curthread)
if topframe.Current.Fn != nil {
return nil
}
registers, _ := grp.Selected.currentThread.Registers()
pc := registers.PC()
mem := make([]byte, 12)
n, _ := grp.Selected.Process.Memory().ReadMemory(mem, pc)
if n == 12 {
i0 := binary.LittleEndian.Uint32(mem[0:])
i1 := binary.LittleEndian.Uint32(mem[4:])
i2 := binary.LittleEndian.Uint32(mem[8:])
i0ADRP := (i0 & 0b1_00_11111_0000000000000000000_00000) == 0b1_00_10000_0000000000000000000_00000
i1ADD := (i1 & 0b1001000100_000000000000_00000_00000) == 0b1001000100_000000000000_00000_00000
i2JMP := (i2 & 0b1101011000011111000000_0000011111) == 0b1101011000011111000000_00000_00000
if i0ADRP && i1ADD && i2JMP {
err = grp.StepInstruction()
if err != nil {
return err
}
err = grp.StepInstruction()
if err != nil {
return err
}
return grp.StepInstruction()
}
}
}
return err
This is the wrong approach, it will break if a breakpoint is encountered during step
, or a manual stop, or the process exit, or if the three instructions happen to be ADRP+ADD+JMP but they aren't a trampoline for a CALL instruction.
I check if topframe.Current.Fn == nil
.
Or I can add that code after s
command failed.
if err = next(grp.Selected, true, false); err != nil {
selg := grp.Selected.SelectedGoroutine()
curthread := grp.Selected.CurrentThread()
topframe, _, _ := topframe(grp.Selected, selg, curthread)
if topframe.Current.Fn == nil && grp.Selected.Process.BinInfo().Arch.Name == "arm64" {
// ...
return err
}
_ = grp.Selected.ClearSteppingBreakpoints()
return err
}
Make second s
command ok.
(dlv) s
Stopped at: 0x11c0e72b0
=> 1: no source available
(dlv) s // !!! this !!!
Stopped at: 0x11c0e72b0
=> 1: no source available
Command failed: no source for PC 0x11c0e72b0
This is the wrong approach, it will break if a breakpoint is encountered during
step
, or a manual stop, or the process exit, or if the three instructions happen to be ADRP+ADD+JMP but they aren't a trampoline for a CALL instruction.
I see, is there any other way?