feat(debug): port the debugger to FreeBSD
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build freebsd && (amd64 || arm64 || riscv64)
|
||||
|
||||
package debug
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// FreeBSD TRAP_* si_code values (sys/signal.h). A breakpoint (INT3, BRK,
|
||||
// EBREAK) arrives as TRAP_BRKPT on every supported architecture; TRAP_TRACE
|
||||
// is shared by the completed single-step and the hardware watchpoint hit,
|
||||
// so the watchpoint layer disambiguates from the debug registers.
|
||||
const (
|
||||
trapBRKPT = 1 // TRAP_BRKPT
|
||||
trapTRACE = 2 // TRAP_TRACE
|
||||
)
|
||||
|
||||
// StopReason describes why the debuggee stopped.
|
||||
type StopReason int
|
||||
|
||||
const (
|
||||
StopNone StopReason = iota
|
||||
StopBreakpoint // software breakpoint hit
|
||||
StopWatchpoint // hardware watchpoint triggered
|
||||
StopSingleStep // single-step completed
|
||||
StopSignal // stopped by a signal
|
||||
StopExited // process exited
|
||||
)
|
||||
|
||||
// StopInfo returns the reason the debuggee stopped and the faulting address
|
||||
// (for watchpoints, the watched address that was accessed). FreeBSD has no
|
||||
// PTRACE_GETSIGINFO; the stop's signal information comes from PT_LWPINFO,
|
||||
// whose pl_siginfo carries the siginfo the kernel delivered. A ptrace stop
|
||||
// with no signal behind it (a completed single-step, the initial attach)
|
||||
// fills no siginfo at all.
|
||||
func (s *Session) StopInfo() (StopReason, uint64) {
|
||||
if s.exited {
|
||||
return StopExited, 0
|
||||
}
|
||||
var info unix.PtraceLwpInfoStruct
|
||||
if err := unix.PtraceLwpInfo(s.pid, &info); err != nil {
|
||||
return StopNone, 0
|
||||
}
|
||||
// The siginfo layout is the FreeBSD siginfo_t: three leading ints
|
||||
// (signo, errno, code), then the union, 8-byte aligned, whose _fault
|
||||
// member puts the address at byte offset 16. The read is byte-wise
|
||||
// because the blob's alignment is not guaranteed.
|
||||
si := (*[64]byte)(unsafe.Pointer(&info.Siginfo))
|
||||
signo := int32(binary.LittleEndian.Uint32(si[0:4]))
|
||||
code := int32(binary.LittleEndian.Uint32(si[8:12]))
|
||||
switch {
|
||||
case signo == 0:
|
||||
// A pure ptrace stop: single-step completion, attach, or the
|
||||
// events the kernel resolves internally.
|
||||
return StopSingleStep, 0
|
||||
case signo != int32(syscall.SIGTRAP):
|
||||
return StopSignal, uint64(code)
|
||||
case code == trapBRKPT:
|
||||
return StopBreakpoint, 0
|
||||
case code == trapTRACE:
|
||||
addr := binary.LittleEndian.Uint64(si[16:24])
|
||||
return archStopTrace(s, addr)
|
||||
default:
|
||||
return StopSingleStep, 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user