trace: split into several functions
This change moves the main loop back to main() and splits trace() into several functions. There are no algorithmic changes. * strace.c (print_debug_info, maybe_allocate_tcb, maybe_switch_tcbs, print_signalled, print_exited, print_stopped, startup_tcb): New functions. (trace) Use them. Move the main loop ... (main): ... here.
This commit is contained in:
parent
d8890b5c9d
commit
4b4ec12087
762
strace.c
762
strace.c
@ -2030,143 +2030,287 @@ interrupt(int sig)
|
||||
}
|
||||
|
||||
static void
|
||||
print_debug_info(const int pid, const int status)
|
||||
{
|
||||
const unsigned int event = (unsigned int) status >> 16;
|
||||
char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16];
|
||||
char evbuf[sizeof(",EVENT_VFORK_DONE (%u)") + sizeof(int)*3 /*paranoia:*/ + 16];
|
||||
|
||||
strcpy(buf, "???");
|
||||
if (WIFSIGNALED(status))
|
||||
#ifdef WCOREDUMP
|
||||
sprintf(buf, "WIFSIGNALED,%ssig=%s",
|
||||
WCOREDUMP(status) ? "core," : "",
|
||||
signame(WTERMSIG(status)));
|
||||
#else
|
||||
sprintf(buf, "WIFSIGNALED,sig=%s",
|
||||
signame(WTERMSIG(status)));
|
||||
#endif
|
||||
if (WIFEXITED(status))
|
||||
sprintf(buf, "WIFEXITED,exitcode=%u", WEXITSTATUS(status));
|
||||
if (WIFSTOPPED(status))
|
||||
sprintf(buf, "WIFSTOPPED,sig=%s", signame(WSTOPSIG(status)));
|
||||
#ifdef WIFCONTINUED
|
||||
/* Should never be seen */
|
||||
if (WIFCONTINUED(status))
|
||||
strcpy(buf, "WIFCONTINUED");
|
||||
#endif
|
||||
evbuf[0] = '\0';
|
||||
if (event != 0) {
|
||||
static const char *const event_names[] = {
|
||||
[PTRACE_EVENT_CLONE] = "CLONE",
|
||||
[PTRACE_EVENT_FORK] = "FORK",
|
||||
[PTRACE_EVENT_VFORK] = "VFORK",
|
||||
[PTRACE_EVENT_VFORK_DONE] = "VFORK_DONE",
|
||||
[PTRACE_EVENT_EXEC] = "EXEC",
|
||||
[PTRACE_EVENT_EXIT] = "EXIT",
|
||||
/* [PTRACE_EVENT_STOP (=128)] would make biggish array */
|
||||
};
|
||||
const char *e = "??";
|
||||
if (event < ARRAY_SIZE(event_names))
|
||||
e = event_names[event];
|
||||
else if (event == PTRACE_EVENT_STOP)
|
||||
e = "STOP";
|
||||
sprintf(evbuf, ",EVENT_%s (%u)", e, event);
|
||||
}
|
||||
fprintf(stderr, " [wait(0x%06x) = %u] %s%s\n", status, pid, buf, evbuf);
|
||||
}
|
||||
|
||||
static struct tcb *
|
||||
maybe_allocate_tcb(const int pid, const int status)
|
||||
{
|
||||
if (!WIFSTOPPED(status)) {
|
||||
/*
|
||||
* This can happen if we inherited an unknown child.
|
||||
* Example: (sleep 1 & exec strace sleep 2)
|
||||
*/
|
||||
error_msg("Exit of unknown pid %u ignored", pid);
|
||||
return NULL;
|
||||
}
|
||||
if (followfork) {
|
||||
/* We assume it's a fork/vfork/clone child */
|
||||
struct tcb *tcp = alloctcb(pid);
|
||||
tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
|
||||
newoutf(tcp);
|
||||
if (!qflag)
|
||||
fprintf(stderr, "Process %d attached\n", pid);
|
||||
return tcp;
|
||||
} else {
|
||||
/* This can happen if a clone call used
|
||||
* CLONE_PTRACE itself.
|
||||
*/
|
||||
ptrace(PTRACE_CONT, pid, (char *) 0, 0);
|
||||
error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static struct tcb *
|
||||
maybe_switch_tcbs(struct tcb *tcp, const int pid)
|
||||
{
|
||||
FILE *fp;
|
||||
struct tcb *execve_thread;
|
||||
long old_pid = 0;
|
||||
|
||||
if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) < 0)
|
||||
return tcp;
|
||||
/* Avoid truncation in pid2tcb() param passing */
|
||||
if (old_pid <= 0 || old_pid == pid)
|
||||
return tcp;
|
||||
if ((unsigned long) old_pid > UINT_MAX)
|
||||
return tcp;
|
||||
execve_thread = pid2tcb(old_pid);
|
||||
/* It should be !NULL, but I feel paranoid */
|
||||
if (!execve_thread)
|
||||
return tcp;
|
||||
|
||||
if (execve_thread->curcol != 0) {
|
||||
/*
|
||||
* One case we are here is -ff:
|
||||
* try "strace -oLOG -ff test/threaded_execve"
|
||||
*/
|
||||
fprintf(execve_thread->outf, " <pid changed to %d ...>\n", pid);
|
||||
/*execve_thread->curcol = 0; - no need, see code below */
|
||||
}
|
||||
/* Swap output FILEs (needed for -ff) */
|
||||
fp = execve_thread->outf;
|
||||
execve_thread->outf = tcp->outf;
|
||||
tcp->outf = fp;
|
||||
/* And their column positions */
|
||||
execve_thread->curcol = tcp->curcol;
|
||||
tcp->curcol = 0;
|
||||
/* Drop leader, but close execve'd thread outfile (if -ff) */
|
||||
droptcb(tcp);
|
||||
/* Switch to the thread, reusing leader's outfile and pid */
|
||||
tcp = execve_thread;
|
||||
tcp->pid = pid;
|
||||
if (cflag != CFLAG_ONLY_STATS) {
|
||||
printleader(tcp);
|
||||
tprintf("+++ superseded by execve in pid %lu +++\n", old_pid);
|
||||
line_ended();
|
||||
tcp->flags |= TCB_REPRINT;
|
||||
}
|
||||
|
||||
return tcp;
|
||||
}
|
||||
|
||||
static void
|
||||
print_signalled(struct tcb *tcp, const int pid, const int status)
|
||||
{
|
||||
if (pid == strace_child)
|
||||
exit_code = 0x100 | WTERMSIG(status);
|
||||
|
||||
if (cflag != CFLAG_ONLY_STATS
|
||||
&& (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)
|
||||
) {
|
||||
printleader(tcp);
|
||||
#ifdef WCOREDUMP
|
||||
tprintf("+++ killed by %s %s+++\n",
|
||||
signame(WTERMSIG(status)),
|
||||
WCOREDUMP(status) ? "(core dumped) " : "");
|
||||
#else
|
||||
tprintf("+++ killed by %s +++\n",
|
||||
signame(WTERMSIG(status)));
|
||||
#endif
|
||||
line_ended();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_exited(struct tcb *tcp, const int pid, const int status)
|
||||
{
|
||||
if (pid == strace_child)
|
||||
exit_code = WEXITSTATUS(status);
|
||||
|
||||
if (cflag != CFLAG_ONLY_STATS &&
|
||||
qflag < 2) {
|
||||
printleader(tcp);
|
||||
tprintf("+++ exited with %d +++\n", WEXITSTATUS(status));
|
||||
line_ended();
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
print_stopped(struct tcb *tcp, const siginfo_t *si, const unsigned int sig)
|
||||
{
|
||||
if (cflag != CFLAG_ONLY_STATS
|
||||
&& !hide_log_until_execve
|
||||
&& (qual_flags[sig] & QUAL_SIGNAL)
|
||||
) {
|
||||
printleader(tcp);
|
||||
if (si) {
|
||||
tprintf("--- %s ", signame(sig));
|
||||
printsiginfo(si, verbose(tcp));
|
||||
tprints(" ---\n");
|
||||
} else
|
||||
tprintf("--- stopped by %s ---\n", signame(sig));
|
||||
line_ended();
|
||||
}
|
||||
}
|
||||
|
||||
static bool
|
||||
startup_tcb(struct tcb *tcp)
|
||||
{
|
||||
if (debug_flag)
|
||||
fprintf(stderr, "pid %d has TCB_STARTUP, initializing it\n",
|
||||
tcp->pid);
|
||||
|
||||
tcp->flags &= ~TCB_STARTUP;
|
||||
|
||||
if (tcp->flags & TCB_BPTSET) {
|
||||
/*
|
||||
* One example is a breakpoint inherited from
|
||||
* parent through fork().
|
||||
*/
|
||||
if (clearbpt(tcp) < 0) {
|
||||
/* Pretty fatal */
|
||||
droptcb(tcp);
|
||||
exit_code = 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!use_seize && ptrace_setoptions) {
|
||||
if (debug_flag)
|
||||
fprintf(stderr, "setting opts 0x%x on pid %d\n",
|
||||
ptrace_setoptions, tcp->pid);
|
||||
if (ptrace(PTRACE_SETOPTIONS, tcp->pid, NULL, ptrace_setoptions) < 0) {
|
||||
if (errno != ESRCH) {
|
||||
/* Should never happen, really */
|
||||
perror_msg_and_die("PTRACE_SETOPTIONS");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Returns true iff the main trace loop has to continue. */
|
||||
static bool
|
||||
trace(void)
|
||||
{
|
||||
int pid;
|
||||
int wait_errno;
|
||||
int status;
|
||||
bool stopped;
|
||||
unsigned int sig;
|
||||
unsigned int event;
|
||||
struct tcb *tcp;
|
||||
struct rusage ru;
|
||||
|
||||
/* Used to be "while (nprocs != 0)", but in this testcase:
|
||||
* int main() { _exit(!!fork()); }
|
||||
* under strace -f, parent sometimes (rarely) manages
|
||||
* to exit before we see the first stop of the child,
|
||||
* and we are losing track of it:
|
||||
* 19923 clone(...) = 19924
|
||||
* 19923 exit_group(1) = ?
|
||||
* 19923 +++ exited with 1 +++
|
||||
* Waiting for ECHILD works better.
|
||||
* (However, if -o|logger is in use, we can't do that.
|
||||
* Can work around that by double-forking the logger,
|
||||
* but that loses the ability to wait for its completion on exit.
|
||||
* Oh well...)
|
||||
*/
|
||||
while (1) {
|
||||
int pid;
|
||||
int wait_errno;
|
||||
int status;
|
||||
int stopped;
|
||||
unsigned int sig;
|
||||
unsigned event;
|
||||
struct tcb *tcp;
|
||||
if (interrupted)
|
||||
return false;
|
||||
|
||||
if (interrupted)
|
||||
return;
|
||||
if (popen_pid != 0 && nprocs == 0)
|
||||
return false;
|
||||
|
||||
if (popen_pid != 0 && nprocs == 0)
|
||||
return;
|
||||
if (interactive)
|
||||
sigprocmask(SIG_SETMASK, &empty_set, NULL);
|
||||
pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
|
||||
wait_errno = errno;
|
||||
if (interactive)
|
||||
sigprocmask(SIG_BLOCK, &blocked_set, NULL);
|
||||
|
||||
if (interactive)
|
||||
sigprocmask(SIG_SETMASK, &empty_set, NULL);
|
||||
pid = wait4(-1, &status, __WALL, (cflag ? &ru : NULL));
|
||||
wait_errno = errno;
|
||||
if (interactive)
|
||||
sigprocmask(SIG_BLOCK, &blocked_set, NULL);
|
||||
if (pid < 0) {
|
||||
if (wait_errno == EINTR)
|
||||
return true;
|
||||
if (nprocs == 0 && wait_errno == ECHILD)
|
||||
return false;
|
||||
/*
|
||||
* If nprocs > 0, ECHILD is not expected,
|
||||
* treat it as any other error here:
|
||||
*/
|
||||
errno = wait_errno;
|
||||
perror_msg_and_die("wait4(__WALL)");
|
||||
}
|
||||
|
||||
if (pid < 0) {
|
||||
if (wait_errno == EINTR)
|
||||
continue;
|
||||
if (nprocs == 0 && wait_errno == ECHILD)
|
||||
return;
|
||||
/* If nprocs > 0, ECHILD is not expected,
|
||||
* treat it as any other error here:
|
||||
*/
|
||||
errno = wait_errno;
|
||||
perror_msg_and_die("wait4(__WALL)");
|
||||
}
|
||||
if (pid == popen_pid) {
|
||||
if (!WIFSTOPPED(status))
|
||||
popen_pid = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pid == popen_pid) {
|
||||
if (!WIFSTOPPED(status))
|
||||
popen_pid = 0;
|
||||
continue;
|
||||
}
|
||||
if (debug_flag)
|
||||
print_debug_info(pid, status);
|
||||
|
||||
event = ((unsigned)status >> 16);
|
||||
if (debug_flag) {
|
||||
char buf[sizeof("WIFEXITED,exitcode=%u") + sizeof(int)*3 /*paranoia:*/ + 16];
|
||||
char evbuf[sizeof(",EVENT_VFORK_DONE (%u)") + sizeof(int)*3 /*paranoia:*/ + 16];
|
||||
strcpy(buf, "???");
|
||||
if (WIFSIGNALED(status))
|
||||
#ifdef WCOREDUMP
|
||||
sprintf(buf, "WIFSIGNALED,%ssig=%s",
|
||||
WCOREDUMP(status) ? "core," : "",
|
||||
signame(WTERMSIG(status)));
|
||||
#else
|
||||
sprintf(buf, "WIFSIGNALED,sig=%s",
|
||||
signame(WTERMSIG(status)));
|
||||
#endif
|
||||
if (WIFEXITED(status))
|
||||
sprintf(buf, "WIFEXITED,exitcode=%u", WEXITSTATUS(status));
|
||||
if (WIFSTOPPED(status))
|
||||
sprintf(buf, "WIFSTOPPED,sig=%s", signame(WSTOPSIG(status)));
|
||||
#ifdef WIFCONTINUED
|
||||
/* Should never be seen */
|
||||
if (WIFCONTINUED(status))
|
||||
strcpy(buf, "WIFCONTINUED");
|
||||
#endif
|
||||
evbuf[0] = '\0';
|
||||
if (event != 0) {
|
||||
static const char *const event_names[] = {
|
||||
[PTRACE_EVENT_CLONE] = "CLONE",
|
||||
[PTRACE_EVENT_FORK] = "FORK",
|
||||
[PTRACE_EVENT_VFORK] = "VFORK",
|
||||
[PTRACE_EVENT_VFORK_DONE] = "VFORK_DONE",
|
||||
[PTRACE_EVENT_EXEC] = "EXEC",
|
||||
[PTRACE_EVENT_EXIT] = "EXIT",
|
||||
/* [PTRACE_EVENT_STOP (=128)] would make biggish array */
|
||||
};
|
||||
const char *e = "??";
|
||||
if (event < ARRAY_SIZE(event_names))
|
||||
e = event_names[event];
|
||||
else if (event == PTRACE_EVENT_STOP)
|
||||
e = "STOP";
|
||||
sprintf(evbuf, ",EVENT_%s (%u)", e, event);
|
||||
}
|
||||
fprintf(stderr, " [wait(0x%06x) = %u] %s%s\n", status, pid, buf, evbuf);
|
||||
}
|
||||
/* Look up 'pid' in our table. */
|
||||
tcp = pid2tcb(pid);
|
||||
|
||||
/* Look up 'pid' in our table. */
|
||||
tcp = pid2tcb(pid);
|
||||
if (!tcp) {
|
||||
tcp = maybe_allocate_tcb(pid, status);
|
||||
if (!tcp)
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!tcp) {
|
||||
if (!WIFSTOPPED(status)) {
|
||||
/* This can happen if we inherited
|
||||
* an unknown child. Example:
|
||||
* (sleep 1 & exec strace sleep 2)
|
||||
*/
|
||||
error_msg("Exit of unknown pid %u seen", pid);
|
||||
continue;
|
||||
}
|
||||
if (followfork) {
|
||||
/* We assume it's a fork/vfork/clone child */
|
||||
tcp = alloctcb(pid);
|
||||
tcp->flags |= TCB_ATTACHED | TCB_STARTUP | post_attach_sigstop;
|
||||
newoutf(tcp);
|
||||
if (!qflag)
|
||||
fprintf(stderr, "Process %d attached\n",
|
||||
pid);
|
||||
} else {
|
||||
/* This can happen if a clone call used
|
||||
* CLONE_PTRACE itself.
|
||||
*/
|
||||
ptrace(PTRACE_CONT, pid, (char *) 0, 0);
|
||||
error_msg("Stop of unknown pid %u seen, PTRACE_CONTed it", pid);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
clear_regs();
|
||||
if (WIFSTOPPED(status))
|
||||
get_regs(pid);
|
||||
|
||||
clear_regs();
|
||||
if (WIFSTOPPED(status))
|
||||
get_regs(pid);
|
||||
event = (unsigned int) status >> 16;
|
||||
|
||||
/* Under Linux, execve changes pid to thread leader's pid,
|
||||
if (event == PTRACE_EVENT_EXEC) {
|
||||
/*
|
||||
* Under Linux, execve changes pid to thread leader's pid,
|
||||
* and we see this changed pid on EVENT_EXEC and later,
|
||||
* execve sysexit. Leader "disappears" without exit
|
||||
* notification. Let user know that, drop leader's tcb,
|
||||
@ -2180,248 +2324,159 @@ trace(void)
|
||||
* PTRACE_GETEVENTMSG returns old pid starting from Linux 3.0.
|
||||
* On 2.6 and earlier, it can return garbage.
|
||||
*/
|
||||
if (event == PTRACE_EVENT_EXEC && os_release >= KERNEL_VERSION(3,0,0)) {
|
||||
FILE *fp;
|
||||
struct tcb *execve_thread;
|
||||
long old_pid = 0;
|
||||
if (os_release >= KERNEL_VERSION(3,0,0))
|
||||
tcp = maybe_switch_tcbs(tcp, pid);
|
||||
|
||||
if (ptrace(PTRACE_GETEVENTMSG, pid, NULL, (long) &old_pid) < 0)
|
||||
goto dont_switch_tcbs;
|
||||
/* Avoid truncation in pid2tcb() param passing */
|
||||
if (old_pid <= 0 || old_pid == pid)
|
||||
goto dont_switch_tcbs;
|
||||
if ((unsigned long) old_pid > UINT_MAX)
|
||||
goto dont_switch_tcbs;
|
||||
execve_thread = pid2tcb(old_pid);
|
||||
/* It should be !NULL, but I feel paranoid */
|
||||
if (!execve_thread)
|
||||
goto dont_switch_tcbs;
|
||||
if (detach_on_execve && !skip_one_b_execve)
|
||||
detach(tcp); /* do "-b execve" thingy */
|
||||
skip_one_b_execve = 0;
|
||||
}
|
||||
|
||||
if (execve_thread->curcol != 0) {
|
||||
/*
|
||||
* One case we are here is -ff:
|
||||
* try "strace -oLOG -ff test/threaded_execve"
|
||||
*/
|
||||
fprintf(execve_thread->outf, " <pid changed to %d ...>\n", pid);
|
||||
/*execve_thread->curcol = 0; - no need, see code below */
|
||||
}
|
||||
/* Swap output FILEs (needed for -ff) */
|
||||
fp = execve_thread->outf;
|
||||
execve_thread->outf = tcp->outf;
|
||||
tcp->outf = fp;
|
||||
/* And their column positions */
|
||||
execve_thread->curcol = tcp->curcol;
|
||||
tcp->curcol = 0;
|
||||
/* Drop leader, but close execve'd thread outfile (if -ff) */
|
||||
droptcb(tcp);
|
||||
/* Switch to the thread, reusing leader's outfile and pid */
|
||||
tcp = execve_thread;
|
||||
tcp->pid = pid;
|
||||
if (cflag != CFLAG_ONLY_STATS) {
|
||||
printleader(tcp);
|
||||
tprintf("+++ superseded by execve in pid %lu +++\n", old_pid);
|
||||
line_ended();
|
||||
tcp->flags |= TCB_REPRINT;
|
||||
}
|
||||
}
|
||||
dont_switch_tcbs:
|
||||
/* Set current output file */
|
||||
current_tcp = tcp;
|
||||
|
||||
if (event == PTRACE_EVENT_EXEC) {
|
||||
if (detach_on_execve && !skip_one_b_execve)
|
||||
detach(tcp); /* do "-b execve" thingy */
|
||||
skip_one_b_execve = 0;
|
||||
}
|
||||
if (cflag) {
|
||||
tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
|
||||
tcp->stime = ru.ru_stime;
|
||||
}
|
||||
|
||||
/* Set current output file */
|
||||
current_tcp = tcp;
|
||||
if (WIFSIGNALED(status)) {
|
||||
print_signalled(tcp, pid, status);
|
||||
droptcb(tcp);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (cflag) {
|
||||
tv_sub(&tcp->dtime, &ru.ru_stime, &tcp->stime);
|
||||
tcp->stime = ru.ru_stime;
|
||||
}
|
||||
if (WIFEXITED(status)) {
|
||||
print_exited(tcp, pid, status);
|
||||
droptcb(tcp);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (WIFSIGNALED(status)) {
|
||||
if (pid == strace_child)
|
||||
exit_code = 0x100 | WTERMSIG(status);
|
||||
if (cflag != CFLAG_ONLY_STATS
|
||||
&& (qual_flags[WTERMSIG(status)] & QUAL_SIGNAL)
|
||||
) {
|
||||
printleader(tcp);
|
||||
#ifdef WCOREDUMP
|
||||
tprintf("+++ killed by %s %s+++\n",
|
||||
signame(WTERMSIG(status)),
|
||||
WCOREDUMP(status) ? "(core dumped) " : "");
|
||||
#else
|
||||
tprintf("+++ killed by %s +++\n",
|
||||
signame(WTERMSIG(status)));
|
||||
#endif
|
||||
line_ended();
|
||||
}
|
||||
droptcb(tcp);
|
||||
continue;
|
||||
}
|
||||
if (WIFEXITED(status)) {
|
||||
if (pid == strace_child)
|
||||
exit_code = WEXITSTATUS(status);
|
||||
if (cflag != CFLAG_ONLY_STATS &&
|
||||
qflag < 2) {
|
||||
printleader(tcp);
|
||||
tprintf("+++ exited with %d +++\n", WEXITSTATUS(status));
|
||||
line_ended();
|
||||
}
|
||||
droptcb(tcp);
|
||||
continue;
|
||||
}
|
||||
if (!WIFSTOPPED(status)) {
|
||||
fprintf(stderr, "PANIC: pid %u not stopped\n", pid);
|
||||
droptcb(tcp);
|
||||
continue;
|
||||
}
|
||||
if (!WIFSTOPPED(status)) {
|
||||
/*
|
||||
* Neither signalled, exited or stopped.
|
||||
* How could that be?
|
||||
*/
|
||||
error_msg("pid %u not stopped!", pid);
|
||||
droptcb(tcp);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Is this the very first time we see this tracee stopped? */
|
||||
if (tcp->flags & TCB_STARTUP) {
|
||||
if (debug_flag)
|
||||
fprintf(stderr, "pid %d has TCB_STARTUP, initializing it\n", tcp->pid);
|
||||
tcp->flags &= ~TCB_STARTUP;
|
||||
if (tcp->flags & TCB_BPTSET) {
|
||||
/*
|
||||
* One example is a breakpoint inherited from
|
||||
* parent through fork().
|
||||
*/
|
||||
if (clearbpt(tcp) < 0) {
|
||||
/* Pretty fatal */
|
||||
droptcb(tcp);
|
||||
exit_code = 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!use_seize && ptrace_setoptions) {
|
||||
if (debug_flag)
|
||||
fprintf(stderr, "setting opts 0x%x on pid %d\n", ptrace_setoptions, tcp->pid);
|
||||
if (ptrace(PTRACE_SETOPTIONS, tcp->pid, NULL, ptrace_setoptions) < 0) {
|
||||
if (errno != ESRCH) {
|
||||
/* Should never happen, really */
|
||||
perror_msg_and_die("PTRACE_SETOPTIONS");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Is this the very first time we see this tracee stopped? */
|
||||
if (tcp->flags & TCB_STARTUP) {
|
||||
if (!startup_tcb(tcp))
|
||||
return false;
|
||||
}
|
||||
|
||||
sig = WSTOPSIG(status);
|
||||
sig = WSTOPSIG(status);
|
||||
|
||||
if (event != 0) {
|
||||
/* Ptrace event */
|
||||
if (event != 0) {
|
||||
/* Ptrace event */
|
||||
#if USE_SEIZE
|
||||
if (event == PTRACE_EVENT_STOP) {
|
||||
/*
|
||||
* PTRACE_INTERRUPT-stop or group-stop.
|
||||
* PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
|
||||
*/
|
||||
if (sig == SIGSTOP
|
||||
|| sig == SIGTSTP
|
||||
|| sig == SIGTTIN
|
||||
|| sig == SIGTTOU
|
||||
) {
|
||||
stopped = 1;
|
||||
if (event == PTRACE_EVENT_STOP) {
|
||||
/*
|
||||
* PTRACE_INTERRUPT-stop or group-stop.
|
||||
* PTRACE_INTERRUPT-stop has sig == SIGTRAP here.
|
||||
*/
|
||||
switch (sig) {
|
||||
case SIGSTOP:
|
||||
case SIGTSTP:
|
||||
case SIGTTIN:
|
||||
case SIGTTOU:
|
||||
stopped = true;
|
||||
goto show_stopsig;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
goto restart_tracee_with_sig_0;
|
||||
}
|
||||
goto restart_tracee_with_sig_0;
|
||||
}
|
||||
|
||||
/* Is this post-attach SIGSTOP?
|
||||
* Interestingly, the process may stop
|
||||
* with STOPSIG equal to some other signal
|
||||
* than SIGSTOP if we happend to attach
|
||||
* just before the process takes a signal.
|
||||
/*
|
||||
* Is this post-attach SIGSTOP?
|
||||
* Interestingly, the process may stop
|
||||
* with STOPSIG equal to some other signal
|
||||
* than SIGSTOP if we happend to attach
|
||||
* just before the process takes a signal.
|
||||
*/
|
||||
if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
|
||||
if (debug_flag)
|
||||
fprintf(stderr, "ignored SIGSTOP on pid %d\n", tcp->pid);
|
||||
tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
|
||||
goto restart_tracee_with_sig_0;
|
||||
}
|
||||
|
||||
if (sig != syscall_trap_sig) {
|
||||
siginfo_t si;
|
||||
|
||||
/*
|
||||
* True if tracee is stopped by signal
|
||||
* (as opposed to "tracee received signal").
|
||||
* TODO: shouldn't we check for errno == EINVAL too?
|
||||
* We can get ESRCH instead, you know...
|
||||
*/
|
||||
if (sig == SIGSTOP && (tcp->flags & TCB_IGNORE_ONE_SIGSTOP)) {
|
||||
if (debug_flag)
|
||||
fprintf(stderr, "ignored SIGSTOP on pid %d\n", tcp->pid);
|
||||
tcp->flags &= ~TCB_IGNORE_ONE_SIGSTOP;
|
||||
goto restart_tracee_with_sig_0;
|
||||
}
|
||||
|
||||
if (sig != syscall_trap_sig) {
|
||||
siginfo_t si;
|
||||
|
||||
/* Nonzero (true) if tracee is stopped by signal
|
||||
* (as opposed to "tracee received signal").
|
||||
* TODO: shouldn't we check for errno == EINVAL too?
|
||||
* We can get ESRCH instead, you know...
|
||||
*/
|
||||
stopped = (ptrace(PTRACE_GETSIGINFO, pid, 0, (long) &si) < 0);
|
||||
stopped = ptrace(PTRACE_GETSIGINFO, pid, 0, (long) &si) < 0;
|
||||
#if USE_SEIZE
|
||||
show_stopsig:
|
||||
show_stopsig:
|
||||
#endif
|
||||
if (cflag != CFLAG_ONLY_STATS
|
||||
&& !hide_log_until_execve
|
||||
&& (qual_flags[sig] & QUAL_SIGNAL)
|
||||
) {
|
||||
printleader(tcp);
|
||||
if (!stopped) {
|
||||
tprintf("--- %s ", signame(sig));
|
||||
printsiginfo(&si, verbose(tcp));
|
||||
tprints(" ---\n");
|
||||
} else
|
||||
tprintf("--- stopped by %s ---\n",
|
||||
signame(sig));
|
||||
line_ended();
|
||||
}
|
||||
print_stopped(tcp, stopped ? NULL : &si, sig);
|
||||
|
||||
if (!stopped)
|
||||
/* It's signal-delivery-stop. Inject the signal */
|
||||
goto restart_tracee;
|
||||
|
||||
/* It's group-stop */
|
||||
if (use_seize) {
|
||||
/*
|
||||
* This ends ptrace-stop, but does *not* end group-stop.
|
||||
* This makes stopping signals work properly on straced process
|
||||
* (that is, process really stops. It used to continue to run).
|
||||
*/
|
||||
if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
|
||||
/* Note: ptrace_restart emitted error message */
|
||||
exit_code = 1;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* We don't have PTRACE_LISTEN support... */
|
||||
if (!stopped)
|
||||
/* It's signal-delivery-stop. Inject the signal */
|
||||
goto restart_tracee;
|
||||
}
|
||||
|
||||
/* We handled quick cases, we are permitted to interrupt now. */
|
||||
if (interrupted)
|
||||
return;
|
||||
|
||||
/* This should be syscall entry or exit.
|
||||
* (Or it still can be that pesky post-execve SIGTRAP!)
|
||||
* Handle it.
|
||||
*/
|
||||
if (trace_syscall(tcp) < 0) {
|
||||
/* ptrace() failed in trace_syscall().
|
||||
* Likely a result of process disappearing mid-flight.
|
||||
* Observed case: exit_group() or SIGKILL terminating
|
||||
* all processes in thread group.
|
||||
* We assume that ptrace error was caused by process death.
|
||||
* We used to detach(tcp) here, but since we no longer
|
||||
* implement "detach before death" policy/hack,
|
||||
* we can let this process to report its death to us
|
||||
* normally, via WIFEXITED or WIFSIGNALED wait status.
|
||||
/* It's group-stop */
|
||||
if (use_seize) {
|
||||
/*
|
||||
* This ends ptrace-stop, but does *not* end group-stop.
|
||||
* This makes stopping signals work properly on straced process
|
||||
* (that is, process really stops. It used to continue to run).
|
||||
*/
|
||||
continue;
|
||||
if (ptrace_restart(PTRACE_LISTEN, tcp, 0) < 0) {
|
||||
/* Note: ptrace_restart emitted error message */
|
||||
exit_code = 1;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
restart_tracee_with_sig_0:
|
||||
sig = 0;
|
||||
restart_tracee:
|
||||
if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
|
||||
/* Note: ptrace_restart emitted error message */
|
||||
exit_code = 1;
|
||||
return;
|
||||
}
|
||||
} /* while (1) */
|
||||
/* We don't have PTRACE_LISTEN support... */
|
||||
goto restart_tracee;
|
||||
}
|
||||
|
||||
/* We handled quick cases, we are permitted to interrupt now. */
|
||||
if (interrupted)
|
||||
return false;
|
||||
|
||||
/*
|
||||
* This should be syscall entry or exit.
|
||||
* (Or it still can be that pesky post-execve SIGTRAP!)
|
||||
* Handle it.
|
||||
*/
|
||||
if (trace_syscall(tcp) < 0) {
|
||||
/*
|
||||
* ptrace() failed in trace_syscall().
|
||||
* Likely a result of process disappearing mid-flight.
|
||||
* Observed case: exit_group() or SIGKILL terminating
|
||||
* all processes in thread group.
|
||||
* We assume that ptrace error was caused by process death.
|
||||
* We used to detach(tcp) here, but since we no longer
|
||||
* implement "detach before death" policy/hack,
|
||||
* we can let this process to report its death to us
|
||||
* normally, via WIFEXITED or WIFSIGNALED wait status.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
restart_tracee_with_sig_0:
|
||||
sig = 0;
|
||||
|
||||
restart_tracee:
|
||||
if (ptrace_restart(PTRACE_SYSCALL, tcp, sig) < 0) {
|
||||
/* Note: ptrace_restart emitted error message */
|
||||
exit_code = 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
@ -2429,8 +2484,25 @@ main(int argc, char *argv[])
|
||||
{
|
||||
init(argc, argv);
|
||||
|
||||
/* Run main tracing loop */
|
||||
trace();
|
||||
/*
|
||||
* Run main tracing loop.
|
||||
*
|
||||
* Used to be "while (nprocs != 0)", but in this testcase:
|
||||
* int main() { _exit(!!fork()); }
|
||||
* under strace -f, parent sometimes (rarely) manages
|
||||
* to exit before we see the first stop of the child,
|
||||
* and we are losing track of it:
|
||||
* 19923 clone(...) = 19924
|
||||
* 19923 exit_group(1) = ?
|
||||
* 19923 +++ exited with 1 +++
|
||||
* Waiting for ECHILD works better.
|
||||
* (However, if -o|logger is in use, we can't do that.
|
||||
* Can work around that by double-forking the logger,
|
||||
* but that loses the ability to wait for its completion on exit.
|
||||
* Oh well...)
|
||||
*/
|
||||
while (trace())
|
||||
;
|
||||
|
||||
cleanup();
|
||||
fflush(NULL);
|
||||
|
Loading…
x
Reference in New Issue
Block a user