feat(debug): multi-architecture debugger support for arm64, riscv64, loong64
Assisted-by: MiMo V2.5 Pro
This commit is contained in:
+29
-124
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build linux && amd64
|
||||
//go:build linux
|
||||
|
||||
package debug
|
||||
|
||||
@@ -26,15 +26,10 @@ type SourceLine struct {
|
||||
Line int
|
||||
}
|
||||
|
||||
// REPL runs the interactive debugger loop. On entry, the debuggee is
|
||||
// stopped in the Go runtime (after PTRACE_TRACEME + SIGSTOP). The REPL
|
||||
// sets a temporary breakpoint at the function entry, continues to it, and
|
||||
// then presents the prompt — so the user starts debugging at the first
|
||||
// instruction of the assembled function.
|
||||
// REPL runs the interactive debugger loop.
|
||||
func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, argsSize int, labels []Label, lines []SourceLine) {
|
||||
entryAddr := codeBase + uint64(funcOffset)
|
||||
|
||||
// The debuggee is already stopped at the function entry point.
|
||||
fmt.Printf("stopped at function entry: %#x (%d bytes)\n", entryAddr, funcSize)
|
||||
fmt.Println("commands: break <label|addr> | step [n] | continue | disas [n] | regs | where | x <addr> [len] | w <addr> <val...> | labels | quit")
|
||||
|
||||
@@ -64,7 +59,6 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
continue
|
||||
}
|
||||
printRegs(®s, codeBase, uint64(funcOffset))
|
||||
// Also show vector registers.
|
||||
vregs, err := s.GetVectorRegs()
|
||||
if err != nil {
|
||||
fmt.Printf(" (vector regs unavailable: %v)\n", err)
|
||||
@@ -89,24 +83,22 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
}
|
||||
if !s.Exited() {
|
||||
regs, _ := s.GetRegs()
|
||||
text, _, _ := s.Disassemble(regs.RIP)
|
||||
fmt.Printf("=> %#x (func+%#x): %s\n", regs.RIP, regs.RIP-codeBase-uint64(funcOffset), text)
|
||||
pc := regs.GetPC()
|
||||
text, _, _ := s.Disassemble(pc)
|
||||
fmt.Printf("=> %#x (func+%#x): %s\n", pc, pc-codeBase-uint64(funcOffset), text)
|
||||
}
|
||||
|
||||
case "next", "n":
|
||||
// Step over: if the current instruction is a CALL, set a
|
||||
// breakpoint after it and continue; otherwise single-step.
|
||||
regs, _ := s.GetRegs()
|
||||
text, instLen, _ := s.Disassemble(regs.RIP)
|
||||
if strings.HasPrefix(strings.ToLower(text), "call") {
|
||||
// Set a temporary breakpoint after the CALL.
|
||||
afterAddr := regs.RIP + uint64(instLen)
|
||||
pc := regs.GetPC()
|
||||
text, instLen, _ := s.Disassemble(pc)
|
||||
if strings.HasPrefix(strings.ToLower(text), "call") || strings.HasPrefix(strings.ToLower(text), "bl") {
|
||||
afterAddr := pc + uint64(instLen)
|
||||
bp, err := bm.Set(afterAddr, "(next)")
|
||||
if err != nil {
|
||||
fmt.Printf("cannot set next breakpoint: %v\n", err)
|
||||
continue
|
||||
}
|
||||
// Continue until the breakpoint.
|
||||
for _, b := range bm.All() {
|
||||
bm.Reinsert(b.Addr)
|
||||
}
|
||||
@@ -119,7 +111,6 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
bm.Clear(afterAddr)
|
||||
_ = bp
|
||||
} else {
|
||||
// Not a CALL — just single-step.
|
||||
if err := s.Step(); err != nil {
|
||||
fmt.Println(err)
|
||||
continue
|
||||
@@ -127,26 +118,23 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
}
|
||||
if !s.Exited() {
|
||||
regs, _ := s.GetRegs()
|
||||
text, _, _ := s.Disassemble(regs.RIP)
|
||||
fmt.Printf("=> %#x (func+%#x): %s\n", regs.RIP, regs.RIP-codeBase-uint64(funcOffset), text)
|
||||
pc := regs.GetPC()
|
||||
text, _, _ := s.Disassemble(pc)
|
||||
fmt.Printf("=> %#x (func+%#x): %s\n", pc, pc-codeBase-uint64(funcOffset), text)
|
||||
}
|
||||
|
||||
case "finish", "fin":
|
||||
// Run until the current function returns.
|
||||
// For NOSPLIT frame=0: return address is at [RSP].
|
||||
regs, _ := s.GetRegs()
|
||||
retAddr, err := s.Peek(regs.RSP)
|
||||
retAddr, err := archReturnAddr(s, ®s)
|
||||
if err != nil {
|
||||
fmt.Printf("cannot read return address: %v\n", err)
|
||||
continue
|
||||
}
|
||||
// Set a temporary breakpoint at the return address.
|
||||
bp, err := bm.Set(retAddr, "(finish)")
|
||||
if err != nil {
|
||||
fmt.Printf("cannot set finish breakpoint: %v\n", err)
|
||||
continue
|
||||
}
|
||||
// Continue until the breakpoint.
|
||||
for _, b := range bm.All() {
|
||||
bm.Reinsert(b.Addr)
|
||||
}
|
||||
@@ -164,7 +152,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
fmt.Println("debuggee exited")
|
||||
} else {
|
||||
regs, _ := s.GetRegs()
|
||||
fmt.Printf("finished, now at %#x\n", regs.RIP)
|
||||
fmt.Printf("finished, now at %#x\n", regs.GetPC())
|
||||
}
|
||||
|
||||
case "continue", "c":
|
||||
@@ -172,9 +160,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
fmt.Println("debuggee exited")
|
||||
continue
|
||||
}
|
||||
// Loop: continue until a breakpoint fires (condition met) or exit.
|
||||
for {
|
||||
// Re-insert all breakpoints before continuing.
|
||||
for _, bp := range bm.All() {
|
||||
bm.Reinsert(bp.Addr)
|
||||
}
|
||||
@@ -186,7 +172,6 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
fmt.Println("debuggee exited")
|
||||
break
|
||||
}
|
||||
// Check for watchpoint hits.
|
||||
reason, wpAddr := s.StopInfo()
|
||||
if reason == StopWatchpoint {
|
||||
fmt.Printf("watchpoint hit at %#x\n", wpAddr)
|
||||
@@ -201,7 +186,6 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
fmt.Printf("breakpoint hit: %s (func+%#x)\n", name, bp.Addr-codeBase-uint64(funcOffset))
|
||||
break
|
||||
}
|
||||
// Condition not met (or single-step trap) — re-insert and continue.
|
||||
}
|
||||
|
||||
case "break", "b":
|
||||
@@ -209,11 +193,9 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
fmt.Println("usage: break <label|addr|line> [if <reg> <op> <val>]")
|
||||
continue
|
||||
}
|
||||
// Try as a line number first.
|
||||
var addr uint64
|
||||
var label string
|
||||
if lineNum, err := strconv.Atoi(parts[1]); err == nil && lineNum > 0 {
|
||||
// Find the byte offset for this line.
|
||||
off := offsetForLine(lines, lineNum)
|
||||
if off < 0 {
|
||||
fmt.Printf("no instruction at line %d\n", lineNum)
|
||||
@@ -228,7 +210,6 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
fmt.Printf("unknown label, address, or line: %s\n", parts[1])
|
||||
continue
|
||||
}
|
||||
// Parse optional condition: "if <reg> <op> <value>"
|
||||
var cond *Condition
|
||||
if len(parts) >= 6 && parts[2] == "if" {
|
||||
val, err := strconv.ParseUint(parts[5], 0, 64)
|
||||
@@ -282,7 +263,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
|
||||
case "x":
|
||||
regs, _ := s.GetRegs()
|
||||
addr := regs.RIP // default: current PC
|
||||
addr := regs.GetPC()
|
||||
length := 64
|
||||
if len(parts) > 1 {
|
||||
addr, _ = resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
|
||||
@@ -314,7 +295,6 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
fmt.Printf("invalid value: %s\n", arg)
|
||||
continue
|
||||
}
|
||||
// Write as 8-byte word if it looks like a large value, else single byte.
|
||||
if v > 255 {
|
||||
for j := 0; j < 8; j++ {
|
||||
bytes = append(bytes, byte(v>>(8*j)))
|
||||
@@ -364,11 +344,11 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
}
|
||||
}
|
||||
regs, _ := s.GetRegs()
|
||||
fmt.Print(s.DisassembleN(regs.RIP, n))
|
||||
fmt.Print(s.DisassembleN(regs.GetPC(), n))
|
||||
|
||||
case "where":
|
||||
regs, _ := s.GetRegs()
|
||||
funcOff := int(regs.RIP - codeBase - uint64(funcOffset))
|
||||
funcOff := int(regs.GetPC() - codeBase - uint64(funcOffset))
|
||||
line := lineAt(lines, funcOff)
|
||||
label := nearestLabel(labels, funcOff)
|
||||
fmt.Printf(" func+%#x", funcOff)
|
||||
@@ -381,32 +361,32 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
fmt.Println()
|
||||
|
||||
case "help", "h", "?":
|
||||
fmt.Println(` break <label|addr> [if <reg> <op> <val>] set a breakpoint
|
||||
fmt.Printf(` break <label|addr> [if <reg> <op> <val>] set a breakpoint
|
||||
delete <label|addr> remove a breakpoint
|
||||
info break list all breakpoints
|
||||
watch <addr> [r|w] [size] set a hardware watchpoint (write by default)
|
||||
unwatch [<slot>] clear one or all watchpoints
|
||||
step [n], s single-step n instructions
|
||||
next, n step over CALL
|
||||
next, n step over CALL/BL
|
||||
continue, c run until breakpoint or exit
|
||||
disas [n], u disassemble n instructions at PC
|
||||
regs print registers and RFLAGS
|
||||
regs print registers
|
||||
where show source line and nearest label
|
||||
stack show stack near RSP (args + return address)
|
||||
stack show stack near %s (args + return address)
|
||||
x [addr] [len] hex-dump memory
|
||||
w <addr> <val...> write bytes to memory
|
||||
labels, l list function labels
|
||||
help, h, ? this help
|
||||
quit, q kill debuggee and exit`)
|
||||
quit, q kill debuggee and exit`, archSPLabel())
|
||||
|
||||
case "stack":
|
||||
regs, _ := s.GetRegs()
|
||||
// For NOSPLIT frame=0: [RSP] = return address, [RSP+8..] = args.
|
||||
retAddr, _ := s.Peek(regs.RSP)
|
||||
fmt.Printf(" [RSP] return addr = %#x\n", retAddr)
|
||||
sp := regs.GetSP()
|
||||
retAddr, _ := archReturnAddr(s, ®s)
|
||||
fmt.Printf(" [%s] return addr = %#x\n", archSPLabel(), retAddr)
|
||||
if argsSize > 0 {
|
||||
fmt.Printf(" args (%d bytes at RSP+8):\n", argsSize)
|
||||
argBytes, err := s.ReadMemory(regs.RSP+8, argsSize)
|
||||
fmt.Printf(" args (%d bytes at %s+8):\n", argsSize, archSPLabel())
|
||||
argBytes, err := s.ReadMemory(sp+8, argsSize)
|
||||
if err == nil {
|
||||
for i := 0; i < argsSize; i += 8 {
|
||||
var v uint64
|
||||
@@ -420,7 +400,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
|
||||
case "bt", "backtrace":
|
||||
regs, _ := s.GetRegs()
|
||||
funcOff := int(regs.RIP - codeBase - uint64(funcOffset))
|
||||
funcOff := int(regs.GetPC() - codeBase - uint64(funcOffset))
|
||||
line := lineAt(lines, funcOff)
|
||||
label := nearestLabel(labels, funcOff)
|
||||
fmt.Printf(" #0 func+%#x", funcOff)
|
||||
@@ -431,7 +411,7 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
fmt.Printf(" [line %d]", line)
|
||||
}
|
||||
fmt.Println()
|
||||
retAddr, _ := s.Peek(regs.RSP)
|
||||
retAddr, _ := archReturnAddr(s, ®s)
|
||||
fmt.Printf(" #1 return to %#x\n", retAddr)
|
||||
|
||||
case "watch":
|
||||
@@ -499,74 +479,6 @@ func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, ar
|
||||
s.Kill()
|
||||
}
|
||||
|
||||
func printRegs(regs *Regs, codeBase, funcOff uint64) {
|
||||
fmt.Printf(" RIP = %#016x (func+%#x)\n", regs.RIP, regs.RIP-codeBase-funcOff)
|
||||
fmt.Printf(" RSP = %#016x RBP = %#016x\n", regs.RSP, regs.RBP)
|
||||
fmt.Printf(" RAX = %#016x RBX = %#016x\n", regs.RAX, regs.RBX)
|
||||
fmt.Printf(" RCX = %#016x RDX = %#016x\n", regs.RCX, regs.RDX)
|
||||
fmt.Printf(" RSI = %#016x RDI = %#016x\n", regs.RSI, regs.RDI)
|
||||
fmt.Printf(" R8 = %#016x R9 = %#016x\n", regs.R8, regs.R9)
|
||||
fmt.Printf(" R10 = %#016x R11 = %#016x\n", regs.R10, regs.R11)
|
||||
fmt.Printf(" R12 = %#016x R13 = %#016x\n", regs.R12, regs.R13)
|
||||
fmt.Printf(" R14 = %#016x R15 = %#016x\n", regs.R14, regs.R15)
|
||||
fmt.Printf(" RFLAGS = %#x [%s]\n", regs.RFLAGS, decodeRflags(regs.RFLAGS))
|
||||
}
|
||||
|
||||
// printVectorRegs displays the YMM registers.
|
||||
func printVectorRegs(v *VectorRegs) {
|
||||
fmt.Println("\n Vector registers (YMM):")
|
||||
for i := 0; i < 16; i += 2 {
|
||||
fmt.Printf(" YMM%-2d = ", i)
|
||||
printYMM(v.YMM[i][:])
|
||||
fmt.Printf(" YMM%-2d = ", i+1)
|
||||
printYMM(v.YMM[i+1][:])
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func printYMM(b []byte) {
|
||||
// Show as 8 32-bit values.
|
||||
for j := 0; j < 32; j += 4 {
|
||||
v := uint32(b[j]) | uint32(b[j+1])<<8 | uint32(b[j+2])<<16 | uint32(b[j+3])<<24
|
||||
fmt.Printf("%08x ", v)
|
||||
}
|
||||
}
|
||||
|
||||
func decodeRflags(f uint64) string {
|
||||
var flags string
|
||||
if f&1 != 0 {
|
||||
flags += "CF "
|
||||
}
|
||||
if f&(1<<2) != 0 {
|
||||
flags += "PF "
|
||||
}
|
||||
if f&(1<<4) != 0 {
|
||||
flags += "AF "
|
||||
}
|
||||
if f&(1<<6) != 0 {
|
||||
flags += "ZF "
|
||||
}
|
||||
if f&(1<<7) != 0 {
|
||||
flags += "SF "
|
||||
}
|
||||
if f&(1<<8) != 0 {
|
||||
flags += "TF "
|
||||
}
|
||||
if f&(1<<9) != 0 {
|
||||
flags += "IF "
|
||||
}
|
||||
if f&(1<<10) != 0 {
|
||||
flags += "DF "
|
||||
}
|
||||
if f&(1<<11) != 0 {
|
||||
flags += "OF "
|
||||
}
|
||||
if flags == "" {
|
||||
return "none"
|
||||
}
|
||||
return flags[:len(flags)-1] // trim trailing space
|
||||
}
|
||||
|
||||
func hexDump(addr uint64, data []byte) {
|
||||
for i := 0; i < len(data); i += 16 {
|
||||
end := i + 16
|
||||
@@ -594,21 +506,18 @@ func hexDump(addr uint64, data []byte) {
|
||||
}
|
||||
|
||||
func resolveAddr(s string, codeBase, funcOff uint64, labels []Label) (uint64, string) {
|
||||
// Try as a hex address.
|
||||
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
|
||||
v, err := strconv.ParseUint(s, 0, 64)
|
||||
if err == nil {
|
||||
return v, ""
|
||||
}
|
||||
}
|
||||
// Try as func+offset.
|
||||
if strings.HasPrefix(s, "+") {
|
||||
off, err := strconv.ParseUint(s[1:], 0, 64)
|
||||
if err == nil {
|
||||
return codeBase + funcOff + off, fmt.Sprintf("func+%#x", off)
|
||||
}
|
||||
}
|
||||
// Try as a label name.
|
||||
for _, l := range labels {
|
||||
if l.Name == s {
|
||||
return codeBase + funcOff + uint64(l.Offset), l.Name
|
||||
@@ -617,7 +526,6 @@ func resolveAddr(s string, codeBase, funcOff uint64, labels []Label) (uint64, st
|
||||
return 0, ""
|
||||
}
|
||||
|
||||
// lineAt returns the source line for a given function-relative offset.
|
||||
func lineAt(lines []SourceLine, offset int) int {
|
||||
if len(lines) == 0 {
|
||||
return 0
|
||||
@@ -637,8 +545,6 @@ func lineAt(lines []SourceLine, offset int) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// offsetForLine returns the byte offset for a given source line number.
|
||||
// Returns -1 if no instruction is at that line.
|
||||
func offsetForLine(lines []SourceLine, line int) int {
|
||||
for _, le := range lines {
|
||||
if le.Line == line {
|
||||
@@ -648,7 +554,6 @@ func offsetForLine(lines []SourceLine, line int) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// nearestLabel returns the name of the label at or just before the offset.
|
||||
func nearestLabel(labels []Label, offset int) string {
|
||||
best := ""
|
||||
bestOff := -1
|
||||
|
||||
Reference in New Issue
Block a user