247 lines
6.2 KiB
Go
247 lines
6.2 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
//go:build linux && amd64
|
|
|
|
package debug
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Label is a named address within the debugged function.
|
|
type Label struct {
|
|
Name string
|
|
Offset int // function-relative offset
|
|
}
|
|
|
|
// 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.
|
|
func REPL(s *Session, bm *Breakpoints, codeBase uint64, funcOffset, funcSize, argsSize int, labels []Label) {
|
|
entryAddr := codeBase + uint64(funcOffset)
|
|
|
|
// Run to the function entry.
|
|
bp, err := bm.Set(entryAddr, "(entry)")
|
|
if err != nil {
|
|
fmt.Printf("warning: cannot set entry breakpoint: %v\n", err)
|
|
} else {
|
|
if err := s.Continue(); err != nil {
|
|
fmt.Printf("warning: continue to entry: %v\n", err)
|
|
}
|
|
regs, _ := s.GetRegs()
|
|
bm.HandleTrap(®s)
|
|
// Remove the temporary entry breakpoint.
|
|
bm.Clear(entryAddr)
|
|
_ = bp
|
|
}
|
|
|
|
fmt.Printf("stopped at function entry: %#x (%d bytes)\n", entryAddr, funcSize)
|
|
fmt.Println("commands: break <label|addr> | step [n] | continue | regs | x <addr> [len] | labels | quit")
|
|
|
|
scanner := bufio.NewScanner(os.Stdin)
|
|
|
|
for {
|
|
fmt.Print("(gasm) ")
|
|
if !scanner.Scan() {
|
|
break
|
|
}
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" {
|
|
continue
|
|
}
|
|
parts := strings.Fields(line)
|
|
cmd := parts[0]
|
|
|
|
switch cmd {
|
|
case "q", "quit":
|
|
s.Kill()
|
|
return
|
|
|
|
case "regs":
|
|
regs, err := s.GetRegs()
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
printRegs(®s, codeBase, uint64(funcOffset))
|
|
|
|
case "step", "s":
|
|
n := 1
|
|
if len(parts) > 1 {
|
|
n, _ = strconv.Atoi(parts[1])
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
if s.Exited() {
|
|
fmt.Println("debuggee exited")
|
|
break
|
|
}
|
|
if err := s.Step(); err != nil {
|
|
fmt.Println(err)
|
|
break
|
|
}
|
|
}
|
|
if !s.Exited() {
|
|
regs, _ := s.GetRegs()
|
|
fmt.Printf("=> %#x (func+%#x)\n", regs.RIP, regs.RIP-codeBase-uint64(funcOffset))
|
|
}
|
|
|
|
case "continue", "c":
|
|
if s.Exited() {
|
|
fmt.Println("debuggee exited")
|
|
continue
|
|
}
|
|
// Re-insert all breakpoints before continuing.
|
|
for _, bp := range bm.All() {
|
|
bm.Reinsert(bp.Addr)
|
|
}
|
|
if err := s.Continue(); err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
regs, _ := s.GetRegs()
|
|
if bp := bm.HandleTrap(®s); bp != nil {
|
|
name := bp.Label
|
|
if name == "" {
|
|
name = fmt.Sprintf("%#x", bp.Addr)
|
|
}
|
|
fmt.Printf("breakpoint hit: %s (func+%#x)\n", name, bp.Addr-codeBase-uint64(funcOffset))
|
|
} else if !s.Exited() {
|
|
fmt.Printf("stopped at %#x\n", regs.RIP)
|
|
}
|
|
|
|
case "break", "b":
|
|
if len(parts) < 2 {
|
|
fmt.Println("usage: break <label|addr>")
|
|
continue
|
|
}
|
|
addr, label := resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
|
|
if addr == 0 {
|
|
fmt.Printf("unknown label or address: %s\n", parts[1])
|
|
continue
|
|
}
|
|
bp, err := bm.Set(addr, label)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
fmt.Printf("breakpoint set: %s at %#x (func+%#x)\n", bp.Label, bp.Addr, bp.Addr-codeBase-uint64(funcOffset))
|
|
|
|
case "delete", "d":
|
|
if len(parts) < 2 {
|
|
fmt.Println("usage: delete <label|addr>")
|
|
continue
|
|
}
|
|
addr, _ := resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
|
|
if addr == 0 {
|
|
fmt.Printf("unknown: %s\n", parts[1])
|
|
continue
|
|
}
|
|
if err := bm.Clear(addr); err != nil {
|
|
fmt.Println(err)
|
|
} else {
|
|
fmt.Println("breakpoint removed")
|
|
}
|
|
|
|
case "x":
|
|
regs, _ := s.GetRegs()
|
|
addr := regs.RIP // default: current PC
|
|
length := 64
|
|
if len(parts) > 1 {
|
|
addr, _ = resolveAddr(parts[1], codeBase, uint64(funcOffset), labels)
|
|
}
|
|
if len(parts) > 2 {
|
|
length, _ = strconv.Atoi(parts[2])
|
|
}
|
|
mem, err := s.ReadMemory(addr, length)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
continue
|
|
}
|
|
hexDump(addr, mem)
|
|
|
|
case "labels", "l":
|
|
sorted := make([]Label, len(labels))
|
|
copy(sorted, labels)
|
|
sort.Slice(sorted, func(i, j int) bool { return sorted[i].Offset < sorted[j].Offset })
|
|
for _, l := range sorted {
|
|
fmt.Printf(" func+%#04x %s\n", l.Offset, l.Name)
|
|
}
|
|
|
|
default:
|
|
fmt.Printf("unknown command: %s\n", cmd)
|
|
}
|
|
}
|
|
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\n", regs.RFLAGS)
|
|
}
|
|
|
|
func hexDump(addr uint64, data []byte) {
|
|
for i := 0; i < len(data); i += 16 {
|
|
end := i + 16
|
|
if end > len(data) {
|
|
end = len(data)
|
|
}
|
|
fmt.Printf(" %#08x:", addr+uint64(i))
|
|
for j := i; j < i+16; j++ {
|
|
if j < end {
|
|
fmt.Printf(" %02x", data[j])
|
|
} else {
|
|
fmt.Print(" ")
|
|
}
|
|
}
|
|
fmt.Print(" ")
|
|
for j := i; j < end; j++ {
|
|
if data[j] >= 0x20 && data[j] < 0x7f {
|
|
fmt.Printf("%c", data[j])
|
|
} else {
|
|
fmt.Print(".")
|
|
}
|
|
}
|
|
fmt.Println()
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
return 0, ""
|
|
}
|