• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# @lint-avoid-python-3-compatibility-imports
3#
4# shmsnoop Trace shm*() syscalls.
5#           For Linux, uses BCC, eBPF. Embedded C.
6#
7# USAGE: shmsnoop [-h] [-T] [-x] [-p PID] [-d DURATION] [-t TID] [-n NAME]
8#
9# Copyright (c) 2018 Jiri Olsa.
10# Licensed under the Apache License, Version 2.0 (the "License")
11#
12# 08-Oct-2018   Jiri Olsa   Created this.
13
14from __future__ import print_function
15from bcc import ArgString, BPF
16import argparse
17import ctypes as ct
18from datetime import datetime, timedelta
19
20# arguments
21examples = """examples:
22    ./shmsnoop           # trace all shm*() syscalls
23    ./shmsnoop -T        # include timestamps
24    ./shmsnoop -p 181    # only trace PID 181
25    ./shmsnoop -t 123    # only trace TID 123
26    ./shmsnoop -d 10     # trace for 10 seconds only
27    ./shmsnoop -n main   # only print process names containing "main"
28"""
29parser = argparse.ArgumentParser(
30    description="Trace shm*() syscalls",
31    formatter_class=argparse.RawDescriptionHelpFormatter,
32    epilog=examples)
33parser.add_argument("-T", "--timestamp", action="store_true",
34    help="include timestamp on output")
35parser.add_argument("-p", "--pid",
36    help="trace this PID only")
37parser.add_argument("-t", "--tid",
38    help="trace this TID only")
39parser.add_argument("-d", "--duration",
40    help="total duration of trace in seconds")
41parser.add_argument("-n", "--name",
42    type=ArgString,
43    help="only print process names containing this name")
44parser.add_argument("--ebpf", action="store_true",
45    help=argparse.SUPPRESS)
46args = parser.parse_args()
47debug = 0
48if args.duration:
49    args.duration = timedelta(seconds=int(args.duration))
50
51# define BPF program
52bpf_text = """
53#include <uapi/linux/ptrace.h>
54#include <uapi/linux/limits.h>
55#include <linux/sched.h>
56
57struct val_t {
58    u64            id;
59    u64            ts;
60    int            sys;
61    unsigned long  key;
62    unsigned long  size;
63    unsigned long  shmflg;
64    unsigned long  shmid;
65    unsigned long  cmd;
66    unsigned long  buf;
67    unsigned long  shmaddr;
68    unsigned long  ret;
69    char           comm[TASK_COMM_LEN];
70};
71
72BPF_HASH(infotmp, u64, struct val_t);
73BPF_PERF_OUTPUT(events);
74
75enum {
76    SYS_SHMGET,
77    SYS_SHMAT,
78    SYS_SHMDT,
79    SYS_SHMCTL,
80};
81
82static int enter(struct val_t *val)
83{
84    u64 id  = bpf_get_current_pid_tgid();
85    u32 pid = id >> 32; // PID is higher part
86    u32 tid = id;       // Cast and get the lower part
87
88    FILTER
89
90    val->id = id;
91    infotmp.update(&id, val);
92    return 0;
93}
94
95int trace_return(struct pt_regs *ctx)
96{
97    u64 id  = bpf_get_current_pid_tgid();
98    u64 tsp = bpf_ktime_get_ns();
99    struct val_t *val;
100
101    val = infotmp.lookup(&id);
102    if (val == 0)
103        return 0;
104
105    if (bpf_get_current_comm(&val->comm, sizeof(val->comm)) != 0)
106        goto out;
107
108    val->ts  = tsp / 1000;
109    val->ret = PT_REGS_RC(ctx);
110    events.perf_submit(ctx, val, sizeof(*val));
111
112out:
113    infotmp.delete(&id);
114    return 0;
115}
116
117int syscall__shmget(struct pt_regs *ctx, u64 key, u64 size, u64 shmflg)
118{
119    struct val_t val = {
120        .sys = SYS_SHMGET,
121    };
122
123    val.key    = key;
124    val.size   = size;
125    val.shmflg = shmflg;
126    return enter(&val);
127};
128
129int syscall__shmat(struct pt_regs *ctx, u64 shmid, u64 shmaddr, u64 shmflg)
130{
131    struct val_t val = {
132        .sys = SYS_SHMAT,
133    };
134
135    val.shmid   = shmid;
136    val.shmaddr = shmaddr;
137    val.shmflg  = shmflg;
138    return enter(&val);
139};
140
141int syscall__shmdt(struct pt_regs *ctx, u64 shmaddr)
142{
143    struct val_t val = {
144        .sys = SYS_SHMDT,
145    };
146
147    val.shmaddr = shmaddr;
148    return enter(&val);
149};
150
151int syscall__shmctl(struct pt_regs *ctx, u64 shmid, u64 cmd, u64 buf)
152{
153    struct val_t val = {
154        .sys = SYS_SHMCTL,
155    };
156
157    val.shmid = shmid;
158    val.cmd   = cmd;
159    val.buf   = buf;
160    return enter(&val);
161};
162
163"""
164if args.tid:  # TID trumps PID
165    bpf_text = bpf_text.replace('FILTER',
166        'if (tid != %s) { return 0; }' % args.tid)
167elif args.pid:
168    bpf_text = bpf_text.replace('FILTER',
169        'if (pid != %s) { return 0; }' % args.pid)
170else:
171    bpf_text = bpf_text.replace('FILTER', '')
172
173if debug or args.ebpf:
174    print(bpf_text)
175    if args.ebpf:
176        exit()
177
178# initialize BPF
179b = BPF(text=bpf_text)
180
181syscall_fnname = b.get_syscall_fnname("shmget")
182if BPF.ksymname(syscall_fnname) != -1:
183    b.attach_kprobe(event=syscall_fnname, fn_name="syscall__shmget")
184    b.attach_kretprobe(event=syscall_fnname, fn_name="trace_return")
185
186syscall_fnname = b.get_syscall_fnname("shmat")
187if BPF.ksymname(syscall_fnname) != -1:
188    b.attach_kprobe(event=syscall_fnname, fn_name="syscall__shmat")
189    b.attach_kretprobe(event=syscall_fnname, fn_name="trace_return")
190
191syscall_fnname = b.get_syscall_fnname("shmdt")
192if BPF.ksymname(syscall_fnname) != -1:
193    b.attach_kprobe(event=syscall_fnname, fn_name="syscall__shmdt")
194    b.attach_kretprobe(event=syscall_fnname, fn_name="trace_return")
195
196syscall_fnname = b.get_syscall_fnname("shmctl")
197if BPF.ksymname(syscall_fnname) != -1:
198    b.attach_kprobe(event=syscall_fnname, fn_name="syscall__shmctl")
199    b.attach_kretprobe(event=syscall_fnname, fn_name="trace_return")
200
201TASK_COMM_LEN = 16    # linux/sched.h
202
203SYS_SHMGET = 0
204SYS_SHMAT  = 1
205SYS_SHMDT  = 2
206SYS_SHMCTL = 3
207
208initial_ts = 0
209
210class Data(ct.Structure):
211    _fields_ = [
212        ("id",      ct.c_ulonglong),
213        ("ts",      ct.c_ulonglong),
214        ("sys",     ct.c_int),
215        ("key",     ct.c_ulong),
216        ("size",    ct.c_ulong),
217        ("shmflg",  ct.c_ulong),
218        ("shmid",   ct.c_ulong),
219        ("cmd",     ct.c_ulong),
220        ("buf",     ct.c_ulong),
221        ("shmaddr", ct.c_ulong),
222        ("ret",     ct.c_ulong),
223        ("comm",    ct.c_char * TASK_COMM_LEN),
224    ]
225
226# header
227if args.timestamp:
228    print("%-14s" % ("TIME(s)"), end="")
229print("%-6s %-16s %6s %16s ARGs" %
230      ("TID" if args.tid else "PID", "COMM", "SYS", "RET"))
231
232def sys_name(sys):
233    switcher = {
234        SYS_SHMGET: "SHMGET",
235        SYS_SHMAT:  "SHMAT",
236        SYS_SHMDT:  "SHMDT",
237        SYS_SHMCTL: "SHMCTL",
238    }
239    return switcher.get(sys, "N/A")
240
241shmget_flags = [
242  { 'name' : 'IPC_CREAT',     'value' :    0o1000 },
243  { 'name' : 'IPC_EXCL',      'value' :    0o2000 },
244  { 'name' : 'SHM_HUGETLB',   'value' :    0o4000 },
245  { 'name' : 'SHM_HUGE_2MB',  'value' :  21 << 26 },
246  { 'name' : 'SHM_HUGE_1GB',  'value' :  30 << 26 },
247  { 'name' : 'SHM_NORESERVE', 'value' :   0o10000 },
248  { 'name' : 'SHM_EXEC',      'value' :  0o100000 }
249]
250
251shmat_flags = [
252  { 'name' : 'SHM_RDONLY', 'value' :  0o10000 },
253  { 'name' : 'SHM_RND',    'value' :  0o20000 },
254  { 'name' : 'SHM_REMAP',  'value' :  0o40000 },
255  { 'name' : 'SHM_EXEC',   'value' : 0o100000 },
256]
257
258def shmflg_str(val, flags):
259    cur = filter(lambda x : x['value'] & val, flags)
260    str = "0x%x" % val
261
262    if (not val):
263        return str
264
265    str += " ("
266    cnt = 0
267    for x in cur:
268        if cnt:
269            str += "|"
270        str +=  x['name']
271        val &= ~x['value']
272        cnt += 1
273
274    if val != 0 or not cnt:
275        if cnt:
276            str += "|"
277        str += "0%o" % val
278
279    str += ")"
280    return str
281
282# process event
283def print_event(cpu, data, size):
284    event = ct.cast(data, ct.POINTER(Data)).contents
285    global initial_ts
286
287    if not initial_ts:
288        initial_ts = event.ts
289
290    if args.name and bytes(args.name) not in event.comm:
291        return
292
293    if args.timestamp:
294        delta = event.ts - initial_ts
295        print("%-14.9f" % (float(delta) / 1000000), end="")
296
297    print("%-6d %-16s %6s %16lx " %
298          (event.id & 0xffffffff if args.tid else event.id >> 32,
299           event.comm.decode(), sys_name(event.sys), event.ret), end = '')
300
301    if event.sys == SYS_SHMGET:
302        print("key: 0x%lx, size: %lu, shmflg: %s" %
303              (event.key, event.size, shmflg_str(event.shmflg, shmget_flags)))
304
305    if event.sys == SYS_SHMAT:
306        print("shmid: 0x%lx, shmaddr: 0x%lx, shmflg: %s" %
307              (event.shmid, event.shmaddr, shmflg_str(event.shmflg, shmat_flags)))
308
309    if event.sys == SYS_SHMDT:
310        print("shmaddr: 0x%lx" % (event.shmaddr))
311
312    if event.sys == SYS_SHMCTL:
313        print("shmid: 0x%lx, cmd: %lu, buf: 0x%x" % (event.shmid, event.cmd, event.buf))
314
315# loop with callback to print_event
316b["events"].open_perf_buffer(print_event, page_cnt=64)
317start_time = datetime.now()
318while not args.duration or datetime.now() - start_time < args.duration:
319    try:
320        b.perf_buffer_poll(timeout=1000)
321    except KeyboardInterrupt:
322        exit()
323