Skip to content

Commit

Permalink
tools/opensnoop: Allow to show full path for an open file with relati…
Browse files Browse the repository at this point in the history
…ve path
  • Loading branch information
xingfeng2510 authored and yonghong-song committed Sep 6, 2022
1 parent 3438ec3 commit c110a4d
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 67 deletions.
7 changes: 5 additions & 2 deletions man/man8/opensnoop.8
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
opensnoop \- Trace open() syscalls. Uses Linux eBPF/bcc.
.SH SYNOPSIS
.B opensnoop [\-h] [\-T] [\-U] [\-x] [\-p PID] [\-t TID] [\-u UID]
[\-d DURATION] [\-n NAME] [\-e] [\-f FLAG_FILTER]
[\-d DURATION] [\-n NAME] [\-e] [\-f FLAG_FILTER] [\-F]
[--cgroupmap MAPPATH] [--mntnsmap MAPPATH]
.SH DESCRIPTION
opensnoop traces the open() syscall, showing which processes are attempting
Expand Down Expand Up @@ -56,6 +56,9 @@ Show extended fields.
\-f FLAG
Filter on open() flags, e.g., O_WRONLY.
.TP
\-F
Show full path for an open file with relative path.
.TP
\--cgroupmap MAPPATH
Trace cgroups in this BPF map only (filtered in-kernel).
.TP
Expand Down Expand Up @@ -151,6 +154,6 @@ Linux
.SH STABILITY
Unstable - in development.
.SH AUTHOR
Brendan Gregg
Brendan Gregg, Rocky Xing
.SH SEE ALSO
execsnoop(8), funccount(1)
184 changes: 134 additions & 50 deletions tools/opensnoop.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# opensnoop Trace open() syscalls.
# For Linux, uses BCC, eBPF. Embedded C.
#
# USAGE: opensnoop [-h] [-T] [-x] [-p PID] [-d DURATION] [-t TID] [-n NAME]
# USAGE: opensnoop [-h] [-T] [-U] [-x] [-p PID] [-t TID]
# [--cgroupmap CGROUPMAP] [--mntnsmap MNTNSMAP] [-u UID]
# [-d DURATION] [-n NAME] [-F] [-e] [-f FLAG_FILTER]
#
# Copyright (c) 2015 Brendan Gregg.
# Licensed under the Apache License, Version 2.0 (the "License")
Expand All @@ -14,30 +16,33 @@
# 08-Oct-2016 Dina Goldshtein Support filtering by PID and TID.
# 28-Dec-2018 Tim Douglas Print flags argument, enable filtering
# 06-Jan-2019 Takuma Kume Support filtering by UID
# 21-Aug-2022 Rocky Xing Support showing full path for an open file.

from __future__ import print_function
from bcc import ArgString, BPF
from bcc.containers import filter_by_containers
from bcc.utils import printb
import argparse
from collections import defaultdict
from datetime import datetime, timedelta
import os

# arguments
examples = """examples:
./opensnoop # trace all open() syscalls
./opensnoop -T # include timestamps
./opensnoop -U # include UID
./opensnoop -x # only show failed opens
./opensnoop -p 181 # only trace PID 181
./opensnoop -t 123 # only trace TID 123
./opensnoop -u 1000 # only trace UID 1000
./opensnoop -d 10 # trace for 10 seconds only
./opensnoop -n main # only print process names containing "main"
./opensnoop -e # show extended fields
./opensnoop # trace all open() syscalls
./opensnoop -T # include timestamps
./opensnoop -U # include UID
./opensnoop -x # only show failed opens
./opensnoop -p 181 # only trace PID 181
./opensnoop -t 123 # only trace TID 123
./opensnoop -u 1000 # only trace UID 1000
./opensnoop -d 10 # trace for 10 seconds only
./opensnoop -n main # only print process names containing "main"
./opensnoop -e # show extended fields
./opensnoop -f O_WRONLY -f O_RDWR # only print calls for writing
./opensnoop --cgroupmap mappath # only trace cgroups in this BPF map
./opensnoop --mntnsmap mappath # only trace mount namespaces in the map
./opensnoop -F # show full path for an open file with relative path
./opensnoop --cgroupmap mappath # only trace cgroups in this BPF map
./opensnoop --mntnsmap mappath # only trace mount namespaces in the map
"""
parser = argparse.ArgumentParser(
description="Trace open() syscalls",
Expand Down Expand Up @@ -70,6 +75,8 @@
help="show extended fields")
parser.add_argument("-f", "--flag_filter", action="append",
help="filter on flags argument (e.g., O_WRONLY)")
parser.add_argument("-F", "--full-path", action="store_true",
help="show full path for an open file with relative path")
args = parser.parse_args()
debug = 0
if args.duration:
Expand All @@ -88,6 +95,17 @@
#include <uapi/linux/ptrace.h>
#include <uapi/linux/limits.h>
#include <linux/sched.h>
#ifdef FULLPATH
#include <linux/fs_struct.h>
#include <linux/dcache.h>
#define MAX_ENTRIES 32
enum event_type {
EVENT_ENTRY,
EVENT_END,
};
#endif
struct val_t {
u64 id;
Expand All @@ -102,7 +120,10 @@
u32 uid;
int ret;
char comm[TASK_COMM_LEN];
char fname[NAME_MAX];
#ifdef FULLPATH
enum event_type type;
#endif
char name[NAME_MAX];
int flags; // EXTENDED_STRUCT_MEMBER
};
Expand All @@ -125,15 +146,17 @@
// missed entry
return 0;
}
bpf_probe_read_kernel(&data.comm, sizeof(data.comm), valp->comm);
bpf_probe_read_user(&data.fname, sizeof(data.fname), (void *)valp->fname);
bpf_probe_read_user(&data.name, sizeof(data.name), (void *)valp->fname);
data.id = valp->id;
data.ts = tsp / 1000;
data.uid = bpf_get_current_uid_gid();
data.flags = valp->flags; // EXTENDED_STRUCT_MEMBER
data.ret = PT_REGS_RC(ctx);
events.perf_submit(ctx, &data, sizeof(data));
SUBMIT_DATA
infotmp.delete(&id);
return 0;
Expand Down Expand Up @@ -245,14 +268,14 @@
u64 tsp = bpf_ktime_get_ns();
bpf_probe_read_user(&data.fname, sizeof(data.fname), (void *)filename);
bpf_probe_read_user(&data.name, sizeof(data.name), (void *)filename);
data.id = id;
data.ts = tsp / 1000;
data.uid = bpf_get_current_uid_gid();
data.flags = flags; // EXTENDED_STRUCT_MEMBER
data.ret = ret;
events.perf_submit(ctx, &data, sizeof(data));
SUBMIT_DATA
return 0;
}
Expand All @@ -266,6 +289,9 @@
if b.ksymname(fnname_openat2) == -1:
fnname_openat2 = None

if args.full_path:
bpf_text = "#define FULLPATH\n" + bpf_text

is_support_kfunc = BPF.support_kfunc()
if is_support_kfunc:
bpf_text += bpf_text_kfunc_header_open.replace('FNNAME', fnname_open)
Expand Down Expand Up @@ -312,6 +338,41 @@
if not (args.extended_fields or args.flag_filter):
bpf_text = '\n'.join(x for x in bpf_text.split('\n')
if 'EXTENDED_STRUCT_MEMBER' not in x)

if args.full_path:
bpf_text = bpf_text.replace('SUBMIT_DATA', """
data.type = EVENT_ENTRY;
events.perf_submit(ctx, &data, sizeof(data));
if (data.name[0] != '/') { // relative path
struct task_struct *task;
struct dentry *dentry;
int i;
task = (struct task_struct *)bpf_get_current_task_btf();
dentry = task->fs->pwd.dentry;
for (i = 1; i < MAX_ENTRIES; i++) {
bpf_probe_read_kernel(&data.name, sizeof(data.name), (void *)dentry->d_name.name);
data.type = EVENT_ENTRY;
events.perf_submit(ctx, &data, sizeof(data));
if (dentry == dentry->d_parent) { // root directory
break;
}
dentry = dentry->d_parent;
}
}
data.type = EVENT_END;
events.perf_submit(ctx, &data, sizeof(data));
""")
else:
bpf_text = bpf_text.replace('SUBMIT_DATA', """
events.perf_submit(ctx, &data, sizeof(data));
""")

if debug or args.ebpf:
print(bpf_text)
if args.ebpf:
Expand Down Expand Up @@ -343,43 +404,66 @@
print("%-9s" % ("FLAGS"), end="")
print("PATH")

class EventType(object):
EVENT_ENTRY = 0
EVENT_END = 1

entries = defaultdict(list)

# process event
def print_event(cpu, data, size):
event = b["events"].event(data)
global initial_ts

# split return value into FD and errno columns
if event.ret >= 0:
fd_s = event.ret
err = 0
else:
fd_s = -1
err = - event.ret

if not initial_ts:
initial_ts = event.ts

if args.failed and (event.ret >= 0):
return

if args.name and bytes(args.name) not in event.comm:
return

if args.timestamp:
delta = event.ts - initial_ts
printb(b"%-14.9f" % (float(delta) / 1000000), nl="")

if args.print_uid:
printb(b"%-6d" % event.uid, nl="")

printb(b"%-6d %-16s %4d %3d " %
(event.id & 0xffffffff if args.tid else event.id >> 32,
event.comm, fd_s, err), nl="")

if args.extended_fields:
printb(b"%08o " % event.flags, nl="")

printb(b'%s' % event.fname)
if not args.full_path or event.type == EventType.EVENT_END:
skip = False

# split return value into FD and errno columns
if event.ret >= 0:
fd_s = event.ret
err = 0
else:
fd_s = -1
err = - event.ret

if not initial_ts:
initial_ts = event.ts

if args.failed and (event.ret >= 0):
skip = True

if args.name and bytes(args.name) not in event.comm:
skip = True

if not skip:
if args.timestamp:
delta = event.ts - initial_ts
printb(b"%-14.9f" % (float(delta) / 1000000), nl="")

if args.print_uid:
printb(b"%-6d" % event.uid, nl="")

printb(b"%-6d %-16s %4d %3d " %
(event.id & 0xffffffff if args.tid else event.id >> 32,
event.comm, fd_s, err), nl="")

if args.extended_fields:
printb(b"%08o " % event.flags, nl="")

if not args.full_path:
printb(b"%s" % event.name)
else:
paths = entries[event.id]
paths.reverse()
printb(b"%s" % os.path.join(*paths))

if args.full_path:
try:
del(entries[event.id])
except Exception:
pass
elif event.type == EventType.EVENT_ENTRY:
entries[event.id].append(event.name)

# loop with callback to print_event
b["events"].open_perf_buffer(print_event, page_cnt=64)
Expand Down
32 changes: 17 additions & 15 deletions tools/opensnoop_example.txt
Original file line number Diff line number Diff line change
Expand Up @@ -195,20 +195,20 @@ USAGE message:
# ./opensnoop -h
usage: opensnoop.py [-h] [-T] [-U] [-x] [-p PID] [-t TID]
[--cgroupmap CGROUPMAP] [--mntnsmap MNTNSMAP] [-u UID]
[-d DURATION] [-n NAME] [-e] [-f FLAG_FILTER]
[-d DURATION] [-n NAME] [-e] [-f FLAG_FILTER] [-F]

Trace open() syscalls

optional arguments:
-h, --help show this help message and exit
-T, --timestamp include timestamp on output
-U, --print-uid include UID on output
-U, --print-uid print UID column
-x, --failed only show failed opens
-p PID, --pid PID trace this PID only
-t TID, --tid TID trace this TID only
--cgroupmap CGROUPMAP
trace cgroups in this BPF map only
--mntnsmap MNTNSMAP trace mount namespaces in this BPF map on
--mntnsmap MNTNSMAP trace mount namespaces in this BPF map only
-u UID, --uid UID trace this UID only
-d DURATION, --duration DURATION
total duration of trace in seconds
Expand All @@ -217,18 +217,20 @@ optional arguments:
show extended fields
-f FLAG_FILTER, --flag_filter FLAG_FILTER
filter on flags argument (e.g., O_WRONLY)
-F, --full-path show full path for an open file with relative path

examples:
./opensnoop # trace all open() syscalls
./opensnoop -T # include timestamps
./opensnoop -U # include UID
./opensnoop -x # only show failed opens
./opensnoop -p 181 # only trace PID 181
./opensnoop -t 123 # only trace TID 123
./opensnoop -u 1000 # only trace UID 1000
./opensnoop -d 10 # trace for 10 seconds only
./opensnoop -n main # only print process names containing "main"
./opensnoop -e # show extended fields
./opensnoop # trace all open() syscalls
./opensnoop -T # include timestamps
./opensnoop -U # include UID
./opensnoop -x # only show failed opens
./opensnoop -p 181 # only trace PID 181
./opensnoop -t 123 # only trace TID 123
./opensnoop -u 1000 # only trace UID 1000
./opensnoop -d 10 # trace for 10 seconds only
./opensnoop -n main # only print process names containing "main"
./opensnoop -e # show extended fields
./opensnoop -f O_WRONLY -f O_RDWR # only print calls for writing
./opensnoop --cgroupmap mappath # only trace cgroups in this BPF map
./opensnoop --mntnsmap mappath # only trace mount namespaces in the map
./opensnoop -F # show full path for an open file with relative path
./opensnoop --cgroupmap mappath # only trace cgroups in this BPF map
./opensnoop --mntnsmap mappath # only trace mount namespaces in the map

0 comments on commit c110a4d

Please sign in to comment.