GeezerVison is showing github.com — Add support for executing a program and tracing it (#5362) · sevan/bcc@78423e1
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +66Lines changed: 66 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +# Copyright 2025 Rocky Xing | | 2 | +# | | 3 | +# Licensed under the Apache License, Version 2.0 (the "License"); | | 4 | +# you may not use this file except in compliance with the License. | | 5 | +# You may obtain a copy of the License at | | 6 | +# | | 7 | +# http://www.apache.org/licenses/LICENSE-2.0 | | 8 | +# | | 9 | +# Unless required by applicable law or agreed to in writing, software | | 10 | +# distributed under the License is distributed on an "AS IS" BASIS, | | 11 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | | 12 | +# See the License for the specific language governing permissions and | | 13 | +# limitations under the License. | | 14 | + | | 15 | +import os | | 16 | +import signal | | 17 | +import sys | | 18 | + | | 19 | +child_exit = 0 | | 20 | + | | 21 | +pipe_read, pipe_write = os.pipe() | | 22 | + | | 23 | +def _child_signal_handler(signum, frame): | | 24 | + while True: | | 25 | + try: | | 26 | + pid, status = os.waitpid(-1, os.WNOHANG) | | 27 | + if pid == 0: | | 28 | + break | | 29 | + except OSError: | | 30 | + break | | 31 | + | | 32 | + global child_exit | | 33 | + child_exit = 1 | | 34 | + | | 35 | +def run_cmd(args) -> int: | | 36 | + pid = os.fork() | | 37 | + if pid < 0: | | 38 | + print("failed to fork", file=sys.stderr) | | 39 | + sys.exit(1) | | 40 | + elif pid == 0: | | 41 | + try: | | 42 | + os.close(pipe_write) | | 43 | + os.read(pipe_read, 1) | | 44 | + os.execvp(args[0], args) | | 45 | + except OSError as e: | | 46 | + print("failed to exec command: %s: %s" % (' '.join(args), e), file=sys.stderr) | | 47 | + sys.exit(1) | | 48 | + finally: | | 49 | + os.close(pipe_read) | | 50 | + sys.exit(0) | | 51 | + else: | | 52 | + signal.signal(signal.SIGCHLD, _child_signal_handler) | | 53 | + return pid | | 54 | + | | 55 | +def cmd_ready(): | | 56 | + try: | | 57 | + os.close(pipe_read) | | 58 | + os.write(pipe_write, b'x') | | 59 | + except OSError as e: | | 60 | + sys.exit(1) | | 61 | + finally: | | 62 | + os.close(pipe_write) | | 63 | + | | 64 | +def cmd_exited(): | | 65 | + return child_exit | | 66 | + |
Collapse file Copy file name to clipboardExpand all lines: tools/opensnoop.py +25Lines changed: 25 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 19 | 19 | # 06-Jan-2019 Takuma Kume Support filtering by UID | 20 | 20 | # 21-Aug-2022 Rocky Xing Support showing full path for an open file. | 21 | 21 | # 06-Sep-2022 Rocky Xing Support setting size of the perf ring buffer. | | 22 | +# 13-Jul-2025 Rocky Xing Execute a program and trace it's open() syscalls. | 22 | 23 |
| 23 | 24 | from __future__ import print_function | 24 | 25 | from bcc import ArgString, BPF | 25 | 26 | from bcc.containers import filter_by_containers | | 27 | +from bcc.exec import run_cmd, cmd_ready, cmd_exited | 26 | 28 | from bcc.utils import printb | 27 | 29 | import argparse | 28 | 30 | from collections import defaultdict | | 82 | 84 | parser.add_argument("-b", "--buffer-pages", type=int, default=64, | 83 | 85 | help="size of the perf ring buffer " | 84 | 86 | "(must be a power of two number of pages and defaults to 64)") | | 87 | +parser.add_argument('--exec', nargs=argparse.REMAINDER, | | 88 | + help="execute command (as the last parameter, " | | 89 | + "supports multiple parameters, for example: --exec ls -l /tmp") | 85 | 90 | args = parser.parse_args() | 86 | 91 | debug = 0 | | 92 | + | | 93 | +if args.pid and args.exec: | | 94 | + print("ERROR: can only use -p or --exec. Exiting.") | | 95 | + exit() | | 96 | + | | 97 | +if args.exec is not None and len(args.exec) == 0: | | 98 | + print("ERROR: --exec without command. Exiting.") | | 99 | + exit() | | 100 | + | 87 | 101 | if args.duration: | 88 | 102 | args.duration = timedelta(seconds=int(args.duration)) | 89 | 103 | flag_filter_mask = 0 | | 399 | 413 | 'if (pid != %s) { return 0; }' % args.pid) | 400 | 414 | bpf_text = bpf_text.replace('KFUNC_PID_TID_FILTER', | 401 | 415 | 'if (pid != %s) { events.ringbuf_discard(data, 0); return 0; }' % args.pid) | | 416 | +elif args.exec: | | 417 | + child_pid = run_cmd(args.exec) | | 418 | + bpf_text = bpf_text.replace('KPROBE_PID_TID_FILTER', | | 419 | + 'if (pid != %s) { return 0; }' % child_pid) | | 420 | + bpf_text = bpf_text.replace('KFUNC_PID_TID_FILTER', | | 421 | + 'if (pid != %s) { events.ringbuf_discard(data, 0); return 0; }' % child_pid) | 402 | 422 | else: | 403 | 423 | bpf_text = bpf_text.replace('KPROBE_PID_TID_FILTER', '') | 404 | 424 | bpf_text = bpf_text.replace('KFUNC_PID_TID_FILTER', '') | | 514 | 534 | b.attach_kprobe(event=fnname_openat2, fn_name="syscall__trace_entry_openat2") | 515 | 535 | b.attach_kretprobe(event=fnname_openat2, fn_name="trace_return") | 516 | 536 |
| | 537 | +if args.exec: | | 538 | + cmd_ready() | | 539 | + | 517 | 540 | initial_ts = 0 | 518 | 541 |
| 519 | 542 | # header | @@ -604,3 +627,5 @@ def print_event(cpu, data, size): | 604 | 627 | b.ring_buffer_poll() | 605 | 628 | except KeyboardInterrupt: | 606 | 629 | exit() | | 630 | + if args.exec and cmd_exited(): | | 631 | + exit() |
Collapse file Copy file name to clipboardExpand all lines: tools/syscount.py +21Lines changed: 21 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 11 | 11 | # 15-Feb-2017 Sasha Goldshtein Created this. | 12 | 12 | # 16-May-2022 Rocky Xing Added TID filter support. | 13 | 13 | # 26-Jul-2022 Rocky Xing Added syscall filter support. | | 14 | +# 12-Jul-2025 Rocky Xing Execute a program and trace it's syscalls. | 14 | 15 |
| 15 | 16 | from time import sleep, strftime | 16 | 17 | import argparse | 17 | 18 | import errno | 18 | 19 | import itertools | | 20 | +import os | 19 | 21 | import sys | 20 | 22 | import signal | 21 | 23 | from bcc import BPF | | 24 | +from bcc.exec import run_cmd, cmd_ready, cmd_exited | 22 | 25 | from bcc.utils import printb | 23 | 26 | from bcc.syscall import syscall_name, syscalls | 24 | 27 |
| @@ -73,12 +76,23 @@ def handle_errno(errstr): | 73 | 76 | help="trace this syscall only (use option -l to get all recognized syscalls)") | 74 | 77 | parser.add_argument("--ebpf", action="store_true", | 75 | 78 | help=argparse.SUPPRESS) | | 79 | +parser.add_argument('--exec', nargs=argparse.REMAINDER, | | 80 | + help="execute command (as the last parameter, " | | 81 | + "supports multiple parameters, for example: --exec ls -l /tmp") | 76 | 82 | args = parser.parse_args() | 77 | 83 | if args.duration and not args.interval: | 78 | 84 | args.interval = args.duration | 79 | 85 | if not args.interval: | 80 | 86 | args.interval = 99999999 | 81 | 87 |
| | 88 | +if args.pid and args.exec: | | 89 | + print("ERROR: can only use -p or --exec. Exiting.") | | 90 | + exit() | | 91 | + | | 92 | +if args.exec is not None and len(args.exec) == 0: | | 93 | + print("ERROR: --exec without command. Exiting.") | | 94 | + exit() | | 95 | + | 82 | 96 | syscall_nr = -1 | 83 | 97 | if args.syscall is not None: | 84 | 98 | syscall = bytes(args.syscall, 'utf-8') | @@ -211,6 +225,8 @@ def handle_errno(errstr): | 211 | 225 |
| 212 | 226 | if args.pid: | 213 | 227 | text = ("#define FILTER_PID %d\n" % args.pid) + text | | 228 | +elif args.exec: | | 229 | + text = ("#define FILTER_PID %d\n" % run_cmd(args.exec)) + text | 214 | 230 | elif args.tid: | 215 | 231 | text = ("#define FILTER_TID %d\n" % args.tid) + text | 216 | 232 | elif args.ppid: | @@ -231,6 +247,9 @@ def handle_errno(errstr): | 231 | 247 |
| 232 | 248 | bpf = BPF(text=text) | 233 | 249 |
| | 250 | +if args.exec: | | 251 | + cmd_ready() | | 252 | + | 234 | 253 | def print_stats(): | 235 | 254 | if args.latency: | 236 | 255 | print_latency_stats() | @@ -303,6 +322,8 @@ def print_latency_stats(): | 303 | 322 | signal.signal(signal.SIGINT, signal_ignore) | 304 | 323 | if args.duration and seconds >= args.duration: | 305 | 324 | exiting = 1 | | 325 | + if args.exec and cmd_exited(): | | 326 | + exiting = 1 | 306 | 327 |
| 307 | 328 | print_stats() | 308 | 329 |
| |
|