refactor(debug): make Regs and breakpoint arch-neutral for arm64
Assisted-by: MiMo V2.5 Pro
This commit is contained in:
+29
-47
@@ -25,43 +25,8 @@ type Condition struct {
|
|||||||
|
|
||||||
// Eval checks the condition against the current registers.
|
// Eval checks the condition against the current registers.
|
||||||
func (c *Condition) Eval(regs *Regs) bool {
|
func (c *Condition) Eval(regs *Regs) bool {
|
||||||
var actual uint64
|
actual, ok := regs.RegValue(c.Reg)
|
||||||
switch c.Reg {
|
if !ok {
|
||||||
case "rax", "eax", "ax", "al":
|
|
||||||
actual = regs.RAX
|
|
||||||
case "rbx", "ebx", "bx", "bl":
|
|
||||||
actual = regs.RBX
|
|
||||||
case "rcx", "ecx", "cx", "cl":
|
|
||||||
actual = regs.RCX
|
|
||||||
case "rdx", "edx", "dx", "dl":
|
|
||||||
actual = regs.RDX
|
|
||||||
case "rsi", "esi", "si":
|
|
||||||
actual = regs.RSI
|
|
||||||
case "rdi", "edi", "di":
|
|
||||||
actual = regs.RDI
|
|
||||||
case "rbp", "ebp", "bp":
|
|
||||||
actual = regs.RBP
|
|
||||||
case "rsp", "esp", "sp":
|
|
||||||
actual = regs.RSP
|
|
||||||
case "r8":
|
|
||||||
actual = regs.R8
|
|
||||||
case "r9":
|
|
||||||
actual = regs.R9
|
|
||||||
case "r10":
|
|
||||||
actual = regs.R10
|
|
||||||
case "r11":
|
|
||||||
actual = regs.R11
|
|
||||||
case "r12":
|
|
||||||
actual = regs.R12
|
|
||||||
case "r13":
|
|
||||||
actual = regs.R13
|
|
||||||
case "r14":
|
|
||||||
actual = regs.R14
|
|
||||||
case "r15":
|
|
||||||
actual = regs.R15
|
|
||||||
case "rip", "eip":
|
|
||||||
actual = regs.RIP
|
|
||||||
default:
|
|
||||||
return true // unknown register — don't block
|
return true // unknown register — don't block
|
||||||
}
|
}
|
||||||
switch c.Op {
|
switch c.Op {
|
||||||
@@ -106,14 +71,18 @@ func (bm *Breakpoints) SetWithCond(addr uint64, label string, cond *Condition) (
|
|||||||
bp.Cond = cond
|
bp.Cond = cond
|
||||||
return bp, nil
|
return bp, nil
|
||||||
}
|
}
|
||||||
// Read the original byte.
|
// Read the original bytes.
|
||||||
word, err := bm.t.Peek(addr)
|
word, err := bm.t.Peek(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
orig := byte(word)
|
orig := byte(word)
|
||||||
// Patch with INT3 (0xCC), preserving the rest of the word.
|
// Patch with the breakpoint instruction, preserving the rest of the word.
|
||||||
patched := (word &^ 0xFF) | 0xCC
|
mask := uint64(0)
|
||||||
|
for range breakpointInsn {
|
||||||
|
mask = (mask << 8) | 0xFF
|
||||||
|
}
|
||||||
|
patched := (word &^ mask) | breakpointWord(breakpointInsn)
|
||||||
if err := bm.t.Poke(addr, patched); err != nil {
|
if err := bm.t.Poke(addr, patched); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -196,12 +165,12 @@ func (bm *Breakpoints) All() []*Breakpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleTrap is called after the debuggee stops on SIGTRAP. It checks
|
// HandleTrap is called after the debuggee stops on SIGTRAP. It checks
|
||||||
// whether the trap was caused by one of our breakpoints (RIP-1 matches
|
// whether the trap was caused by one of our breakpoints (PC-adjust matches
|
||||||
// a breakpoint address), restores the original byte, rewinds RIP, and
|
// a breakpoint address), restores the original byte, rewinds PC, and
|
||||||
// returns the breakpoint that was hit (or nil if it was a single-step).
|
// returns the breakpoint that was hit (or nil if it was a single-step).
|
||||||
func (bm *Breakpoints) HandleTrap(regs *Regs) *Breakpoint {
|
func (bm *Breakpoints) HandleTrap(regs *Regs) *Breakpoint {
|
||||||
// After INT3, RIP points to the byte AFTER the 0xCC.
|
// After a breakpoint trap, PC points past the breakpoint instruction.
|
||||||
trapAddr := regs.RIP - 1
|
trapAddr := regs.GetPC() - uint64(breakpointPCAdjust)
|
||||||
bp, ok := bm.bps[trapAddr]
|
bp, ok := bm.bps[trapAddr]
|
||||||
if !ok || !bp.Enabled {
|
if !ok || !bp.Enabled {
|
||||||
return nil // single-step trap or unknown
|
return nil // single-step trap or unknown
|
||||||
@@ -225,8 +194,8 @@ func (bm *Breakpoints) HandleTrap(regs *Regs) *Breakpoint {
|
|||||||
restored := (word &^ 0xFF) | uint64(bp.Orig)
|
restored := (word &^ 0xFF) | uint64(bp.Orig)
|
||||||
bm.t.Poke(trapAddr, restored)
|
bm.t.Poke(trapAddr, restored)
|
||||||
}
|
}
|
||||||
// Rewind RIP to re-execute the original instruction.
|
// Rewind PC to re-execute the original instruction.
|
||||||
regs.RIP = trapAddr
|
regs.SetPC(trapAddr)
|
||||||
bm.t.SetRegs(regs)
|
bm.t.SetRegs(regs)
|
||||||
return bp
|
return bp
|
||||||
}
|
}
|
||||||
@@ -243,6 +212,19 @@ func (bm *Breakpoints) Reinsert(addr uint64) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
patched := (word &^ 0xFF) | 0xCC
|
mask := uint64(0)
|
||||||
|
for range breakpointInsn {
|
||||||
|
mask = (mask << 8) | 0xFF
|
||||||
|
}
|
||||||
|
patched := (word &^ mask) | breakpointWord(breakpointInsn)
|
||||||
return bm.t.Poke(addr, patched)
|
return bm.t.Poke(addr, patched)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// breakpointWord converts the breakpoint instruction bytes to a uint64.
|
||||||
|
func breakpointWord(insn []byte) uint64 {
|
||||||
|
var w uint64
|
||||||
|
for i, b := range insn {
|
||||||
|
w |= uint64(b) << (i * 8)
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
//go:build linux && amd64
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
// Regs holds the full general-purpose register set of a traced process
|
||||||
|
// (the Linux amd64 user_regs_struct layout).
|
||||||
|
type Regs struct {
|
||||||
|
R15 uint64
|
||||||
|
R14 uint64
|
||||||
|
R13 uint64
|
||||||
|
R12 uint64
|
||||||
|
RBP uint64
|
||||||
|
RBX uint64
|
||||||
|
R11 uint64
|
||||||
|
R10 uint64
|
||||||
|
R9 uint64
|
||||||
|
R8 uint64
|
||||||
|
RAX uint64
|
||||||
|
RCX uint64
|
||||||
|
RDX uint64
|
||||||
|
RSI uint64
|
||||||
|
RDI uint64
|
||||||
|
OrigRAX uint64
|
||||||
|
RIP uint64
|
||||||
|
CS uint64
|
||||||
|
RFLAGS uint64
|
||||||
|
RSP uint64
|
||||||
|
SS uint64
|
||||||
|
FSBase uint64
|
||||||
|
GSBase uint64
|
||||||
|
DS uint64
|
||||||
|
ES uint64
|
||||||
|
FS uint64
|
||||||
|
GS uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPC returns the program counter.
|
||||||
|
func (r *Regs) GetPC() uint64 { return r.RIP }
|
||||||
|
|
||||||
|
// SetPC sets the program counter.
|
||||||
|
func (r *Regs) SetPC(pc uint64) { r.RIP = pc }
|
||||||
|
|
||||||
|
// GetSP returns the stack pointer.
|
||||||
|
func (r *Regs) GetSP() uint64 { return r.RSP }
|
||||||
|
|
||||||
|
// RegValue returns the value of the named register, or false if unknown.
|
||||||
|
func (r *Regs) RegValue(name string) (uint64, bool) {
|
||||||
|
switch name {
|
||||||
|
case "rax", "eax", "ax", "al":
|
||||||
|
return r.RAX, true
|
||||||
|
case "rbx", "ebx", "bx", "bl":
|
||||||
|
return r.RBX, true
|
||||||
|
case "rcx", "ecx", "cx", "cl":
|
||||||
|
return r.RCX, true
|
||||||
|
case "rdx", "edx", "dx", "dl":
|
||||||
|
return r.RDX, true
|
||||||
|
case "rsi", "esi", "si":
|
||||||
|
return r.RSI, true
|
||||||
|
case "rdi", "edi", "di":
|
||||||
|
return r.RDI, true
|
||||||
|
case "rbp", "ebp", "bp":
|
||||||
|
return r.RBP, true
|
||||||
|
case "rsp", "esp", "sp":
|
||||||
|
return r.RSP, true
|
||||||
|
case "r8":
|
||||||
|
return r.R8, true
|
||||||
|
case "r9":
|
||||||
|
return r.R9, true
|
||||||
|
case "r10":
|
||||||
|
return r.R10, true
|
||||||
|
case "r11":
|
||||||
|
return r.R11, true
|
||||||
|
case "r12":
|
||||||
|
return r.R12, true
|
||||||
|
case "r13":
|
||||||
|
return r.R13, true
|
||||||
|
case "r14":
|
||||||
|
return r.R14, true
|
||||||
|
case "r15":
|
||||||
|
return r.R15, true
|
||||||
|
case "rip", "eip":
|
||||||
|
return r.RIP, true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// breakpointInsn is the software breakpoint instruction.
|
||||||
|
var breakpointInsn = []byte{0xCC} // INT3
|
||||||
|
|
||||||
|
// breakpointPCAdjust is how far PC is past the breakpoint instruction after a trap.
|
||||||
|
const breakpointPCAdjust = 1
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
//go:build linux && arm64
|
||||||
|
|
||||||
|
package debug
|
||||||
|
|
||||||
|
// Regs holds the full general-purpose register set of a traced process
|
||||||
|
// (the Linux arm64 user_pt_regs layout).
|
||||||
|
type Regs struct {
|
||||||
|
X0 uint64
|
||||||
|
X1 uint64
|
||||||
|
X2 uint64
|
||||||
|
X3 uint64
|
||||||
|
X4 uint64
|
||||||
|
X5 uint64
|
||||||
|
X6 uint64
|
||||||
|
X7 uint64
|
||||||
|
X8 uint64
|
||||||
|
X9 uint64
|
||||||
|
X10 uint64
|
||||||
|
X11 uint64
|
||||||
|
X12 uint64
|
||||||
|
X13 uint64
|
||||||
|
X14 uint64
|
||||||
|
X15 uint64
|
||||||
|
X16 uint64
|
||||||
|
X17 uint64
|
||||||
|
X18 uint64
|
||||||
|
X19 uint64
|
||||||
|
X20 uint64
|
||||||
|
X21 uint64
|
||||||
|
X22 uint64
|
||||||
|
X23 uint64
|
||||||
|
X24 uint64
|
||||||
|
X25 uint64
|
||||||
|
X26 uint64
|
||||||
|
X27 uint64
|
||||||
|
X28 uint64
|
||||||
|
X29 uint64 // FP (frame pointer)
|
||||||
|
X30 uint64 // LR (link register)
|
||||||
|
SP uint64
|
||||||
|
PC uint64
|
||||||
|
PSTATE uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// PC returns the program counter.
|
||||||
|
func (r *Regs) GetPC() uint64 { return r.PC }
|
||||||
|
|
||||||
|
// SetPC sets the program counter.
|
||||||
|
func (r *Regs) SetPC(pc uint64) { r.PC = pc }
|
||||||
|
|
||||||
|
// GetSP returns the stack pointer.
|
||||||
|
func (r *Regs) GetSP() uint64 { return r.SP }
|
||||||
|
|
||||||
|
// RegValue returns the value of the named register, or false if unknown.
|
||||||
|
func (r *Regs) RegValue(name string) (uint64, bool) {
|
||||||
|
switch name {
|
||||||
|
case "x0":
|
||||||
|
return r.X0, true
|
||||||
|
case "x1":
|
||||||
|
return r.X1, true
|
||||||
|
case "x2":
|
||||||
|
return r.X2, true
|
||||||
|
case "x3":
|
||||||
|
return r.X3, true
|
||||||
|
case "x4":
|
||||||
|
return r.X4, true
|
||||||
|
case "x5":
|
||||||
|
return r.X5, true
|
||||||
|
case "x6":
|
||||||
|
return r.X6, true
|
||||||
|
case "x7":
|
||||||
|
return r.X7, true
|
||||||
|
case "x8":
|
||||||
|
return r.X8, true
|
||||||
|
case "x9":
|
||||||
|
return r.X9, true
|
||||||
|
case "x10":
|
||||||
|
return r.X10, true
|
||||||
|
case "x11":
|
||||||
|
return r.X11, true
|
||||||
|
case "x12":
|
||||||
|
return r.X12, true
|
||||||
|
case "x13":
|
||||||
|
return r.X13, true
|
||||||
|
case "x14":
|
||||||
|
return r.X14, true
|
||||||
|
case "x15":
|
||||||
|
return r.X15, true
|
||||||
|
case "x16":
|
||||||
|
return r.X16, true
|
||||||
|
case "x17":
|
||||||
|
return r.X17, true
|
||||||
|
case "x18":
|
||||||
|
return r.X18, true
|
||||||
|
case "x19":
|
||||||
|
return r.X19, true
|
||||||
|
case "x20":
|
||||||
|
return r.X20, true
|
||||||
|
case "x21":
|
||||||
|
return r.X21, true
|
||||||
|
case "x22":
|
||||||
|
return r.X22, true
|
||||||
|
case "x23":
|
||||||
|
return r.X23, true
|
||||||
|
case "x24":
|
||||||
|
return r.X24, true
|
||||||
|
case "x25":
|
||||||
|
return r.X25, true
|
||||||
|
case "x26":
|
||||||
|
return r.X26, true
|
||||||
|
case "x27":
|
||||||
|
return r.X27, true
|
||||||
|
case "x28":
|
||||||
|
return r.X28, true
|
||||||
|
case "x29", "fp":
|
||||||
|
return r.X29, true
|
||||||
|
case "x30", "lr":
|
||||||
|
return r.X30, true
|
||||||
|
case "sp":
|
||||||
|
return r.SP, true
|
||||||
|
case "pc":
|
||||||
|
return r.PC, true
|
||||||
|
default:
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// breakpointInsn is the software breakpoint instruction (BRK #0).
|
||||||
|
var breakpointInsn = []byte{0x00, 0x00, 0x20, 0xD4} // BRK #0
|
||||||
|
|
||||||
|
// breakpointPCAdjust is how far PC is past the breakpoint instruction after a trap.
|
||||||
|
const breakpointPCAdjust = 4
|
||||||
@@ -3,38 +3,6 @@
|
|||||||
|
|
||||||
package debug
|
package debug
|
||||||
|
|
||||||
// Regs holds the full general-purpose register set of a traced process
|
|
||||||
// (the Linux amd64 user_regs_struct layout).
|
|
||||||
type Regs struct {
|
|
||||||
R15 uint64
|
|
||||||
R14 uint64
|
|
||||||
R13 uint64
|
|
||||||
R12 uint64
|
|
||||||
RBP uint64
|
|
||||||
RBX uint64
|
|
||||||
R11 uint64
|
|
||||||
R10 uint64
|
|
||||||
R9 uint64
|
|
||||||
R8 uint64
|
|
||||||
RAX uint64
|
|
||||||
RCX uint64
|
|
||||||
RDX uint64
|
|
||||||
RSI uint64
|
|
||||||
RDI uint64
|
|
||||||
OrigRAX uint64
|
|
||||||
RIP uint64
|
|
||||||
CS uint64
|
|
||||||
RFLAGS uint64
|
|
||||||
RSP uint64
|
|
||||||
SS uint64
|
|
||||||
FSBase uint64
|
|
||||||
GSBase uint64
|
|
||||||
DS uint64
|
|
||||||
ES uint64
|
|
||||||
FS uint64
|
|
||||||
GS uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// tracer abstracts the minimal ptrace operations needed by the breakpoint
|
// tracer abstracts the minimal ptrace operations needed by the breakpoint
|
||||||
// manager and the stop-information helpers. The live implementation is
|
// manager and the stop-information helpers. The live implementation is
|
||||||
// *Session (ptrace_linux_amd64.go); tests supply a mock.
|
// *Session (ptrace_linux_amd64.go); tests supply a mock.
|
||||||
|
|||||||
Reference in New Issue
Block a user