GSPY(1)

NAME
gspy - forensic goroutine-to-syscall inspector for live Go processes

SYNOPSIS
gspy pid [options]
gspy --version
gspy --help

DESCRIPTION
gspy (goroutine spy) attaches to a running Go process and displays a live map of goroutine IDs to kernel syscalls using eBPF uprobes and tracepoints.

It is designed for security incident response and forensic analysis of Go-based processes. No instrumentation, no recompilation, no process restart, and no modification of the target binary's memory or file hash is required.

Goroutine IDs are extracted from thread-local storage by reading the runtime.g struct via process_vm_readv(2) (never ptrace). Syscalls are intercepted using kernel tracepoints (raw_syscalls/sys_enter and raw_syscalls/sys_exit). Goroutine context switches are tracked via uprobes on runtime.execute.

Events are collected via a BPF ring buffer and displayed in a terminal user interface (TUI) with 1 Hz refresh rate, or emitted as newline-delimited JSON for pipeline processing.

OPTIONS

--top
    Sort by syscall frequency (default).

--latency
    Sort by highest current syscall latency.

--filter mode
    Filter displayed syscalls by category. mode can be one of: all (default), io (read, write, open, etc.), net (socket, connect, accept, etc.), sched (futex, nanosleep, sched_yield, etc.).

--readonly
    Forensic mode. Computes and displays the SHA-256 hash of the target binary on attach. Guarantees zero writes to target process memory. Logs "READONLY MODE" to stderr.

--json
    Emit one JSON object per line per syscall event to stdout. No TUI. Signal-safe and pipeable.

--debug
    Print BPF verifier log and map statistics to stderr on startup.

--version
    Print version information and exit.

--help
    Print usage information and exit.

FORENSIC MODE
When --readonly is specified, gspy operates in forensic mode:

•   Computes and logs the SHA-256 hash of the target binary on attach.

•   Guarantees zero writes to target process memory. gspy only uses process_vm_readv(2), which is a read-only system call.

•   The SHA-256 hash is displayed in the TUI header and included in JSON output.

•   Logs "READONLY MODE: no writes to target process memory" to stderr.

This mode is designed for chain-of-custody scenarios where the target binary's integrity must be preserved and verifiable.

GOROUTINE ID EXTRACTION
gspy extracts goroutine IDs by:

1.  Attaching a uprobe to runtime.execute in the target Go binary.

2.  Reading the goroutine pointer from the RAX register (Go 1.17+ ABIInternal on amd64, where the first argument is passed in RAX).

3.  Using bpf_probe_read_user() to read the goid field from the runtime.g struct at a version-specific offset.

4.  Maintaining a TID → GID map in a BPF hash table.

The goid offset is determined by:

1.  DWARF debug information in the target binary (most accurate).

2.  A hardcoded offset table verified against Go runtime source code for Go 1.17 through 1.23 on amd64.

3.  A fallback default of 152 bytes with a logged warning.

SUPPORTED GO VERSIONS
Go 1.17 through 1.23 on amd64 (verified).

arm64 is experimental — goid offsets are unverified and may be incorrect. Other architectures are unsupported.

KERNEL REQUIREMENTS

•   Linux kernel >= 5.8 (required for BPF ring buffer support).

•   CONFIG_BPF_SYSCALL=y (enabled in all major distributions).

•   CONFIG_DEBUG_INFO_BTF=y recommended for CO-RE portability.

•   perf_event_paranoid <= 2 recommended. If higher, gspy warns but attempts to continue.

CAPABILITIES
gspy requires one of:

•   CAP_BPF + CAP_PERFMON (preferred, Linux 5.8+).

•   CAP_SYS_ADMIN (fallback for older configurations).

To grant capabilities without running as root:

    sudo setcap cap_bpf,cap_perfmon+ep $(which gspy)

EXIT CODES

0   Normal exit, or target process exited cleanly.

1   Fatal error (missing capabilities, invalid PID, BPF load failure).

2   Kernel version too old (< 5.8).

130 Interrupted by SIGINT (Ctrl-C).

KNOWN LIMITATIONS

•   arm64 goid offsets are unverified and may produce incorrect goroutine IDs.

•   Go generics may produce inlined frames that resolve incorrectly.

•   Processes with ASLR and no DWARF info may have partial frame resolution.

•   cgroupv1 namespaces are unsupported.

•   Kernel < 5.8 is not supported; gspy exits with exit code 2.

•   Stripped binaries (no symbol table) display raw hex addresses instead of function names.

EXAMPLES
Basic attach to a running Go process:

    sudo gspy 1234

Forensic readonly mode with network filter:

    sudo gspy 1234 --readonly --filter net

JSON pipeline — find syscalls with latency > 1ms:

    sudo gspy 1234 --json | jq 'select(.latency_us > 1000)'

Sort by latency to find blocked goroutines:

    sudo gspy 1234 --latency

Debug BPF loading issues:

    sudo gspy 1234 --debug

SEE ALSO
bpf(2), process_vm_readv(2), bpftool(8), dlv(1), strace(1), perf(1)

AUTHOR
Mutasem Kharma <mutasem@gspy.dev>

LICENSE
GPL-2.0-only

This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.