// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause //go:build linux || (freebsd && (amd64 || arm64 || riscv64)) package debug import ( "os" "path/filepath" ) // markDead records the target's failure reason in the handshake directory, // the death notice the debugger's readiness poll reads. The poll must not // wait on the child while it polls: a wait there could consume the SIGSTOP // park the debugger's own waitStopped must receive, hanging the launch, so // the file is the only fast death notice that is safe to read. A debuggee // killed without a notice (a crash, SIGKILL) surfaces through the ordinary // wait after the poll instead. func markDead(tmpDir, reason string) { os.WriteFile(filepath.Join(tmpDir, "dead"), []byte(reason), 0o644) } // tracer abstracts the minimal ptrace operations needed by the breakpoint // manager and the stop-information helpers. The live implementation is // *Session (ptrace_linux_amd64.go); tests supply a mock. type tracer interface { Peek(addr uint64) (uint64, error) Poke(addr uint64, val uint64) error SetRegs(regs *Regs) error Step() error Pid() int } // mockTracer records Peek/Poke/Step calls and provides fake register state. type mockTracer struct { mem map[uint64]byte peeks []uint64 pokes []struct { addr uint64 val uint64 } steps int regs *Regs } func newMockTracer() *mockTracer { return &mockTracer{ mem: make(map[uint64]byte), regs: &Regs{}, } } func (m *mockTracer) Peek(addr uint64) (uint64, error) { m.peeks = append(m.peeks, addr) var val uint64 for i := range uint64(8) { val |= uint64(m.mem[addr+i]) << (i * 8) } return val, nil } func (m *mockTracer) Poke(addr uint64, val uint64) error { m.pokes = append(m.pokes, struct { addr uint64 val uint64 }{addr, val}) for i := range uint64(8) { m.mem[addr+i] = byte(val >> (i * 8)) } return nil } func (m *mockTracer) SetRegs(regs *Regs) error { m.regs = regs return nil } func (m *mockTracer) Step() error { m.steps++ return nil } func (m *mockTracer) Pid() int { return 42 }