-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmonitor-process.sh
More file actions
executable file
·322 lines (279 loc) · 10.9 KB
/
monitor-process.sh
File metadata and controls
executable file
·322 lines (279 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#!/usr/bin/env bash
#
# monitor-process.sh
#
# Builds a combined bpftrace script from common.bt and the enabled
# tracer modules (execs, files, netcalls, suspicious), then runs a single
# bpftrace process with elevated privileges to monitor a process and all
# its descendants.
#
# Two modes:
# Run mode: Launch a command and trace it (default)
# Attach mode: Attach to an already-running process (-p PID)
#
# In run mode, trace output is written to a log file (not the terminal)
# to avoid interleaving with the process's own output. Use -o/--output
# to set a custom path, otherwise a temporary file is used.
# In attach mode (-p), traces go to stdout by default.
#
# Options can also be set via environment variables or a config file
# sourced with -c/--config (see --help for details).
#
# Stops tracing when the process exits or on Ctrl-C / SIGTERM.
#
# Usage:
# ./monitor-process.sh [OPTIONS] -- <COMMAND> [ARGS...]
# ./monitor-process.sh [OPTIONS] -p <PID>
#
# Options:
# -c, --config FILE Source env vars from FILE before applying
# defaults (CLI options override config)
# -p, --pid PID Attach to an existing process instead of
# launching a new one
# -o, --output FILE Write traces to FILE (default: temp file)
# -f, --filter REGEX Exclude output lines matching REGEX
# (grep -vE; see monitor-claude.conf for an example)
# --no-filter Disable the exclusion filter
# -E, --disable-execs Disable subprocess/exec tracing
# -F, --disable-files Disable file operation tracing
# -N, --disable-netcalls Disable network call tracing
# -S, --disable-suspicious Disable suspicious operation tracing
# -h, --help Show this help message
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# --- Defaults (env vars and config file values take precedence) ---
TRACE_EXECS="${TRACE_EXECS:-1}"
TRACE_FILES="${TRACE_FILES:-1}"
TRACE_NETCALLS="${TRACE_NETCALLS:-1}"
TRACE_SUSPICIOUS="${TRACE_SUSPICIOUS:-1}"
LOG_FILE="${LOG_FILE:-}"
ATTACH_PID="${ATTACH_PID:-}"
FILE_FILTER="${FILE_FILTER:-}"
# --- Usage ---
usage() {
cat >&2 <<EOF
Usage:
$0 [OPTIONS] -- <COMMAND> [ARGS...] (run mode)
$0 [OPTIONS] -p <PID> (attach mode)
Attach bpftrace monitors to a process.
In run mode, the command is launched as the current user and traced.
In attach mode (-p), tracers attach to an already-running process.
This script must NOT be run as root — the traced process should run
unprivileged. Sudo credentials will be requested for bpftrace only.
In run mode, trace output goes to a log file (to avoid interleaving with
the process's own output). To follow traces in real time, open another
terminal and run:
tail -f <log-file>
In attach mode (-p), traces go to stdout by default. Use -o/--output to
redirect to a file instead.
Options:
-c, --config FILE Source env vars from FILE (see below)
-p, --pid PID Attach to an existing process instead of
launching a new one
-o, --output FILE Write traces to FILE (default: temp file)
-f, --filter REGEX Exclude output lines matching REGEX
(grep -vE; see monitor-claude.conf for an example)
--no-filter Disable the exclusion filter
-E, --disable-execs Disable subprocess/exec tracing
-F, --disable-files Disable file operation tracing
-N, --disable-netcalls Disable network call tracing
-S, --disable-suspicious Disable suspicious operation tracing
-h, --help Show this help message
Configuration:
Use -c/--config or set env vars directly. The config file is sourced
as bash and can set any of these variables:
TRACE_EXECS=0
TRACE_NETCALLS=0
FILE_FILTER="/dev/tty|/proc/"
LOG_FILE="/tmp/my-traces.log"
Precedence: defaults < env vars < config file < CLI options.
Process-specific configs can be provided (e.g. monitor-claude.conf).
Example:
$0 -c confs/monitor-claude.conf -- claude --resume
$0 -p 12345
$0 -c my.conf -o traces.log -- my-program run
$0 -EN -- my-program run
$0 --no-filter -- aider
TRACE_EXECS=0 $0 -- codex
EOF
exit 1
}
# --- Refuse to run as root ---
if [[ $(id -u) -eq 0 ]]; then
echo "Error: do not run this script as root or via sudo." >&2
echo "The traced process must run as an unprivileged user." >&2
echo "Sudo will be used internally for bpftrace only." >&2
exit 1
fi
# --- Parse options with getopt ---
OPTS=$(getopt -o 'c:p:o:f:EFNSh' -l 'config:,pid:,output:,filter:,no-filter,disable-execs,disable-files,disable-netcalls,disable-suspicious,help' -n "$0" -- "$@") || usage
eval set -- "$OPTS"
while true; do
case "$1" in
-c|--config) source "$2"; shift 2 ;;
-p|--pid) ATTACH_PID="$2"; shift 2 ;;
-o|--output) LOG_FILE="$2"; shift 2 ;;
-f|--filter) FILE_FILTER="$2"; shift 2 ;;
--no-filter) FILE_FILTER=""; shift ;;
-E|--disable-execs) TRACE_EXECS=0; shift ;;
-F|--disable-files) TRACE_FILES=0; shift ;;
-N|--disable-netcalls) TRACE_NETCALLS=0; shift ;;
-S|--disable-suspicious) TRACE_SUSPICIOUS=0; shift ;;
-h|--help) usage ;;
--) shift; break ;;
esac
done
# --- Validate mode ---
if [[ -n "$ATTACH_PID" && $# -gt 0 ]]; then
echo "Error: -p/--pid and a command are mutually exclusive" >&2
usage
fi
if [[ -z "$ATTACH_PID" && $# -eq 0 ]]; then
echo "Error: specify a command or use -p/--pid to attach" >&2
usage
fi
# --- Build combined bpftrace script ---
# Concatenate common.bt (shared preamble) with enabled tracer
# modules, then append cleanup.bt for process-exit teardown.
# Tracer-specific END handlers (e.g. netcalls flush_agg) are placed
# before cleanup so they fire first.
TRACERS_ENABLED=0
COMBINED_SCRIPT=$(mktemp "/tmp/monitor-combined.XXXXXX.bt")
cat "$SCRIPT_DIR/common.bt" >> "$COMBINED_SCRIPT"
if [[ "$TRACE_EXECS" -eq 1 ]]; then
echo "[monitor-process] Including exec tracer"
cat "$SCRIPT_DIR/trace-execs.bt" >> "$COMBINED_SCRIPT"
((TRACERS_ENABLED++)) || true
fi
if [[ "$TRACE_FILES" -eq 1 ]]; then
echo "[monitor-process] Including file tracer"
cat "$SCRIPT_DIR/trace-files.bt" >> "$COMBINED_SCRIPT"
((TRACERS_ENABLED++)) || true
fi
if [[ "$TRACE_NETCALLS" -eq 1 ]]; then
echo "[monitor-process] Including network tracer"
cat "$SCRIPT_DIR/trace-netcalls.bt" >> "$COMBINED_SCRIPT"
((TRACERS_ENABLED++)) || true
fi
if [[ "$TRACE_SUSPICIOUS" -eq 1 ]]; then
echo "[monitor-process] Including suspicious ops tracer"
cat "$SCRIPT_DIR/trace-suspicious.bt" >> "$COMBINED_SCRIPT"
((TRACERS_ENABLED++)) || true
fi
if [[ "$TRACERS_ENABLED" -eq 0 ]]; then
echo "[monitor-process] All tracers disabled, nothing to do." >&2
rm -f "$COMBINED_SCRIPT"
exit 1
fi
# Append shared cleanup: process-exit shrinking and root-exit handler.
# Must come AFTER all tracer modules so that tracer-specific END handlers
# (e.g. netcalls flush_agg) fire before @watched is cleared.
cat "$SCRIPT_DIR/cleanup.bt" >> "$COMBINED_SCRIPT"
# --- Output setup ---
# In attach mode, default to stdout (no interleaving risk since we
# didn't launch the process). In run mode, default to a temp file.
LOG_TO_STDOUT=0
if [[ -z "$LOG_FILE" ]]; then
if [[ -n "$ATTACH_PID" ]]; then
LOG_TO_STDOUT=1
else
LOG_FILE=$(mktemp "/tmp/monitor-process.XXXXXX.log")
fi
fi
if [[ "$LOG_TO_STDOUT" -eq 0 ]]; then
: > "$LOG_FILE"
fi
# --- Acquire sudo credentials upfront ---
# Prompt once for the password before starting the process, so the
# process's stdout/stdin are not interleaved with a sudo prompt.
echo "[monitor-process] bpftrace requires root — requesting sudo credentials..."
sudo -v
# --- Child PID tracking ---
BPFTRACE_PID=""
TARGET_PID=""
TARGET_OWNED=0
CLEANING_UP=0
# --- Cleanup ---
cleanup() {
# Guard against re-entrant cleanup (e.g. EXIT firing after INT)
[[ "$CLEANING_UP" -eq 1 ]] && return
CLEANING_UP=1
echo ""
echo "[monitor-process] Cleaning up..."
# Kill the bpftrace process (root-owned via sudo) and any filter pipe
if [[ -n "$BPFTRACE_PID" ]]; then
kill "$BPFTRACE_PID" 2>/dev/null || sudo kill "$BPFTRACE_PID" 2>/dev/null || true
fi
# We never kill the target — we only trace, we don't own its lifecycle.
if [[ -n "$TARGET_PID" ]] && kill -0 "$TARGET_PID" 2>/dev/null; then
echo "[monitor-process] Process (PID $TARGET_PID) is still running."
fi
wait 2>/dev/null || true
rm -f "$COMBINED_SCRIPT"
if [[ "$LOG_TO_STDOUT" -eq 0 ]]; then
echo "[monitor-process] Traces saved to: $LOG_FILE"
fi
echo "[monitor-process] Done."
}
trap cleanup EXIT
trap 'trap - EXIT; cleanup; exit 130' INT
trap 'trap - EXIT; cleanup; exit 143' TERM
trap 'trap - EXIT; cleanup; exit 131' QUIT
trap 'trap - EXIT; cleanup; exit 134' HUP
# --- Resolve target PID ---
if [[ -n "$ATTACH_PID" ]]; then
# Attach mode: verify the target PID is alive
TARGET_PID="$ATTACH_PID"
if ! kill -0 "$TARGET_PID" 2>/dev/null; then
echo "[monitor-process] Process $TARGET_PID is not running." >&2
exit 1
fi
echo "[monitor-process] Attaching to PID: $TARGET_PID"
else
# Run mode: launch the command (unprivileged)
TARGET_OWNED=1
echo "[monitor-process] Starting: $*"
"$@" &
TARGET_PID=$!
echo "[monitor-process] PID: $TARGET_PID"
# Give the process a moment to start (bpftrace needs a live PID)
sleep 0.2
if ! kill -0 "$TARGET_PID" 2>/dev/null; then
echo "[monitor-process] Process exited immediately." >&2
exit 1
fi
fi
# --- Run single combined bpftrace (elevated) ---
# bpftrace -B line forces line-buffered output (including to files,
# since v0.25). When a FILE_FILTER is active, pipe through grep.
if [[ "$LOG_TO_STDOUT" -eq 1 ]]; then
exec 3>&1
OUT_REDIR="/dev/fd/3"
else
OUT_REDIR="$LOG_FILE"
fi
echo "[monitor-process] Starting bpftrace with $TRACERS_ENABLED tracer(s)..."
if [[ -n "$FILE_FILTER" ]]; then
sudo bpftrace -B line "$COMBINED_SCRIPT" "$TARGET_PID" 2>&1 \
| grep --line-buffered -vE "$FILE_FILTER" >> "$OUT_REDIR" &
else
sudo bpftrace -B line "$COMBINED_SCRIPT" "$TARGET_PID" >> "$OUT_REDIR" 2>&1 &
fi
BPFTRACE_PID=$!
if [[ "$LOG_TO_STDOUT" -eq 0 ]]; then
echo "[monitor-process] Traces: $LOG_FILE"
echo "[monitor-process] Follow live with: tail -f $LOG_FILE"
fi
echo ""
# --- Wait for the process to exit, then tear down ---
if [[ "$TARGET_OWNED" -eq 1 ]]; then
# We spawned the process — wait(2) works on our own children
wait "$TARGET_PID" 2>/dev/null || true
else
# Attach mode — poll since we can't wait on a foreign process
while kill -0 "$TARGET_PID" 2>/dev/null; do
sleep 1
done
fi
echo ""
echo "[monitor-process] Process (PID $TARGET_PID) exited."