xref: /aosp_15_r20/external/bcc/tools/sofdsnoop.py (revision 387f9dfdfa2baef462e92476d413c7bc2470293e)
1#!/usr/bin/env python
2# @lint-avoid-python-3-compatibility-imports
3#
4# sofdsnoop traces file descriptors passed via socket
5#           For Linux, uses BCC, eBPF. Embedded C.
6#
7# USAGE: sofdsnoop
8#
9# Copyright (c) 2018 Jiri Olsa.
10# Licensed under the Apache License, Version 2.0 (the "License")
11#
12# 30-Jul-2018   Jiri Olsa   Created this.
13
14from __future__ import print_function
15from bcc import ArgString, BPF
16import os
17import argparse
18from datetime import datetime, timedelta
19
20# arguments
21examples = """examples:
22    ./sofdsnoop           # trace passed file descriptors
23    ./sofdsnoop -T        # include timestamps
24    ./sofdsnoop -p 181    # only trace PID 181
25    ./sofdsnoop -t 123    # only trace TID 123
26    ./sofdsnoop -d 10     # trace for 10 seconds only
27    ./sofdsnoop -n main   # only print process names containing "main"
28
29"""
30parser = argparse.ArgumentParser(
31    description="Trace file descriptors passed via socket",
32    formatter_class=argparse.RawDescriptionHelpFormatter,
33    epilog=examples)
34parser.add_argument("-T", "--timestamp", action="store_true",
35    help="include timestamp on output")
36parser.add_argument("-p", "--pid",
37    help="trace this PID only")
38parser.add_argument("-t", "--tid",
39    help="trace this TID only")
40parser.add_argument("-n", "--name",
41    type=ArgString,
42    help="only print process names containing this name")
43parser.add_argument("-d", "--duration",
44    help="total duration of trace in seconds")
45args = parser.parse_args()
46debug = 0
47
48ACTION_SEND=0
49ACTION_RECV=1
50MAX_FD=10
51
52if args.duration:
53    args.duration = timedelta(seconds=int(args.duration))
54
55# define BPF program
56bpf_text = """
57#include <uapi/linux/ptrace.h>
58#include <uapi/linux/limits.h>
59#include <linux/sched.h>
60#include <linux/socket.h>
61#include <net/sock.h>
62
63#define MAX_FD 10
64#define ACTION_SEND   0
65#define ACTION_RECV   1
66
67struct val_t {
68    u64  id;
69    u64  ts;
70    int  action;
71    int  sock_fd;
72    int  fd_cnt;
73    int  fd[MAX_FD];
74    char comm[TASK_COMM_LEN];
75};
76
77BPF_HASH(detach_ptr, u64, struct cmsghdr *);
78BPF_HASH(sock_fd, u64, int);
79BPF_PERF_OUTPUT(events);
80
81static void set_fd(int fd)
82{
83    u64 id = bpf_get_current_pid_tgid();
84
85    sock_fd.update(&id, &fd);
86}
87
88static int get_fd(void)
89{
90    u64 id = bpf_get_current_pid_tgid();
91    int *fd;
92
93    fd = sock_fd.lookup(&id);
94    return fd ? *fd : -1;
95}
96
97static void put_fd(void)
98{
99    u64 id = bpf_get_current_pid_tgid();
100
101    sock_fd.delete(&id);
102}
103
104static int sent_1(struct pt_regs *ctx, struct val_t *val, int num, void *data)
105{
106    val->fd_cnt = min(num, MAX_FD);
107
108    if (bpf_probe_read_kernel(&val->fd[0], MAX_FD * sizeof(int), data))
109        return -1;
110
111    events.perf_submit(ctx, val, sizeof(*val));
112    return 0;
113}
114
115#define SEND_1                                  \
116    if (sent_1(ctx, &val, num, (void *) data))  \
117        return 0;                               \
118                                                \
119    num -= MAX_FD;                              \
120    if (num < 0)                                \
121        return 0;                               \
122                                                \
123    data += MAX_FD;
124
125#define SEND_2   SEND_1 SEND_1
126#define SEND_4   SEND_2 SEND_2
127#define SEND_8   SEND_4 SEND_4
128#define SEND_260 SEND_8 SEND_8 SEND_8 SEND_2
129
130static int send(struct pt_regs *ctx, struct cmsghdr *cmsg, int action)
131{
132    struct val_t val = { 0 };
133    int *data, num, fd;
134    u64 tsp = bpf_ktime_get_ns();
135
136    data = (void *) ((char *) cmsg + sizeof(struct cmsghdr));
137    num  = (cmsg->cmsg_len - sizeof(struct cmsghdr)) / sizeof(int);
138
139    val.id      = bpf_get_current_pid_tgid();
140    val.action  = action;
141    val.sock_fd = get_fd();
142    val.ts      = tsp / 1000;
143
144    if (bpf_get_current_comm(&val.comm, sizeof(val.comm)) != 0)
145        return 0;
146
147    SEND_260
148    return 0;
149}
150
151static bool allow_pid(u64 id)
152{
153    u32 pid = id >> 32; // PID is higher part
154    u32 tid = id;       // Cast and get the lower part
155
156    FILTER
157
158    return 1;
159}
160
161int trace_scm_send_entry(struct pt_regs *ctx, struct socket *sock, struct msghdr *hdr)
162{
163    struct cmsghdr *cmsg = NULL;
164
165    if (!allow_pid(bpf_get_current_pid_tgid()))
166        return 0;
167
168    if (hdr->msg_controllen >= sizeof(struct cmsghdr))
169        cmsg = hdr->msg_control;
170
171    if (!cmsg || (cmsg->cmsg_type != SCM_RIGHTS))
172        return 0;
173
174    return send(ctx, cmsg, ACTION_SEND);
175};
176
177int trace_scm_detach_fds_entry(struct pt_regs *ctx, struct msghdr *hdr)
178{
179    struct cmsghdr *cmsg = NULL;
180    u64 id = bpf_get_current_pid_tgid();
181
182    if (!allow_pid(id))
183        return 0;
184
185    if (hdr->msg_controllen >= sizeof(struct cmsghdr))
186        cmsg = hdr->msg_control;
187
188    if (!cmsg)
189        return 0;
190
191    detach_ptr.update(&id, &cmsg);
192    return 0;
193};
194
195int trace_scm_detach_fds_return(struct pt_regs *ctx)
196{
197    struct cmsghdr **cmsgp;
198    u64 id = bpf_get_current_pid_tgid();
199
200    if (!allow_pid(id))
201        return 0;
202
203    cmsgp = detach_ptr.lookup(&id);
204
205    if (!cmsgp)
206        return 0;
207
208    return send(ctx, *cmsgp, ACTION_RECV);
209}
210
211int syscall__sendmsg(struct pt_regs *ctx, u64 fd, u64 msg, u64 flags)
212{
213    struct pt_regs p;
214
215    if (!allow_pid(bpf_get_current_pid_tgid()))
216        return 0;
217
218    set_fd(fd);
219    return 0;
220}
221
222int trace_sendmsg_return(struct pt_regs *ctx)
223{
224    if (!allow_pid(bpf_get_current_pid_tgid()))
225        return 0;
226
227    put_fd();
228    return 0;
229}
230
231int syscall__recvmsg(struct pt_regs *ctx, u64 fd, u64 msg, u64 flags)
232{
233    struct pt_regs p;
234
235    if (!allow_pid(bpf_get_current_pid_tgid()))
236        return 0;
237
238    fd = fd;
239
240    set_fd(fd);
241    return 0;
242}
243
244int trace_recvmsg_return(struct pt_regs *ctx)
245{
246    if (!allow_pid(bpf_get_current_pid_tgid()))
247        return 0;
248
249    put_fd();
250    return 0;
251}
252
253"""
254
255if args.tid:  # TID trumps PID
256    bpf_text = bpf_text.replace('FILTER',
257        'if (tid != %s) { return 0; }' % args.tid)
258elif args.pid:
259    bpf_text = bpf_text.replace('FILTER',
260        'if (pid != %s) { return 0; }' % args.pid)
261else:
262    bpf_text = bpf_text.replace('FILTER', '')
263
264# initialize BPF
265b = BPF(text=bpf_text)
266
267syscall_fnname = b.get_syscall_fnname("sendmsg")
268if BPF.ksymname(syscall_fnname) != -1:
269    b.attach_kprobe(event=syscall_fnname, fn_name="syscall__sendmsg")
270    b.attach_kretprobe(event=syscall_fnname, fn_name="trace_sendmsg_return")
271
272syscall_fnname = b.get_syscall_fnname("recvmsg")
273if BPF.ksymname(syscall_fnname) != -1:
274    b.attach_kprobe(event=syscall_fnname, fn_name="syscall__recvmsg")
275    b.attach_kretprobe(event=syscall_fnname, fn_name="trace_recvmsg_return")
276
277b.attach_kprobe(event="__scm_send", fn_name="trace_scm_send_entry")
278b.attach_kprobe(event="scm_detach_fds", fn_name="trace_scm_detach_fds_entry")
279b.attach_kretprobe(event="scm_detach_fds", fn_name="trace_scm_detach_fds_return")
280
281initial_ts = 0
282
283# header
284if args.timestamp:
285    print("%-14s" % ("TIME(s)"), end="")
286print("%-6s %-6s %-16s %-25s %-5s %s" %
287      ("ACTION", "TID", "COMM", "SOCKET", "FD", "NAME"))
288
289def get_file(pid, fd):
290    proc = "/proc/%d/fd/%d" % (pid, fd)
291    try:
292        return os.readlink(proc)
293    except OSError as err:
294        return "N/A"
295
296# process event
297def print_event(cpu, data, size):
298    event = b["events"].event(data)
299    tid = event.id & 0xffffffff;
300
301    cnt = min(MAX_FD, event.fd_cnt);
302
303    if args.name and bytes(args.name) not in event.comm:
304        return
305
306    for i in range(0, cnt):
307        global initial_ts
308
309        if not initial_ts:
310            initial_ts = event.ts
311
312        if args.timestamp:
313            delta = event.ts - initial_ts
314            print("%-14.9f" % (float(delta) / 1000000), end="")
315
316        print("%-6s %-6d %-16s " %
317              ("SEND" if event.action == ACTION_SEND else "RECV",
318               tid, event.comm.decode()), end = '')
319
320        sock = "%d:%s" % (event.sock_fd, get_file(tid, event.sock_fd))
321        print("%-25s " % sock, end = '')
322
323        fd = event.fd[i]
324        fd_file = get_file(tid, fd) if event.action == ACTION_SEND else ""
325        print("%-5d %s" % (fd, fd_file))
326
327# loop with callback to print_event
328b["events"].open_perf_buffer(print_event, page_cnt=64)
329start_time = datetime.now()
330while not args.duration or datetime.now() - start_time < args.duration:
331    try:
332        b.perf_buffer_poll(timeout=1000)
333    except KeyboardInterrupt:
334        exit()
335