summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugene Syromyatnikov <evgsyr@gmail.com>2022-07-07 18:10:57 +0200
committerEugene Syromyatnikov <evgsyr@gmail.com>2022-08-11 14:34:17 +0200
commit1521d93b17e911f88935561abda20d82bed169cd (patch)
treeedade5f61f48037012ce0ca907f859937befddd9
parentedf0518768523edc16492029fa9fece9700c022d (diff)
downloadstrace-1521d93b17e911f88935561abda20d82bed169cd.tar.gz
[wip] Add support for specifying path filters
-rw-r--r--doc/strace.1.in104
-rw-r--r--src/defs.h57
-rw-r--r--src/pathtrace.c35
-rw-r--r--src/print_utils.h8
-rw-r--r--src/strace.c95
-rw-r--r--src/util.c110
6 files changed, 384 insertions, 25 deletions
diff --git a/doc/strace.1.in b/doc/strace.1.in
index 262d618a1..fdc7f11ee 100644
--- a/doc/strace.1.in
+++ b/doc/strace.1.in
@@ -51,6 +51,7 @@ strace \- trace system calls and signals
.OP \-s strsize
.OP \-X format
.OM \-P path
+.OM \-H path_filter
.OM \-p pid
.OP \-\-seccomp\-bpf
.if '@ENABLE_SECONTEXT_FALSE@'#' .OP \-\-secontext\fR[=\fIformat\fR]
@@ -73,6 +74,7 @@ strace \- trace system calls and signals
.OP \-S sortby
.OP \-U columns
.OM \-P path
+.OM \-H path_filter
.OM \-p pid
.OP \-\-seccomp\-bpf
.BR "" {
@@ -717,11 +719,107 @@ Trace system calls that returned but strace failed to fetch the error status.
Trace system calls for which strace detached before the return.
.RE
.TP
+.BR "\-H " [ \fIqualifier\fR\fB:\fR ]... \fIpath_filter\fR
+.TQ
+.BR "\-\-path\-filter " [ \fIqualifier\fR\fB:\fR ]... \fIpath_filter\fR
+Add a path tracing filter entry.
+It restricts the printing of the trace only to syscalls that operate
+on file paths and/or descriptors
+(that is, the syscalls that are included in either
+.BR %file ", " %desc ", or " %network
+syscall tracing class)
+and conform to at least one of the supplied path tracing specifications.
+The set of qualifiers at the beginning of the filter specification affect
+the way a certain path tracing filter specification entry is applied:
+.RS
+.TP 19
+.BR path\-str ", " pathstr
+Match the syscalls that use the string provided in
+.I path_filter
+as a path string.
+.TQ
+.BR fd\-deleted
+Match the syscalls that operate on the file descriptor that is associated
+with path provided in
+.I path_filter
+and the path
+.I is
+unlinked from the file descriptor.
+.TQ
+.BR fd\-not\-deleted
+Match the syscalls that operate on the file descriptor that is associated
+with path provided in
+.I path_filter
+and the path
+.I is not
+unlinked from the file descriptor.
+.RE
+.IP
+If neither of
+.BR pathstr ", " fd-deleted ", or " fd-not-deleted
+qualifiers has been provided, all three are assumed.
+.IP
+.I path_filter
+is a byte string in which the following escape sequences are recognised:
+.RS
+.TP 4
+.B \e\e
+Backslash (ASCII code 92).
+.TQ
+.B \e:
+Colon (ASCII code 58).
+.TQ
+.B \ea
+Alert (ASCII code 7).
+.TQ
+.B \eb
+Backspace (ASCII code 8).
+.TQ
+.B \ef
+Form feed (ASCII code 12).
+.TQ
+.B \en
+New line (ASCII code 10).
+.TQ
+.B \er
+Carriage return (ASCII code 13).
+.TQ
+.B \et
+Horizontal tab (ASCII code 9).
+.TQ
+.B \ev
+Vertical tab (ASCII code 11).
+.TQ
+.BR \e0 ", " \e0\fIn\fR ", " \e0\fInn\fR ", or " \e0\fInnn\fR
+Byte with octal value
+.IR nnn .
+The sequence ends after specification of three digits
+or on the first character that is not an octal digit.
+.TQ
+.BR \ex ", " \ex\fIn\fR ", or " \ex\fInn\fR
+Byte with hexadecimal value
+.IR nn .
+The sequence ends after specification of two digits
+or on the first character that is not an hexadecimal digit.
+.RE
+.IP
+It is advisable to enquote the
+.I path_filter
+string in single quotes in the shell invocations
+to avoid the need of double escaping.
+.IP
+Multiple
+.B \-H
+options can be used to specify multiple path tracing filter entries.
+.TP
.BI "\-P " path
.TQ
.BR "\-\-trace\-path" = \fIpath\fR
Trace only system calls accessing
-.IR path .
+.I path
+(equivalent to
+.BR \-H escape( \fIpath\fI ),
+where escape() escapes all non-printable characters and ":").
Multiple
.B \-P
options can be used to specify several paths.
@@ -863,8 +961,8 @@ Suppress messages about process exits
.TQ
.B path-resolution
Suppress messages about resolution of paths provided via the
-.B \-P
-option
+.BR \-P and \-H
+options
.RB (\[dq] "Requested path \[dq]...\[dq] resolved into \[dq]...\[dq]" \[dq]).
.TQ
.B personality
diff --git a/src/defs.h b/src/defs.h
index f90763dd5..e42a00730 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -514,8 +514,21 @@ extern int Tflag_width;
extern bool iflag;
extern bool count_wallclock;
+enum path_trace_flag_bits {
+ PTF_PATH_STR_BIT,
+ PTF_FD_NOT_DELETED_BIT,
+ PTF_FD_DELETED_BIT,
+};
+
+enum path_trace_flags {
+ FLAG(PTF_PATH_STR),
+ FLAG(PTF_FD_NOT_DELETED),
+ FLAG(PTF_FD_DELETED),
+};
+
struct path_set_item {
const char *path;
+ enum path_trace_flags flags;
};
/* are we filtering traces based on paths? */
@@ -776,13 +789,14 @@ extern long getrval2(struct tcb *);
extern const char *signame(const int);
extern const char *sprintsigname(const int);
-extern void pathtrace_select_set(const char *, struct path_set *);
+extern void pathtrace_select_set(const char *, enum path_trace_flags flags,
+ struct path_set *);
extern bool pathtrace_match_set(struct tcb *, struct path_set *);
static inline void
-pathtrace_select(const char *path)
+pathtrace_select(const char *path, enum path_trace_flags flags)
{
- return pathtrace_select_set(path, &global_path_set);
+ return pathtrace_select_set(path, flags, &global_path_set);
}
static inline bool
@@ -846,6 +860,43 @@ str_strip_prefix_len(const char *str, const char *prefix, size_t prefix_len)
# define STR_STRIP_PREFIX(str, prefix) \
str_strip_prefix_len((str), (prefix), sizeof(prefix) - 1)
+/**
+ * Compare two strings of a known maximum size.
+ */
+extern int strnncmp(const char *s1, const char *s2, size_t n1, size_t n2);
+
+/**
+ * Un-escapes a string that is escaped using the following rules:
+ * * A backslash followed by '\', 'a', 'b', 'f', 'n', 'r', 't', 'v',
+ * is translated into '\', '\a', '\b', '\f', '\n', '\r', '\t', '\v'.
+ * * \0, \0n, \0nn, or \0nnn (up to the first character that is not 0-7)
+ * is translated into a byte with octal code 0nnn.
+ * * \x, \xn, or \xnn (up to the first character that is not in 0-9,A-F,a-f)
+ * is translated into a byte with hexadecimal code 0xnn (this deviates
+ * from the C standard that allows unlimited number of digits in the \x
+ * escaping notation).
+ * * A backslash followed by a character from escape_chars is translated
+ * into the character.
+ * * Any other character or end-of-string are considered an error.
+ *
+ * instr and outstr can point at the same memory, as un-escaping only shrinks
+ * the string and doesn't perform any lookback.
+ *
+ * instr parsing terminates after an \0 or reaching instr+size;
+ * \0 is appended to outstr unless there's no room.
+ *
+ * @return Returns one of the following:
+ * * 0 - everything has been been processed.
+ * * positive number NUM - finished early (\0 occurred in instr)
+ * at position NUM.
+ * * negative number NUM - incorrect escaped character at position NUM.
+ * * INT_MIN - size is too big (larger than INT_MAX or wraps around
+ * the instr).
+ */
+extern int string_unescape(const char *instr, char *outstr,
+ const unsigned int size, const char *escape_chars,
+ unsigned int *outsize);
+
/** String is '\0'-terminated. */
# define QUOTE_0_TERMINATED 0x01
/** Do not emit leading and ending '"' characters. */
diff --git a/src/pathtrace.c b/src/pathtrace.c
index f94f2e60d..6836816ce 100644
--- a/src/pathtrace.c
+++ b/src/pathtrace.c
@@ -21,14 +21,19 @@
struct path_set global_path_set;
+enum { PTF_DELETED_MASK = PTF_PATH_STR | PTF_FD_DELETED | PTF_FD_NOT_DELETED };
+
/*
* Return true if specified path matches one that we're tracing.
*/
static bool
-pathmatch(const char *path, struct path_set *set)
+pathmatch(const char *path, struct path_set *set, enum path_trace_flags flags)
{
for (unsigned int i = 0; i < set->num_selected; ++i) {
- if (strcmp(path, set->paths_selected[i].path) == 0)
+ if (strcmp(path, set->paths_selected[i].path))
+ continue;
+
+ if (flags & set->paths_selected[i].flags & PTF_DELETED_MASK)
return true;
}
return false;
@@ -44,7 +49,7 @@ upathmatch(struct tcb *const tcp, const kernel_ulong_t upath,
char path[PATH_MAX + 1];
return umovestr(tcp, upath, sizeof(path), path) > 0 &&
- pathmatch(path, set);
+ pathmatch(path, set, PTF_PATH_STR);
}
/*
@@ -54,9 +59,12 @@ static bool
fdmatch(struct tcb *tcp, int fd, struct path_set *set)
{
char path[PATH_MAX + 1];
- int n = getfdpath(tcp, fd, path, sizeof(path));
+ bool deleted;
+ int n = getfdpath_pid(tcp->pid, fd, path, sizeof(path), &deleted);
- return n >= 0 && pathmatch(path, set);
+ return n >= 0 && pathmatch(path, set,
+ deleted ? PTF_FD_DELETED
+ : PTF_FD_NOT_DELETED);
}
/*
@@ -64,17 +72,19 @@ fdmatch(struct tcb *tcp, int fd, struct path_set *set)
* Specifying NULL will delete all paths.
*/
static void
-storepath(const char *path, struct path_set *set)
+storepath(const char *path, enum path_trace_flags flags, struct path_set *set)
{
- if (pathmatch(path, set))
- return; /* already in table */
+ debug_func_msg("adding '%s' with flags %#x", path, flags);
if (set->num_selected >= set->size)
set->paths_selected =
xgrowarray(set->paths_selected, &set->size,
sizeof(set->paths_selected[0]));
- set->paths_selected[set->num_selected++].path = path;
+ set->paths_selected[set->num_selected].path = path;
+ set->paths_selected[set->num_selected].flags =
+ flags | (!(flags & PTF_DELETED_MASK) ? PTF_DELETED_MASK : 0);
+ set->num_selected++;
}
int
@@ -157,11 +167,12 @@ getfdpath_pid(pid_t pid, int fd, char *buf, unsigned bufsize, bool *deleted)
* version of the path. Specifying NULL will delete all paths.
*/
void
-pathtrace_select_set(const char *path, struct path_set *set)
+pathtrace_select_set(const char *path, enum path_trace_flags flags,
+ struct path_set *set)
{
char *rpath;
- storepath(path, set);
+ storepath(path, flags, set);
rpath = realpath(path, NULL);
@@ -189,7 +200,7 @@ pathtrace_select_set(const char *path, struct path_set *set)
free(path_quoted);
free(rpath_quoted);
}
- storepath(rpath, set);
+ storepath(rpath, flags, set);
}
static bool
diff --git a/src/print_utils.h b/src/print_utils.h
index 8b899a3b2..f3424373f 100644
--- a/src/print_utils.h
+++ b/src/print_utils.h
@@ -23,6 +23,14 @@ is_print(uint8_t c)
return (c >= ' ') && (c < 0x7f);
}
+static inline bool
+is_hex(uint8_t c)
+{
+ return ((c >= '0') && (c < '9')) ||
+ ((c >= 'A') && (c <= 'F')) ||
+ ((c >= 'a') && (c <= 'f'));
+}
+
/* Character printing functions */
/** @param unabbrev Whether to always print \ooo instead of \[[o]o]o. */
diff --git a/src/strace.c b/src/strace.c
index 3f5a00a0a..0c295d6b2 100644
--- a/src/strace.c
+++ b/src/strace.c
@@ -280,11 +280,13 @@ usage(void)
printf("\
Usage: strace [-ACdffhi" K_OPT "qqrtttTvVwxxyyzZ] [-I N] [-b execve] [-e EXPR]...\n\
[-a COLUMN] [-o FILE] [-s STRSIZE] [-X FORMAT] [-O OVERHEAD]\n\
- [-S SORTBY] [-P PATH]... [-p PID]... [-U COLUMNS] [--seccomp-bpf]\n"\
+ [-S SORTBY] [-P PATH]... [-H PATH_FILTER]... [-p PID]...\n\
+ [-U COLUMNS] [--seccomp-bpf]\n"\
SECONTEXT_OPT "\
{ -p PID | [-DDD] [-E VAR=VAL]... [-u USERNAME] PROG [ARGS] }\n\
or: strace -c[dfwzZ] [-I N] [-b execve] [-e EXPR]... [-O OVERHEAD]\n\
- [-S SORTBY] [-P PATH]... [-p PID]... [-U COLUMNS] [--seccomp-bpf]\n\
+ [-S SORTBY] [-P PATH]... [-H PATH_FILTER] [-p PID]...\n\
+ [-U COLUMNS] [--seccomp-bpf]\n\
{ -p PID | [-DDD] [-E VAR=VAL]... [-u USERNAME] PROG [ARGS] }\n\
\n\
General:\n\
@@ -335,6 +337,13 @@ Filtering:\n\
-e status=SET, --status=SET\n\
print only system calls with the return statuses in SET\n\
statuses: successful, failed, unfinished, unavailable, detached\n\
+ -H [QUALIFIER:]...PATH_STR, --path-filter=[QUALIFIER:]...PATH_STR\n\
+ specify a path filter entry\n\
+ qualifiers: path-str, pathstr (PATH_STR is used as a string),\n\
+ fd-deleted (fd is unlinked from PATH_STR),\n\
+ fd-not-deleted (fd that is linked to PATH_STR)\n\
+ path_str: string with \\\\. \\:, \\a, \\b, \\f, \\n, \\r, \\t, \\v, \\0nnn, \\xnn\n\
+ escape sequences.\n\
-P PATH, --trace-path=PATH\n\
trace accesses to PATH\n\
-z, --successful-only\n\
@@ -2137,7 +2146,8 @@ struct pathtrace {
};
static void
-add_path_trace(struct pathtrace *pt, const char *path)
+add_path_trace(struct pathtrace *pt, const char *path,
+ enum path_trace_flags flags)
{
if (pt->count >= pt->size) {
pt->paths = xgrowarray(pt->paths, &pt->size,
@@ -2145,9 +2155,74 @@ add_path_trace(struct pathtrace *pt, const char *path)
}
pt->paths[pt->count].path = path;
+ pt->paths[pt->count].flags = flags;
pt->count++;
}
+/* Checks if a string is equal to some expected string literal */
+#define CHECK_STR(str_, chk_, sz_) \
+ (!strnncmp((str_), (chk_), (sz_), sizeof(chk_) - 1))
+
+static void
+parse_path_filter_arg(struct pathtrace *pt, char *optarg)
+{
+ char *arg = optarg;
+ enum path_trace_flags flags = 0;
+
+ while (true) {
+ char *search_arg = arg;
+ char *pos;
+ while ((pos = strchr(search_arg, ':'))) {
+ if ((pos > search_arg) && (pos[-1] == '\\')) {
+ search_arg = pos + 1;
+ continue;
+ }
+ break;
+ }
+
+ if (!pos)
+ break;
+
+ pos += 1;
+
+ if (CHECK_STR(arg, "pathstr:", pos - arg)) {
+ flags |= PTF_PATH_STR;
+ } else if (CHECK_STR(arg, "path-str:", pos - arg)) {
+ flags |= PTF_PATH_STR;
+ } else if (CHECK_STR(arg, "fd-deleted:", pos - arg)) {
+ flags |= PTF_FD_DELETED;
+ } else if (CHECK_STR(arg, "fd-not-deleted:", pos - arg)) {
+ flags |= PTF_FD_NOT_DELETED;
+ } else {
+ error_msg_and_die("invalid path trace filter qualifier:"
+ " '%*s'", (int) (pos - arg), arg);
+ }
+
+ arg = pos;
+ }
+
+ unsigned int argsz;
+ size_t arglen = strlen(arg);
+ int ret = string_unescape(arg, arg, arglen, ":", &argsz);
+
+ if (ret == INT_MIN) {
+ error_msg_and_die("path trace filter argument is too big"
+ " (size is %zu)", arglen);
+ } else if (ret < 0) {
+ error_msg_and_die("invalid escaping: \\%c at position %d",
+ arg[-ret], -ret);
+ }
+
+ arglen = strlen(arg);
+ if (arglen < argsz) {
+ error_msg_and_die("embedded '\\0' in the path trace filter"
+ " argument (length %zu < size %u)",
+ arglen, argsz);
+ }
+
+ add_path_trace(pt, arg, flags);
+}
+
/*
* Initialization part of main() was eating much stack (~0.5k),
* which was unused after init.
@@ -2234,7 +2309,7 @@ init(int argc, char *argv[])
#endif
static const char optstring[] =
- "+a:Ab:cCdDe:E:fFhiI:kno:O:p:P:qrs:S:tTu:U:vVwxX:yYzZ";
+ "+a:Ab:cCdDe:E:fFhH:iI:kno:O:p:P:qrs:S:tTu:U:vVwxX:yYzZ";
enum {
GETOPT_SECCOMP = 0x100,
@@ -2277,6 +2352,7 @@ init(int argc, char *argv[])
{ "output-separately", no_argument, 0,
GETOPT_OUTPUT_SEPARATELY },
{ "help", no_argument, 0, 'h' },
+ { "path-filter", required_argument, 0, 'H' },
{ "instruction-pointer", no_argument, 0, 'i' },
{ "interruptible", required_argument, 0, 'I' },
{ "stack-traces", no_argument, 0, 'k' },
@@ -2404,6 +2480,9 @@ init(int argc, char *argv[])
case 'h':
usage();
break;
+ case 'H':
+ parse_path_filter_arg(&pathtrace, optarg);
+ break;
case 'i':
iflag = 1;
break;
@@ -2435,7 +2514,7 @@ init(int argc, char *argv[])
process_opt_p_list(optarg);
break;
case 'P':
- add_path_trace(&pathtrace, optarg);
+ add_path_trace(&pathtrace, optarg, 0);
break;
case 'q':
qflag_short++;
@@ -2768,8 +2847,10 @@ init(int argc, char *argv[])
"take effect. "
"See status qualifier for more complex filters.");
- for (size_t cnt = 0; cnt < pathtrace.count; ++cnt)
- pathtrace_select(pathtrace.paths[cnt].path);
+ for (size_t cnt = 0; cnt < pathtrace.count; ++cnt) {
+ pathtrace_select(pathtrace.paths[cnt].path,
+ pathtrace.paths[cnt].flags);
+ }
free(pathtrace.paths);
acolumn_spaces = xmalloc(acolumn + 1);
diff --git a/src/util.c b/src/util.c
index c4c4bc0ef..ee54be574 100644
--- a/src/util.c
+++ b/src/util.c
@@ -838,6 +838,116 @@ proc_status_get_id_list(int proc_pid, int *id_buf, size_t id_buf_size,
return n;
}
+int
+strnncmp(const char *s1, const char *s2, size_t n1, size_t n2)
+{
+ size_t n = MIN(n1, n2);
+
+ int ret = strncmp(s1, s2, n);
+ if (ret)
+ return ret;
+
+ if ((n2 == n1) || (n2 < n1 && !s1[n2]) || (n2 > n1 && !s2[n1]))
+ return 0;
+
+ return n1 < n2 ? -256 : 256;
+}
+
+int
+string_unescape(const char *instr, char *outstr, const unsigned int size,
+ const char *escape_chars, unsigned int *outsize)
+{
+ const char *inpos = instr;
+ char *outpos = outstr;
+ enum {
+ NORMAL,
+ ESC,
+ ESC_OCT,
+ ESC_HEX,
+ } state = NORMAL;
+ size_t cnt = 0;
+ size_t val = 0;
+
+ if ((size > INT_MAX) || ((uintptr_t) instr + size < (uintptr_t) instr))
+ return INT_MIN;
+
+ while (inpos < instr + size) {
+ switch (state) {
+ case NORMAL:
+ if (!*inpos)
+ goto out;
+ if (*inpos == '\\') {
+ state = ESC;
+ break;
+ }
+
+ *outpos++ = *inpos;
+ break;
+
+ case ESC:
+ switch (*inpos) {
+ case '\\': *outpos++ = '\\'; break;
+ case 'a': *outpos++ = '\a'; break;
+ case 'b': *outpos++ = '\b'; break;
+ case 'f': *outpos++ = '\f'; break;
+ case 'n': *outpos++ = '\n'; break;
+ case 'r': *outpos++ = '\r'; break;
+ case 't': *outpos++ = '\t'; break;
+ case 'v': *outpos++ = '\v'; break;
+ case '0': state = ESC_OCT; cnt = val = 0; break;
+ case 'x': state = ESC_HEX; cnt = val = 0; break;
+ default:
+ if (strchr(escape_chars, *inpos))
+ *outpos++ = *inpos;
+ else
+ return -(inpos - instr);
+ }
+ break;
+
+ case ESC_OCT:
+ if (cnt >= 3 || *inpos < '0' || *inpos > '7') {
+ *outpos++ = val;
+ state = NORMAL;
+ inpos -= 1;
+ break;
+ }
+ val = val * 8 + (*inpos - '0');
+ cnt++;
+ break;
+
+ case ESC_HEX:
+ if (cnt >= 2 || !is_hex(*inpos))
+ state = NORMAL;
+ else if (*inpos >= '0' && *inpos <= '9')
+ val = val * 16 + (*inpos - '0');
+ else if (*inpos >= 'A' && *inpos <= 'F')
+ val = val * 16 + (*inpos + 10 - 'A');
+ else if (*inpos >= 'a' && *inpos <= 'f')
+ val = val * 16 + (*inpos + 10 - 'a');
+ else
+ state = NORMAL;
+
+ if (state == NORMAL) {
+ *outpos++ = val;
+ inpos -= 1;
+ } else {
+ cnt++;
+ }
+
+ break;
+ }
+
+ inpos += 1;
+ }
+out:
+ if (outpos - outstr < size)
+ *outpos = '\0';
+ if (outsize)
+ *outsize = outpos - outstr;
+
+ return inpos - instr < size ? inpos - instr : 0;
+}
+
/*
* Quote string `instr' of length `size'
* Write up to (3 + `size' * 4) bytes to `outstr' buffer.