// Copyright (c) 2026 Petr BalvĂ­n (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 } }