74 lines
2.4 KiB
Go
74 lines
2.4 KiB
Go
// 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
|
||
|
|
}
|
||
|
|
}
|