aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/truss/setup.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/truss/setup.c')
-rw-r--r--usr.bin/truss/setup.c543
1 files changed, 455 insertions, 88 deletions
diff --git a/usr.bin/truss/setup.c b/usr.bin/truss/setup.c
index d6e430012e253..4452f9f47c7f3 100644
--- a/usr.bin/truss/setup.c
+++ b/usr.bin/truss/setup.c
@@ -37,37 +37,39 @@ __FBSDID("$FreeBSD$");
* I'm afraid.
*/
-#include <sys/param.h>
-#include <sys/types.h>
#include <sys/ptrace.h>
+#include <sys/sysctl.h>
#include <sys/wait.h>
+#include <assert.h>
#include <err.h>
#include <errno.h>
-#include <fcntl.h>
#include <signal.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
-#include <machine/reg.h>
-
#include "truss.h"
+#include "syscall.h"
#include "extern.h"
+SET_DECLARE(procabi, struct procabi);
+
static sig_atomic_t detaching;
+static void new_proc(struct trussinfo *, pid_t);
+
/*
* setup_and_wait() is called to start a process. All it really does
- * is fork(), set itself up to stop on exec or exit, and then exec
- * the given command. At that point, the child process stops, and
- * the parent can wake up and deal with it.
+ * is fork(), enable tracing in the child, and then exec the given
+ * command. At that point, the child process stops, and the parent
+ * can wake up and deal with it.
*/
-
-int
-setup_and_wait(char *command[])
+void
+setup_and_wait(struct trussinfo *info, char *command[])
{
pid_t pid;
@@ -84,17 +86,14 @@ setup_and_wait(char *command[])
if (waitpid(pid, NULL, 0) < 0)
err(1, "unexpect stop in waitpid");
- return (pid);
+ new_proc(info, pid);
}
/*
- * start_tracing picks up where setup_and_wait() dropped off -- namely,
- * it sets the event mask for the given process id. Called for both
- * monitoring an existing process and when we create our own.
+ * start_tracing is called to attach to an existing process.
*/
-
-int
-start_tracing(pid_t pid)
+void
+start_tracing(struct trussinfo *info, pid_t pid)
{
int ret, retry;
@@ -109,7 +108,7 @@ start_tracing(pid_t pid)
if (waitpid(pid, NULL, 0) < 0)
err(1, "Unexpect stop in waitpid");
- return (0);
+ new_proc(info, pid);
}
/*
@@ -118,7 +117,6 @@ start_tracing(pid_t pid)
* applies if truss was told to monitor an already-existing
* process.
*/
-
void
restore_proc(int signo __unused)
{
@@ -126,116 +124,485 @@ restore_proc(int signo __unused)
detaching = 1;
}
-static int
+static void
detach_proc(pid_t pid)
{
- int waitval;
/* stop the child so that we can detach */
kill(pid, SIGSTOP);
- if (waitpid(pid, &waitval, 0) < 0)
+ if (waitpid(pid, NULL, 0) < 0)
err(1, "Unexpected stop in waitpid");
if (ptrace(PT_DETACH, pid, (caddr_t)1, 0) < 0)
err(1, "Can not detach the process");
kill(pid, SIGCONT);
+}
+
+/*
+ * Determine the ABI. This is called after every exec, and when
+ * a process is first monitored.
+ */
+static struct procabi *
+find_abi(pid_t pid)
+{
+ struct procabi **pabi;
+ size_t len;
+ int error;
+ int mib[4];
+ char progt[32];
+
+ len = sizeof(progt);
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_SV_NAME;
+ mib[3] = pid;
+ error = sysctl(mib, 4, progt, &len, NULL, 0);
+ if (error != 0)
+ err(2, "can not get sysvec name");
+
+ SET_FOREACH(pabi, procabi) {
+ if (strcmp((*pabi)->type, progt) == 0)
+ return (*pabi);
+ }
+ warnx("ABI %s for pid %ld is not supported", progt, (long)pid);
+ return (NULL);
+}
+
+static void
+new_proc(struct trussinfo *info, pid_t pid)
+{
+ struct procinfo *np;
+
+ /*
+ * If this happens it means there is a bug in truss. Unfortunately
+ * this will kill any processes are attached to.
+ */
+ LIST_FOREACH(np, &info->proclist, entries) {
+ if (np->pid == pid)
+ errx(1, "Duplicate process for pid %ld", (long)pid);
+ }
+
+ if (info->flags & FOLLOWFORKS)
+ if (ptrace(PT_FOLLOW_FORK, pid, NULL, 1) == -1)
+ err(1, "Unable to follow forks for pid %ld", (long)pid);
+ np = calloc(1, sizeof(struct procinfo));
+ np->pid = pid;
+ np->abi = find_abi(pid);
+ SLIST_INIT(&np->threadlist);
+ LIST_INSERT_HEAD(&info->proclist, np, entries);
+}
+
+static void
+free_proc(struct procinfo *p)
+{
+ struct threadinfo *t, *t2;
+
+ SLIST_FOREACH_SAFE(t, &p->threadlist, entries, t2) {
+ free(t);
+ }
+ LIST_REMOVE(p, entries);
+ free(p);
+}
- return (waitval);
+static void
+detach_all_procs(struct trussinfo *info)
+{
+ struct procinfo *p, *p2;
+
+ LIST_FOREACH_SAFE(p, &info->proclist, entries, p2) {
+ detach_proc(p->pid);
+ free_proc(p);
+ }
+}
+
+static struct procinfo *
+find_proc(struct trussinfo *info, pid_t pid)
+{
+ struct procinfo *np;
+
+ LIST_FOREACH(np, &info->proclist, entries) {
+ if (np->pid == pid)
+ return (np);
+ }
+
+ return (NULL);
}
/*
- * Change curthread member based on lwpid.
- * If it is a new thread, create a threadinfo structure
+ * Change curthread member based on (pid, lwpid).
+ * If it is a new thread, create a threadinfo structure.
*/
static void
-find_thread(struct trussinfo *info, lwpid_t lwpid)
+find_thread(struct trussinfo *info, pid_t pid, lwpid_t lwpid)
{
- struct threadinfo *np;
+ struct procinfo *np;
+ struct threadinfo *nt;
- info->curthread = NULL;
- SLIST_FOREACH(np, &info->threadlist, entries) {
- if (np->tid == lwpid) {
- info->curthread = np;
+ np = find_proc(info, pid);
+ assert(np != NULL);
+
+ SLIST_FOREACH(nt, &np->threadlist, entries) {
+ if (nt->tid == lwpid) {
+ info->curthread = nt;
return;
}
}
- np = (struct threadinfo *)calloc(1, sizeof(struct threadinfo));
- if (np == NULL)
+ nt = calloc(1, sizeof(struct threadinfo));
+ if (nt == NULL)
err(1, "calloc() failed");
- np->tid = lwpid;
- SLIST_INSERT_HEAD(&info->threadlist, np, entries);
- info->curthread = np;
+ nt->proc = np;
+ nt->tid = lwpid;
+ SLIST_INSERT_HEAD(&np->threadlist, nt, entries);
+ info->curthread = nt;
}
/*
- * Start the traced process and wait until it stoped.
- * Fill trussinfo structure.
- * When this even returns, the traced process is in stop state.
+ * When a process exits, it no longer has any threads left. However,
+ * the main loop expects a valid curthread. In cases when a thread
+ * triggers the termination (e.g. calling exit or triggering a fault)
+ * we would ideally use that thread. However, if a process is killed
+ * by a signal sent from another process then there is no "correct"
+ * thread. We just punt and use the first thread.
*/
-void
-waitevent(struct trussinfo *info)
+static void
+find_exit_thread(struct trussinfo *info, pid_t pid)
+{
+ struct procinfo *np;
+ struct threadinfo *nt;
+
+ np = find_proc(info, pid);
+ assert(np != NULL);
+
+ if (SLIST_EMPTY(&np->threadlist)) {
+ /*
+ * If an existing process exits right after we attach
+ * to it but before it posts any events, there won't
+ * be any threads. Create a dummy thread and set its
+ * "before" time to the global start time.
+ */
+ nt = calloc(1, sizeof(struct threadinfo));
+ if (nt == NULL)
+ err(1, "calloc() failed");
+ nt->proc = np;
+ nt->tid = 0;
+ SLIST_INSERT_HEAD(&np->threadlist, nt, entries);
+ nt->before = info->start_time;
+ }
+ info->curthread = SLIST_FIRST(&np->threadlist);
+}
+
+static void
+alloc_syscall(struct threadinfo *t, struct ptrace_lwpinfo *pl)
+{
+ u_int i;
+
+ assert(t->in_syscall == 0);
+ assert(t->cs.number == 0);
+ assert(t->cs.name == NULL);
+ assert(t->cs.nargs == 0);
+ for (i = 0; i < nitems(t->cs.s_args); i++)
+ assert(t->cs.s_args[i] == NULL);
+ memset(t->cs.args, 0, sizeof(t->cs.args));
+ t->cs.number = pl->pl_syscall_code;
+ t->in_syscall = 1;
+}
+
+static void
+free_syscall(struct threadinfo *t)
{
- struct ptrace_lwpinfo lwpinfo;
- static int pending_signal = 0;
- int waitval;
+ u_int i;
+
+ for (i = 0; i < t->cs.nargs; i++)
+ free(t->cs.s_args[i]);
+ memset(&t->cs, 0, sizeof(t->cs));
+ t->in_syscall = 0;
+}
- ptrace(PT_SYSCALL, info->pid, (caddr_t)1, pending_signal);
- pending_signal = 0;
+static void
+enter_syscall(struct trussinfo *info, struct ptrace_lwpinfo *pl)
+{
+ struct threadinfo *t;
+ struct syscall *sc;
+ u_int i, narg;
-detach:
- if (detaching) {
- waitval = detach_proc(info->pid);
- info->pr_why = S_DETACHED;
- info->pr_data = WEXITSTATUS(waitval);
+ t = info->curthread;
+ alloc_syscall(t, pl);
+ narg = MIN(pl->pl_syscall_narg, nitems(t->cs.args));
+ if (narg != 0 && t->proc->abi->fetch_args(info, narg) != 0) {
+ free_syscall(t);
return;
}
- if (waitpid(info->pid, &waitval, 0) == -1) {
- if (errno == EINTR)
- goto detach;
- err(1, "Unexpected stop in waitpid");
+ if (t->cs.number >= 0 && t->cs.number < t->proc->abi->nsyscalls)
+ t->cs.name = t->proc->abi->syscallnames[t->cs.number];
+ if (t->cs.name == NULL)
+ fprintf(info->outfile, "-- UNKNOWN %s SYSCALL %d --\n",
+ t->proc->abi->type, t->cs.number);
+
+ sc = get_syscall(t->cs.name);
+ if (sc) {
+ t->cs.nargs = sc->nargs;
+ assert(sc->nargs <= nitems(t->cs.s_args));
+ } else {
+#if DEBUG
+ fprintf(stderr, "unknown syscall %s -- setting "
+ "args to %d\n", t->cs.name, t->cs.nargs);
+#endif
+ t->cs.nargs = narg;
}
- if (WIFCONTINUED(waitval)) {
- info->pr_why = S_NONE;
- return;
+ t->cs.sc = sc;
+
+ /*
+ * At this point, we set up the system call arguments.
+ * We ignore any OUT ones, however -- those are arguments that
+ * are set by the system call, and so are probably meaningless
+ * now. This doesn't currently support arguments that are
+ * passed in *and* out, however.
+ */
+ if (t->cs.name != NULL) {
+#if DEBUG
+ fprintf(stderr, "syscall %s(", t->cs.name);
+#endif
+ for (i = 0; i < t->cs.nargs; i++) {
+#if DEBUG
+ fprintf(stderr, "0x%lx%s", sc ?
+ t->cs.args[sc->args[i].offset] : t->cs.args[i],
+ i < (t->cs.nargs - 1) ? "," : "");
+#endif
+ if (sc && !(sc->args[i].type & OUT)) {
+ t->cs.s_args[i] = print_arg(&sc->args[i],
+ t->cs.args, 0, info);
+ }
+ }
+#if DEBUG
+ fprintf(stderr, ")\n");
+#endif
}
- if (WIFEXITED(waitval)) {
- info->pr_why = S_EXIT;
- info->pr_data = WEXITSTATUS(waitval);
+
+ clock_gettime(CLOCK_REALTIME, &t->before);
+}
+
+static void
+exit_syscall(struct trussinfo *info, struct ptrace_lwpinfo *pl)
+{
+ struct threadinfo *t;
+ struct procinfo *p;
+ struct syscall *sc;
+ long retval[2];
+ u_int i;
+ int errorp;
+
+ t = info->curthread;
+ if (!t->in_syscall)
+ return;
+
+ clock_gettime(CLOCK_REALTIME, &t->after);
+ p = t->proc;
+ if (p->abi->fetch_retval(info, retval, &errorp) < 0) {
+ free_syscall(t);
return;
}
- if (WIFSTOPPED(waitval)) {
- ptrace(PT_LWPINFO, info->pid, (caddr_t)&lwpinfo,
- sizeof(lwpinfo));
- find_thread(info, lwpinfo.pl_lwpid);
- switch (WSTOPSIG(waitval)) {
- case SIGTRAP:
- if (lwpinfo.pl_flags & PL_FLAG_SCE) {
- info->pr_why = S_SCE;
- info->curthread->in_syscall = 1;
- break;
- } else if (lwpinfo.pl_flags & PL_FLAG_SCX) {
- info->pr_why = S_SCX;
- info->curthread->in_syscall = 0;
- break;
- } else {
- errx(1,
+
+ sc = t->cs.sc;
+ if (sc == NULL) {
+ for (i = 0; i < t->cs.nargs; i++)
+ asprintf(&t->cs.s_args[i], "0x%lx", t->cs.args[i]);
+ } else {
+ /*
+ * Here, we only look for arguments that have OUT masked in --
+ * otherwise, they were handled in enter_syscall().
+ */
+ for (i = 0; i < sc->nargs; i++) {
+ char *temp;
+
+ if (sc->args[i].type & OUT) {
+ /*
+ * If an error occurred, then don't bother
+ * getting the data; it may not be valid.
+ */
+ if (errorp) {
+ asprintf(&temp, "0x%lx",
+ t->cs.args[sc->args[i].offset]);
+ } else {
+ temp = print_arg(&sc->args[i],
+ t->cs.args, retval, info);
+ }
+ t->cs.s_args[i] = temp;
+ }
+ }
+ }
+
+ print_syscall_ret(info, t->cs.name, t->cs.nargs, t->cs.s_args,
+ errorp, retval, sc);
+ free_syscall(t);
+
+ /*
+ * If the process executed a new image, check the ABI. If the
+ * new ABI isn't supported, stop tracing this process.
+ */
+ if (pl->pl_flags & PL_FLAG_EXEC) {
+ p->abi = find_abi(p->pid);
+ if (p->abi == NULL) {
+ if (ptrace(PT_DETACH, p->pid, (caddr_t)1, 0) < 0)
+ err(1, "Can not detach the process");
+ free_proc(p);
+ }
+ }
+}
+
+static void
+report_exit(struct trussinfo *info, siginfo_t *si)
+{
+ struct timespec timediff;
+
+ if (info->flags & FOLLOWFORKS)
+ fprintf(info->outfile, "%5d: ", si->si_pid);
+ clock_gettime(CLOCK_REALTIME, &info->curthread->after);
+ if (info->flags & ABSOLUTETIMESTAMPS) {
+ timespecsubt(&info->curthread->after, &info->start_time,
+ &timediff);
+ fprintf(info->outfile, "%jd.%09ld ", (intmax_t)timediff.tv_sec,
+ timediff.tv_nsec);
+ }
+ if (info->flags & RELATIVETIMESTAMPS) {
+ timespecsubt(&info->curthread->after, &info->curthread->before,
+ &timediff);
+ fprintf(info->outfile, "%jd.%09ld ", (intmax_t)timediff.tv_sec,
+ timediff.tv_nsec);
+ }
+ if (si->si_code == CLD_EXITED)
+ fprintf(info->outfile, "process exit, rval = %u\n",
+ si->si_status);
+ else
+ fprintf(info->outfile, "process killed, signal = %u%s\n",
+ si->si_status, si->si_code == CLD_DUMPED ?
+ " (core dumped)" : "");
+}
+
+static void
+report_new_child(struct trussinfo *info, pid_t pid)
+{
+ struct timespec timediff;
+
+ clock_gettime(CLOCK_REALTIME, &info->curthread->after);
+ assert(info->flags & FOLLOWFORKS);
+ fprintf(info->outfile, "%5d: ", pid);
+ if (info->flags & ABSOLUTETIMESTAMPS) {
+ timespecsubt(&info->curthread->after, &info->start_time,
+ &timediff);
+ fprintf(info->outfile, "%jd.%09ld ", (intmax_t)timediff.tv_sec,
+ timediff.tv_nsec);
+ }
+ if (info->flags & RELATIVETIMESTAMPS) {
+ timediff.tv_sec = 0;
+ timediff.tv_nsec = 0;
+ fprintf(info->outfile, "%jd.%09ld ", (intmax_t)timediff.tv_sec,
+ timediff.tv_nsec);
+ }
+ fprintf(info->outfile, "<new process>\n");
+}
+
+static void
+report_signal(struct trussinfo *info, siginfo_t *si)
+{
+ struct timespec timediff;
+ char *signame;
+
+ if (info->flags & FOLLOWFORKS)
+ fprintf(info->outfile, "%5d: ", si->si_pid);
+ if (info->flags & ABSOLUTETIMESTAMPS) {
+ timespecsubt(&info->curthread->after, &info->start_time,
+ &timediff);
+ fprintf(info->outfile, "%jd.%09ld ", (intmax_t)timediff.tv_sec,
+ timediff.tv_nsec);
+ }
+ if (info->flags & RELATIVETIMESTAMPS) {
+ timespecsubt(&info->curthread->after, &info->curthread->before,
+ &timediff);
+ fprintf(info->outfile, "%jd.%09ld ", (intmax_t)timediff.tv_sec,
+ timediff.tv_nsec);
+ }
+ signame = strsig(si->si_status);
+ fprintf(info->outfile, "SIGNAL %u (%s)\n", si->si_status,
+ signame == NULL ? "?" : signame);
+}
+
+/*
+ * Wait for events until all the processes have exited or truss has been
+ * asked to stop.
+ */
+void
+eventloop(struct trussinfo *info)
+{
+ struct ptrace_lwpinfo pl;
+ siginfo_t si;
+ int pending_signal;
+
+ while (!LIST_EMPTY(&info->proclist)) {
+ if (detaching) {
+ detach_all_procs(info);
+ return;
+ }
+
+ if (waitid(P_ALL, 0, &si, WTRAPPED | WEXITED) == -1) {
+ if (errno == EINTR)
+ continue;
+ err(1, "Unexpected error from waitid");
+ }
+
+ assert(si.si_signo == SIGCHLD);
+
+ switch (si.si_code) {
+ case CLD_EXITED:
+ case CLD_KILLED:
+ case CLD_DUMPED:
+ find_exit_thread(info, si.si_pid);
+ if ((info->flags & COUNTONLY) == 0)
+ report_exit(info, &si);
+ free_proc(info->curthread->proc);
+ info->curthread = NULL;
+ break;
+ case CLD_TRAPPED:
+ if (ptrace(PT_LWPINFO, si.si_pid, (caddr_t)&pl,
+ sizeof(pl)) == -1)
+ err(1, "ptrace(PT_LWPINFO)");
+
+ if (pl.pl_flags & PL_FLAG_CHILD) {
+ new_proc(info, si.si_pid);
+ assert(LIST_FIRST(&info->proclist)->abi !=
+ NULL);
+ }
+ find_thread(info, si.si_pid, pl.pl_lwpid);
+
+ if (si.si_status == SIGTRAP) {
+ if (pl.pl_flags & PL_FLAG_SCE)
+ enter_syscall(info, &pl);
+ else if (pl.pl_flags & PL_FLAG_SCX)
+ exit_syscall(info, &pl);
+ else
+ errx(1,
"pl_flags %x contains neither PL_FLAG_SCE nor PL_FLAG_SCX",
- lwpinfo.pl_flags);
+ pl.pl_flags);
+ pending_signal = 0;
+ } else if (pl.pl_flags & PL_FLAG_CHILD) {
+ if ((info->flags & COUNTONLY) == 0)
+ report_new_child(info, si.si_pid);
+ pending_signal = 0;
+ } else {
+ if ((info->flags & NOSIGS) == 0)
+ report_signal(info, &si);
+ pending_signal = si.si_status;
}
- default:
- info->pr_why = S_SIG;
- info->pr_data = WSTOPSIG(waitval);
- pending_signal = info->pr_data;
+ ptrace(PT_SYSCALL, si.si_pid, (caddr_t)1,
+ pending_signal);
+ break;
+ case CLD_STOPPED:
+ errx(1, "waitid reported CLD_STOPPED");
+ case CLD_CONTINUED:
break;
}
}
- if (WIFSIGNALED(waitval)) {
- info->pr_why = S_EXIT;
- info->pr_data = 0;
- return;
- }
}