See every exec() call on your Linux system — in real time, with process tree indentation, exit status, and timing.
proc-trace-exec listens to the Linux kernel's proc connector via a netlink socket and prints a line every time any process calls exec(). No eBPF, no ptrace, no kernel module — just a netlink socket and /proc.
- System-wide: every
exec()on the machine, not just your shell's children - Process tree: indented output shows who spawned what
- Exit timing (
-t): exit status + wall-clock runtime for every process - User / CWD / env (
-u,-d,-e): attach context to each event - Subtree filter (
-p PID): watch only descendants of one process - CMD mode:
proc-trace-exec CMD...runs a command and traces only its subtree - Single static binary, zero runtime dependencies
- Linux kernel with
CONFIG_CONNECTOR=yandCONFIG_PROC_EVENTS=y(default on all major distros) - Root or
CAP_NET_ADMIN
chmod +x build.sh
./build.sh
# → binaries in ./dist/
# proc-trace-exec-linux-amd64
# proc-trace-exec-linux-arm64go build -o proc-trace-exec .CGO_ENABLED=0 go build -ldflags="-s -w" -o proc-trace-exec .proc-trace-exec [-deflqQtu] [-o FILE] [-p PID | CMD...]
sudo proc-trace-execsudo proc-trace-exec -t sh -c 'find /etc -name "*.conf" | head -3'32741+ sh -c 'find /etc -name "*.conf" | head -3'
32742+ find /etc -name '*.conf'
32743+ head -3
/etc/host.conf
/etc/nsswitch.conf
/etc/resolv.conf
32743- head exited status=0 time=0.001s
32742- find exited status=141 time=0.003s
32741- sh exited status=0 time=0.006s
sudo proc-trace-exec -p $(pgrep dockerd)sudo proc-trace-exec -u -d -l95100+ <root> /root % /usr/sbin/sshd
95101+ <root> /root % /usr/sbin/sshd
95102+ <rick> /home/rick % /bin/bash
sudo proc-trace-exec -Q -o /var/log/execs.log &sudo proc-trace-exec -f | grep python| Flag | Description |
|---|---|
-d |
Print working directory of each process |
-e |
Print environment variables of each process |
-f |
Flat output — no indentation |
-l |
Print full executable path (via /proc/pid/exe) |
-o FILE |
Write output to FILE instead of stdout |
-p PID |
Only trace descendants of PID |
-q |
Suppress arguments — show executable name only |
-Q |
Suppress error messages (e.g. vanished processes) |
-t |
Show exit status and wall-clock runtime per process |
-u |
Print owning user of each process |
[indent]PID[+/-] [<user>] [cwd % ] argv...
+after PID — exec event (process started a new image)-after PID — exit event (only with-t)- Indentation — 2 spaces per depth level in the process tree
status=N— process exited with code Nsignal=SIGNAME— process was killed by a signal
Linux exposes process lifecycle events via the proc connector — a netlink socket (AF_NETLINK / NETLINK_CONNECTOR, multicast group CN_IDX_PROC). Any process holding CAP_NET_ADMIN can subscribe with PROC_CN_MCAST_LISTEN and receive kernel events for every fork(), exec(), and exit() system-wide.
flowchart LR
subgraph K ["Linux Kernel"]
direction TB
PC["proc connector\nCN_IDX_PROC"]
end
subgraph FS ["/proc filesystem"]
direction TB
CMD["/proc/PID/cmdline"]
STAT["/proc/PID/stat"]
EXE["/proc/PID/exe · cwd · environ"]
end
P(["Any Process"]) -->|"execve()"| PC
PC -->|"PROC_EVENT_EXEC\n(netlink multicast)"| PTE["proc-trace-exec"]
PTE <-->|"read"| FS
PTE --> OUT["Terminal / File"]
sequenceDiagram
participant P as bash (PID 999)
participant K as Linux Kernel
participant PTE as proc-trace-exec
participant FS as /proc
P->>K: execve("/usr/bin/curl", ["curl","https://..."])
K-->>PTE: PROC_EVENT_EXEC pid=1234
PTE->>FS: /proc/1234/cmdline
FS-->>PTE: ["curl", "https://example.com"]
PTE->>FS: /proc/1234/stat
FS-->>PTE: ppid=999 → depth=1
Note over PTE: " 1234+ curl https://example.com"
K-->>PTE: PROC_EVENT_EXIT pid=1234 exit_code=0
PTE->>PTE: lookup pidDB[1234] → elapsed
Note over PTE: " 1234- curl exited status=0 time=0.342s"
On each PROC_EVENT_EXEC:
- Read
/proc/<pid>/cmdlinefor the full argument vector - Read
/proc/<pid>/statto find the parent PID and compute tree depth - Optionally read
/proc/<pid>/exe(full path),/proc/<pid>/cwd,/proc/<pid>/environ,/proc/<pid>/status(uid) - Print the formatted line with depth-based indentation
On PROC_EVENT_EXIT (with -t): compute wall-clock elapsed time using the kernel's timestamp_ns from the event header, then print the exit line and remove the PID from the internal table.
The depth of a process in the tree is computed lazily by walking the /proc/pid/stat parent chain until reaching watchPID (default: PID 1, i.e. all processes). Results are cached in a map[int32]*pidEntry for subsequent events on the same PID.