118 lines
2.6 KiB
Go
118 lines
2.6 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 (
|
||
|
|
"fmt"
|
||
|
|
"syscall"
|
||
|
|
"unsafe"
|
||
|
|
)
|
||
|
|
|
||
|
|
// StopReason describes why the debuggee stopped.
|
||
|
|
type StopReason int
|
||
|
|
|
||
|
|
const (
|
||
|
|
StopNone StopReason = iota
|
||
|
|
StopBreakpoint // INT3 breakpoint hit
|
||
|
|
StopWatchpoint // hardware watchpoint triggered
|
||
|
|
StopSingleStep // single-step completed
|
||
|
|
StopSignal // stopped by a signal
|
||
|
|
StopExited // process exited
|
||
|
|
)
|
||
|
|
|
||
|
|
// siginfo_t layout (Linux amd64): si_signo, si_errno, si_code, then union.
|
||
|
|
type siginfoT struct {
|
||
|
|
SiSigno int32
|
||
|
|
SiErrno int32
|
||
|
|
SiCode int32
|
||
|
|
_pad [125]byte
|
||
|
|
}
|
||
|
|
|
||
|
|
const (
|
||
|
|
trapBRKPT = 1 // INT3 breakpoint
|
||
|
|
trapHWBRKPT = 4 // hardware watchpoint
|
||
|
|
)
|
||
|
|
|
||
|
|
// StopInfo returns the reason the debuggee stopped and the faulting address
|
||
|
|
// (for watchpoints, the watched address that was accessed).
|
||
|
|
func (s *Session) StopInfo() (StopReason, uint64) {
|
||
|
|
if s.exited {
|
||
|
|
return StopExited, 0
|
||
|
|
}
|
||
|
|
var info siginfoT
|
||
|
|
_, _, errno := syscall.Syscall6(
|
||
|
|
syscall.SYS_PTRACE,
|
||
|
|
uintptr(syscall.PTRACE_GETSIGINFO),
|
||
|
|
uintptr(s.pid),
|
||
|
|
0,
|
||
|
|
uintptr(unsafe.Pointer(&info)),
|
||
|
|
0, 0,
|
||
|
|
)
|
||
|
|
if errno != 0 {
|
||
|
|
return StopNone, 0
|
||
|
|
}
|
||
|
|
if info.SiSigno != int32(syscall.SIGTRAP) {
|
||
|
|
return StopSignal, uint64(info.SiCode)
|
||
|
|
}
|
||
|
|
switch info.SiCode {
|
||
|
|
case trapBRKPT:
|
||
|
|
return StopBreakpoint, 0
|
||
|
|
case trapHWBRKPT:
|
||
|
|
// The faulting address is in si_addr (offset 16 in siginfo_t on amd64).
|
||
|
|
addr := *(*uint64)(unsafe.Pointer(uintptr(unsafe.Pointer(&info)) + 16))
|
||
|
|
return StopWatchpoint, addr
|
||
|
|
default:
|
||
|
|
return StopSingleStep, 0
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// SetReg modifies a register value in the debuggee.
|
||
|
|
func (s *Session) SetReg(name string, value uint64) error {
|
||
|
|
regs, err := s.GetRegs()
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
switch name {
|
||
|
|
case "rax", "eax", "ax", "al":
|
||
|
|
regs.RAX = value
|
||
|
|
case "rbx", "ebx", "bx", "bl":
|
||
|
|
regs.RBX = value
|
||
|
|
case "rcx", "ecx", "cx", "cl":
|
||
|
|
regs.RCX = value
|
||
|
|
case "rdx", "edx", "dx", "dl":
|
||
|
|
regs.RDX = value
|
||
|
|
case "rsi", "esi", "si":
|
||
|
|
regs.RSI = value
|
||
|
|
case "rdi", "edi", "di":
|
||
|
|
regs.RDI = value
|
||
|
|
case "rbp", "ebp", "bp":
|
||
|
|
regs.RBP = value
|
||
|
|
case "rsp", "esp", "sp":
|
||
|
|
regs.RSP = value
|
||
|
|
case "r8":
|
||
|
|
regs.R8 = value
|
||
|
|
case "r9":
|
||
|
|
regs.R9 = value
|
||
|
|
case "r10":
|
||
|
|
regs.R10 = value
|
||
|
|
case "r11":
|
||
|
|
regs.R11 = value
|
||
|
|
case "r12":
|
||
|
|
regs.R12 = value
|
||
|
|
case "r13":
|
||
|
|
regs.R13 = value
|
||
|
|
case "r14":
|
||
|
|
regs.R14 = value
|
||
|
|
case "r15":
|
||
|
|
regs.R15 = value
|
||
|
|
case "rip", "eip":
|
||
|
|
regs.RIP = value
|
||
|
|
default:
|
||
|
|
return fmt.Errorf("debug: unknown register %q", name)
|
||
|
|
}
|
||
|
|
return s.SetRegs(®s)
|
||
|
|
}
|