refactor(debug): make Regs and breakpoint arch-neutral for arm64

Assisted-by: MiMo V2.5 Pro
This commit is contained in:
2026-08-21 00:35:21 +02:00
parent 874e054ecb
commit ba502c9b79
4 changed files with 258 additions and 79 deletions
+29 -47
View File
@@ -25,43 +25,8 @@ type Condition struct {
// Eval checks the condition against the current registers.
func (c *Condition) Eval(regs *Regs) bool {
var actual uint64
switch c.Reg {
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:
actual, ok := regs.RegValue(c.Reg)
if !ok {
return true // unknown register — don't block
}
switch c.Op {
@@ -106,14 +71,18 @@ func (bm *Breakpoints) SetWithCond(addr uint64, label string, cond *Condition) (
bp.Cond = cond
return bp, nil
}
// Read the original byte.
// Read the original bytes.
word, err := bm.t.Peek(addr)
if err != nil {
return nil, err
}
orig := byte(word)
// Patch with INT3 (0xCC), preserving the rest of the word.
patched := (word &^ 0xFF) | 0xCC
// Patch with the breakpoint instruction, preserving the rest of the word.
mask := uint64(0)
for range breakpointInsn {
mask = (mask << 8) | 0xFF
}
patched := (word &^ mask) | breakpointWord(breakpointInsn)
if err := bm.t.Poke(addr, patched); err != nil {
return nil, err
}
@@ -196,12 +165,12 @@ func (bm *Breakpoints) All() []*Breakpoint {
}
// HandleTrap is called after the debuggee stops on SIGTRAP. It checks
// whether the trap was caused by one of our breakpoints (RIP-1 matches
// a breakpoint address), restores the original byte, rewinds RIP, and
// whether the trap was caused by one of our breakpoints (PC-adjust matches
// a breakpoint address), restores the original byte, rewinds PC, and
// returns the breakpoint that was hit (or nil if it was a single-step).
func (bm *Breakpoints) HandleTrap(regs *Regs) *Breakpoint {
// After INT3, RIP points to the byte AFTER the 0xCC.
trapAddr := regs.RIP - 1
// After a breakpoint trap, PC points past the breakpoint instruction.
trapAddr := regs.GetPC() - uint64(breakpointPCAdjust)
bp, ok := bm.bps[trapAddr]
if !ok || !bp.Enabled {
return nil // single-step trap or unknown
@@ -225,8 +194,8 @@ func (bm *Breakpoints) HandleTrap(regs *Regs) *Breakpoint {
restored := (word &^ 0xFF) | uint64(bp.Orig)
bm.t.Poke(trapAddr, restored)
}
// Rewind RIP to re-execute the original instruction.
regs.RIP = trapAddr
// Rewind PC to re-execute the original instruction.
regs.SetPC(trapAddr)
bm.t.SetRegs(regs)
return bp
}
@@ -243,6 +212,19 @@ func (bm *Breakpoints) Reinsert(addr uint64) error {
if err != nil {
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)
}
// 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
}