diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | defs.h | 10 | ||||
-rw-r--r-- | pathtrace.c | 390 | ||||
-rw-r--r-- | strace.1 | 16 | ||||
-rw-r--r-- | strace.c | 26 | ||||
-rw-r--r-- | syscall.c | 12 | ||||
-rw-r--r-- | util.c | 7 |
7 files changed, 451 insertions, 12 deletions
diff --git a/Makefile.am b/Makefile.am index ba19a7d..8134b0d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -18,7 +18,7 @@ AM_CPPFLAGS = -I$(srcdir)/$(OS)/$(ARCH) -I$(srcdir)/$(OS) -I$(builddir)/$(OS) strace_SOURCES = strace.c syscall.c count.c util.c desc.c file.c ipc.c \ io.c ioctl.c mem.c net.c process.c bjm.c quota.c \ resource.c signal.c sock.c system.c term.c time.c \ - proc.c scsi.c stream.c block.c + proc.c scsi.c stream.c block.c pathtrace.c noinst_HEADERS = defs.h EXTRA_DIST = $(man_MANS) errnoent.sh signalent.sh syscallent.sh ioctlsort.c \ @@ -401,6 +401,7 @@ struct tcb { #define TCB_SIGTRAPPED 00200 /* Process wanted to block SIGTRAP */ #define TCB_FOLLOWFORK 00400 /* Process should have forks followed */ #define TCB_REPRINT 01000 /* We should reprint this syscall on exit */ +#define TCB_FILTERED 02000 /* This system call has been filtered out */ #ifdef LINUX /* x86 does not need TCB_WAITEXECVE. * It can detect execve's SIGTRAP by looking at eax/rax. @@ -410,7 +411,7 @@ struct tcb { || defined(POWERPC) || defined(IA64) || defined(HPPA) \ || defined(SH) || defined(SH64) || defined(S390) || defined(S390X) \ || defined(ARM) || defined(MIPS) || defined(BFIN) || defined(TILE) -# define TCB_WAITEXECVE 02000 /* ignore SIGTRAP after exceve */ +# define TCB_WAITEXECVE 04000 /* ignore SIGTRAP after exceve */ # endif # define TCB_CLONE_THREAD 010000 /* CLONE_THREAD set in creating syscall */ # define TCB_GROUP_EXITING 020000 /* TCB_EXITING was exit_group, not _exit */ @@ -452,6 +453,7 @@ struct tcb { #define syserror(tcp) ((tcp)->u_error != 0) #define verbose(tcp) (qual_flags[(tcp)->scno] & QUAL_VERBOSE) #define abbrev(tcp) (qual_flags[(tcp)->scno] & QUAL_ABBREV) +#define filtered(tcp) ((tcp)->flags & TCB_FILTERED) struct xlat { int val; @@ -553,6 +555,7 @@ extern void sprint_timespec(char *, struct tcb *, long); #ifdef HAVE_SIGINFO_T extern void printsiginfo(siginfo_t *, int); #endif +extern const char *getfdpath(struct tcb *, int); extern void printfd(struct tcb *, int); extern void printsock(struct tcb *, long, int); extern void print_sock_optmgmt(struct tcb *, long, int); @@ -574,6 +577,9 @@ extern void tprint_open_modes(mode_t); extern const char *sprint_open_modes(mode_t); extern int is_restart_error(struct tcb *); +extern int pathtrace_select(const char *); +extern int pathtrace_match(struct tcb *); + extern int change_syscall(struct tcb *, int); extern int internal_fork(struct tcb *); extern int internal_exec(struct tcb *); @@ -706,3 +712,5 @@ extern long ia32; #endif extern int not_failing_only; +extern int show_fd_path; +extern int tracing_paths; diff --git a/pathtrace.c b/pathtrace.c new file mode 100644 index 0000000..bcab4bb --- /dev/null +++ b/pathtrace.c @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2011, Comtrol Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "defs.h" + +#include <ctype.h> +#include <limits.h> + +#ifdef HAVE_POLL_H +#include <poll.h> +#endif +#ifdef HAVE_SYS_POLL_H +#include <sys/poll.h> +#endif + +#include "syscall.h" + +#define NumElem(a) ((int)((sizeof (a))/((sizeof (a)[0])))) + +#define MAXSELECTED 256 /* max number of "selected" paths */ +static const char *selected[MAXSELECTED]; /* paths selected for tracing */ + +/* + * Return true if specified path matches one that we're tracing. + */ +static int +pathmatch(const char *path) +{ + int i; + + for (i = 0; i < NumElem(selected); ++i) + { + if (selected[i] == NULL) + return 0; + if (!strcmp(path, selected[i])) + return 1; + } + return 0; +} + +/* + * Return true if specified path (in user-space) matches. + */ +static int +upathmatch(struct tcb *tcp, unsigned long upath) +{ + char path[PATH_MAX + 1]; + + return umovestr(tcp, upath, sizeof path, path) == 0 && + pathmatch(path); +} + +/* + * Return true if specified fd maps to a path we're tracing. + */ +static int +fdmatch(struct tcb *tcp, int fd) +{ + const char *path = getfdpath(tcp, fd); + + return path && pathmatch(path); +} + +/* + * Add a path to the set we're tracing. + * Secifying NULL will delete all paths. + */ +static int +storepath(const char *path) +{ + int i; + + if (path == NULL) + { + for (i = 0; i < NumElem(selected); ++i) + if (selected[i]) + { + free((char *) selected[i]); + selected[i] = NULL; + } + return 0; + } + + for (i = 0; i < NumElem(selected); ++i) + if (!selected[i]) + { + selected[i] = path; + return 0; + } + + fprintf(stderr, "Max trace paths exceeded, only using first %d\n", + NumElem(selected)); + return -1; +} + +/* + * Get path associated with fd. + */ +const char *getfdpath(struct tcb *tcp, int fd) +{ +#ifdef LINUX + static char path[PATH_MAX+1]; + char linkpath[64]; + ssize_t n; + + if (fd < 0) + return NULL; + + snprintf(linkpath, sizeof linkpath, "/proc/%d/fd/%d", tcp->pid, fd); + n = readlink(linkpath, path, (sizeof path) - 1); + if (n <= 0) + return NULL; + path[n] = '\0'; + return path; +#else + return NULL; +#endif +} + +/* + * Add a path to the set we're tracing. Also add the canonicalized + * version of the path. Secifying NULL will delete all paths. + */ +int +pathtrace_select(const char *path) +{ + char *rpath; + + if (path == NULL) + return storepath(path); + + if (storepath(path)) + return -1; + + rpath = realpath(path, NULL); + + if (rpath == NULL) + return 0; + + /* if realpath and specified path are same, we're done */ + if (!strcmp(path, rpath)) + { + free(rpath); + return 0; + } + + fprintf(stderr, "Requested path '%s' resolved into '%s'\n", + path, rpath); + return storepath(rpath); +} + +/* + * Return true if syscall accesses a selected path + * (or if no paths have been specified for tracing). + */ +int +pathtrace_match(struct tcb *tcp) +{ + const struct sysent *s; + + if (selected[0] == NULL) + return 1; + + s = &sysent[tcp->scno]; + + if (!(s->sys_flags & (TRACE_FILE | TRACE_DESC))) + return 0; + + /* + * Check for special cases where we need to do something + * other than test arg[0]. + */ + +#ifdef LINUX + + if (s->sys_func == sys_dup2 || + s->sys_func == sys_dup3 || + s->sys_func == sys_sendfile || + s->sys_func == sys_sendfile64 || + !strcmp(s->sys_name, "tee")) + { + /* fd, fd */ + return fdmatch(tcp, tcp->u_arg[0]) || + fdmatch(tcp, tcp->u_arg[1]); + } + + if (s->sys_func == sys_inotify_add_watch || + s->sys_func == sys_faccessat || + s->sys_func == sys_fchmodat || + s->sys_func == sys_futimesat || + s->sys_func == sys_mkdirat || + s->sys_func == sys_unlinkat || + s->sys_func == sys_newfstatat || + s->sys_func == sys_mknodat || + s->sys_func == sys_openat || + s->sys_func == sys_readlinkat || + s->sys_func == sys_utimensat || + s->sys_func == sys_fchownat || + s->sys_func == sys_pipe2) + { + /* fd, path */ + return fdmatch(tcp, tcp->u_arg[0]) || + upathmatch(tcp, tcp->u_arg[1]); + } + + if (s->sys_func == sys_link || + s->sys_func == sys_pivotroot || + s->sys_func == sys_rename || + s->sys_func == sys_symlink || + s->sys_func == sys_mount) + { + /* path, path */ + return upathmatch(tcp, tcp->u_arg[0]) || + upathmatch(tcp, tcp->u_arg[1]); + } + + if (s->sys_func == sys_renameat || + s->sys_func == sys_linkat) + { + /* fd, path, fd, path */ + return fdmatch(tcp, tcp->u_arg[0]) || + fdmatch(tcp, tcp->u_arg[2]) || + upathmatch(tcp, tcp->u_arg[1]) || + upathmatch(tcp, tcp->u_arg[3]); + } + + if (s->sys_func == sys_old_mmap || s->sys_func == sys_mmap) + { + /* x, x, x, x, fd */ + return fdmatch(tcp, tcp->u_arg[4]); + } + + if (s->sys_func == sys_symlinkat) + { + /* path, fd, path */ + return fdmatch(tcp, tcp->u_arg[1]) || + upathmatch(tcp, tcp->u_arg[0]) || + upathmatch(tcp, tcp->u_arg[2]); + } + + if (!strcmp(s->sys_name, "splice")) + { + /* fd, x, fd, x, x */ + return fdmatch(tcp, tcp->u_arg[0]) || + fdmatch(tcp, tcp->u_arg[2]); + } + + if (s->sys_func == sys_epoll_ctl) + { + /* x, x, fd, x */ + return fdmatch(tcp, tcp->u_arg[2]); + } + + if (s->sys_func == sys_select || + s->sys_func == sys_oldselect || + s->sys_func == sys_pselect6) + { + int i, j, nfds; + long *args, oldargs[5]; + unsigned fdsize; + fd_set *fds; + + if (s->sys_func == sys_oldselect) + { + if (umoven(tcp, tcp->u_arg[0], sizeof oldargs, + (char*) oldargs) < 0) + { + fprintf(stderr, "umoven() failed\n"); + return 0; + } + args = oldargs; + } else + args = tcp->u_arg; + + nfds = args[0]; + fdsize = ((((nfds + 7) / 8) + sizeof(long) - 1) + & -sizeof(long)); + fds = malloc(fdsize); + + if (fds == NULL) + { + fprintf(stderr, "out of memory\n"); + return 0; + } + + for (i = 1; i <= 3; ++i) + { + if (args[i] == 0) + continue; + + if (umoven(tcp, args[i], fdsize, (char *) fds) < 0) + { + fprintf(stderr, "umoven() failed\n"); + continue; + } + + for (j = 0; j < nfds; ++j) + if (FD_ISSET(j, fds) && fdmatch(tcp, j)) + { + free(fds); + return 1; + } + } + free(fds); + return 0; + } + + if (s->sys_func == sys_poll || + s->sys_func == sys_ppoll) + { + struct pollfd fds; + unsigned nfds; + unsigned long start, cur, end; + + start = tcp->u_arg[0]; + nfds = tcp->u_arg[1]; + + end = start + sizeof(fds) * nfds; + + if (nfds == 0 || end < start) + return 0; + + for (cur = start; cur < end; cur += sizeof(fds)) + if ((umoven(tcp, cur, sizeof fds, (char *) &fds) == 0) + && fdmatch(tcp, fds.fd)) + return 1; + + return 0; + } + + if (s->sys_func == printargs || + s->sys_func == sys_pipe || + s->sys_func == sys_pipe2 || + s->sys_func == sys_eventfd2 || + s->sys_func == sys_eventfd || + s->sys_func == sys_inotify_init1 || + s->sys_func == sys_timerfd_create || + s->sys_func == sys_timerfd_settime || + s->sys_func == sys_timerfd_gettime || + s->sys_func == sys_epoll_create || + !strcmp(s->sys_name, "fanotify_init")) + { + /* + * These have TRACE_FILE or TRACE_DESCRIPTOR set, but they + * don't have any file descriptor or path args to test. + */ + return 0; + } +#else +#warning "path tracing only using arg[0]" +#endif + + /* + * Our fallback position for calls that haven't already + * been handled is to just check arg[0]. + */ + + if (s->sys_flags & TRACE_FILE) + return upathmatch(tcp, tcp->u_arg[0]); + + if (s->sys_flags & TRACE_DESC) + return fdmatch(tcp, tcp->u_arg[0]); + + return 0; +} @@ -43,7 +43,7 @@ strace \- trace system calls and signals .SH SYNOPSIS .B strace [ -.B \-CdDffhiqrtttTvxx +.B \-CdDffhiqrtttTvxxy ] [ .BI \-a column @@ -60,6 +60,10 @@ strace \- trace system calls and signals ] \&... [ +.BI \-P path +] +\&... +[ .BI \-s strsize ] [ @@ -358,6 +362,9 @@ Print all non-ASCII strings in hexadecimal string format. .B \-xx Print all strings in hexadecimal string format. .TP +.B \-y +Print paths associated with file descriptor arguments. +.TP .BI "\-a " column Align return values in a specific column (default column 40). .TP @@ -549,6 +556,13 @@ options can be used to attach to up to 32 processes in addition to .B \-p option is given). .TP +.BI "\-P " path +Trace only system calls accessing +.I path. +Multiple +.B \-P +options can be used to specify up to 256 paths. +.TP .BI "\-s " strsize Specify the maximum string size to print (the default is 32). Note that filenames are not considered strings and are always printed in @@ -104,6 +104,12 @@ static bool daemonized_tracer = 0; /* Sometimes we want to print only succeeding syscalls. */ int not_failing_only = 0; +/* Show path associated with fd arguments */ +int show_fd_path = 0; + +/* are we filtering traces based on paths? */ +int tracing_paths = 0; + static int exit_code = 0; static int strace_child = 0; @@ -169,9 +175,9 @@ FILE *ofp; int exitval; { fprintf(ofp, "\ -usage: strace [-CdDffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\ +usage: strace [-CdDffhiqrtttTvVxxy] [-a column] [-e expr] ... [-o file]\n\ [-p pid] ... [-s strsize] [-u username] [-E var=val] ...\n\ - [command [arg ...]]\n\ + [-P path] [command [arg ...]]\n\ or: strace -c [-D] [-e expr] ... [-O overhead] [-S sortby] [-E var=val] ...\n\ [command [arg ...]]\n\ -c -- count time, calls, and errors for each syscall and report summary\n\ @@ -184,6 +190,7 @@ usage: strace [-CdDffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\ -T -- print time spent in each syscall, -V -- print version\n\ -v -- verbose mode: print unabbreviated argv, stat, termio[s], etc. args\n\ -x -- print non-ascii strings in hex, -xx -- print all strings in hex\n\ +-y -- print paths associated with file descriptor arguments\n\ -a column -- alignment COLUMN for printing syscall results (default %d)\n\ -e expr -- a qualifying expression: option=[!]all or option=[!]val1[,val2]...\n\ options: trace, abbrev, verbose, raw, signal, read, or write\n\ @@ -196,6 +203,7 @@ usage: strace [-CdDffhiqrtttTvVxx] [-a column] [-e expr] ... [-o file]\n\ -u username -- run command as username handling setuid and/or setgid\n\ -E var=val -- put var=val in the environment for command\n\ -E var -- remove var from the environment for command\n\ +-P path -- trace accesses to path\n\ " /* this is broken, so don't document it -z -- print only succeeding syscalls\n\ */ @@ -792,11 +800,11 @@ main(int argc, char *argv[]) qualify("verbose=all"); qualify("signal=all"); while ((c = getopt(argc, argv, - "+cCdfFhiqrtTvVxz" + "+cCdfFhiqrtTvVxyz" #ifndef USE_PROCFS "D" #endif - "a:e:o:O:p:s:S:u:E:")) != EOF) { + "a:e:o:O:p:s:S:u:E:P:")) != EOF) { switch (c) { case 'c': if (cflag == CFLAG_BOTH) { @@ -850,6 +858,9 @@ main(int argc, char *argv[]) case 'x': xflag++; break; + case 'y': + show_fd_path = 1; + break; case 'v': qualify("abbrev=none"); break; @@ -886,6 +897,13 @@ main(int argc, char *argv[]) tcp->flags |= TCB_ATTACHED; pflag_seen++; break; + case 'P': + tracing_paths = 1; + if (pathtrace_select(optarg)) { + fprintf(stderr,"%s : failed to select path '%s'\n", progname, optarg); + exit(1); + } + break; case 's': max_strlen = atoi(optarg); if (max_strlen < 0) { @@ -2422,8 +2422,7 @@ trace_syscall_exiting(struct tcb *tcp) if (res == 1) internal_syscall(tcp); - if (res == 1 && tcp->scno >= 0 && tcp->scno < nsyscalls && - !(qual_flags[tcp->scno] & QUAL_TRACE)) { + if (res == 1 && filtered(tcp)) { tcp->flags &= ~TCB_INSYSCALL; return 0; } @@ -2688,11 +2687,16 @@ trace_syscall_entering(struct tcb *tcp) } internal_syscall(tcp); - if (tcp->scno >=0 && tcp->scno < nsyscalls && !(qual_flags[tcp->scno] & QUAL_TRACE)) { - tcp->flags |= TCB_INSYSCALL; + + if ((tcp->scno >= 0 && tcp->scno < nsyscalls && + !(qual_flags[tcp->scno] & QUAL_TRACE)) || + (tracing_paths && !pathtrace_match(tcp))) { + tcp->flags |= TCB_INSYSCALL | TCB_FILTERED; return 0; } + tcp->flags &= ~TCB_FILTERED; + if (cflag == CFLAG_ONLY_STATS) { tcp->flags |= TCB_INSYSCALL; gettimeofday(&tcp->etime, NULL); @@ -418,7 +418,12 @@ printnum_int(struct tcb *tcp, long addr, const char *fmt) void printfd(struct tcb *tcp, int fd) { - tprintf("%d", fd); + const char *p; + + if (show_fd_path && (p = getfdpath(tcp, fd))) + tprintf("%d<%s>", fd, p); + else + tprintf("%d", fd); } void |