// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause //go:build linux package debug import ( "encoding/binary" "syscall" "unsafe" ) // 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 ) // siginfo_t layout (Linux): si_signo, si_errno, si_code, then union. // The si_addr field is at offset 16 on all supported architectures. type siginfoT struct { SiSigno int32 SiErrno int32 SiCode int32 _pad [125]byte } const ( trapBRKPT = 1 // software 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: // si_addr sits at struct offset 16 (12 bytes of signo/errno/code // plus 4 bytes of union alignment). The siginfo buffer is only // 4-byte aligned, so the address is read byte-wise to keep the // load aligned on riscv64 and loong64. What si_addr names is // architecture-specific (the data address on arm64, the // instruction pointer on x86), so the per-architecture // archWatchpointAddr resolves it to the watched address. addr := binary.LittleEndian.Uint64(info._pad[4:12]) return StopWatchpoint, archWatchpointAddr(s, addr) default: return StopSingleStep, 0 } }