407 lines
9.0 KiB
C
407 lines
9.0 KiB
C
|
/*-
|
||
|
* Copyright (c) 2011, 2012 Konstantin Belousov <kib@FreeBSD.org>
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 <sys/cdefs.h>
|
||
|
__FBSDID("$FreeBSD$");
|
||
|
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/ptrace.h>
|
||
|
#include <sys/sysctl.h>
|
||
|
#include <sys/wait.h>
|
||
|
#include <assert.h>
|
||
|
#include <errno.h>
|
||
|
#include <signal.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
#define TRACE ">>>> "
|
||
|
|
||
|
static const char *
|
||
|
decode_wait_status(int status)
|
||
|
{
|
||
|
static char c[128];
|
||
|
char b[32];
|
||
|
int first;
|
||
|
|
||
|
c[0] = '\0';
|
||
|
first = 1;
|
||
|
if (WIFCONTINUED(status)) {
|
||
|
first = 0;
|
||
|
strlcat(c, "CONT", sizeof(c));
|
||
|
}
|
||
|
if (WIFEXITED(status)) {
|
||
|
if (first)
|
||
|
first = 0;
|
||
|
else
|
||
|
strlcat(c, ",", sizeof(c));
|
||
|
snprintf(b, sizeof(b), "EXIT(%d)", WEXITSTATUS(status));
|
||
|
strlcat(c, b, sizeof(c));
|
||
|
}
|
||
|
if (WIFSIGNALED(status)) {
|
||
|
if (first)
|
||
|
first = 0;
|
||
|
else
|
||
|
strlcat(c, ",", sizeof(c));
|
||
|
snprintf(b, sizeof(b), "SIG(%s)", strsignal(WTERMSIG(status)));
|
||
|
strlcat(c, b, sizeof(c));
|
||
|
if (WCOREDUMP(status))
|
||
|
strlcat(c, ",CORE", sizeof(c));
|
||
|
}
|
||
|
if (WIFSTOPPED(status)) {
|
||
|
if (first)
|
||
|
first = 0;
|
||
|
else
|
||
|
strlcat(c, ",", sizeof(c));
|
||
|
snprintf(b, sizeof(b), "SIG(%s)", strsignal(WSTOPSIG(status)));
|
||
|
strlcat(c, b, sizeof(c));
|
||
|
}
|
||
|
return (c);
|
||
|
}
|
||
|
|
||
|
static const char *
|
||
|
decode_pl_flags(struct ptrace_lwpinfo *lwpinfo)
|
||
|
{
|
||
|
static char c[128];
|
||
|
static struct decode_tag {
|
||
|
int flag;
|
||
|
const char *desc;
|
||
|
} decode[] = {
|
||
|
{ PL_FLAG_SA, "SA" },
|
||
|
{ PL_FLAG_BOUND, "BOUND" },
|
||
|
{ PL_FLAG_SCE, "SCE" },
|
||
|
{ PL_FLAG_SCX, "SCX" },
|
||
|
{ PL_FLAG_EXEC, "EXEC" },
|
||
|
{ PL_FLAG_SI, "SI" },
|
||
|
{ PL_FLAG_FORKED, "FORKED" },
|
||
|
};
|
||
|
char de[32];
|
||
|
unsigned first, flags, i;
|
||
|
|
||
|
c[0] = '\0';
|
||
|
first = 1;
|
||
|
flags = lwpinfo->pl_flags;
|
||
|
for (i = 0; i < sizeof(decode) / sizeof(decode[0]); i++) {
|
||
|
if ((flags & decode[i].flag) != 0) {
|
||
|
if (first)
|
||
|
first = 0;
|
||
|
else
|
||
|
strlcat(c, ",", sizeof(c));
|
||
|
strlcat(c, decode[i].desc, sizeof(c));
|
||
|
flags &= ~decode[i].flag;
|
||
|
}
|
||
|
}
|
||
|
for (i = 0; i < sizeof(flags) * NBBY; i++) {
|
||
|
if ((flags & (1 << i)) != 0) {
|
||
|
if (first)
|
||
|
first = 0;
|
||
|
else
|
||
|
strlcat(c, ",", sizeof(c));
|
||
|
snprintf(de, sizeof(de), "<%d>", i);
|
||
|
strlcat(c, de, sizeof(c));
|
||
|
}
|
||
|
}
|
||
|
return (c);
|
||
|
}
|
||
|
|
||
|
static const char *
|
||
|
decode_pl_event(struct ptrace_lwpinfo *lwpinfo)
|
||
|
{
|
||
|
|
||
|
switch (lwpinfo->pl_event) {
|
||
|
case PL_EVENT_NONE:
|
||
|
return ("NONE");
|
||
|
|
||
|
case PL_EVENT_SIGNAL:
|
||
|
return ("SIG");
|
||
|
|
||
|
default:
|
||
|
return ("UNKNOWN");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
get_pathname(pid_t pid)
|
||
|
{
|
||
|
char pathname[PATH_MAX];
|
||
|
int error, name[4];
|
||
|
size_t len;
|
||
|
|
||
|
name[0] = CTL_KERN;
|
||
|
name[1] = KERN_PROC;
|
||
|
name[2] = KERN_PROC_PATHNAME;
|
||
|
name[3] = pid;
|
||
|
|
||
|
len = sizeof(pathname);
|
||
|
error = sysctl(name, 4, pathname, &len, NULL, 0);
|
||
|
if (error < 0) {
|
||
|
if (errno != ESRCH) {
|
||
|
fprintf(stderr, "sysctl kern.proc.pathname.%d: %s\n",
|
||
|
pid, strerror(errno));
|
||
|
return;
|
||
|
}
|
||
|
fprintf(stderr, "pid %d exited\n", pid);
|
||
|
return;
|
||
|
}
|
||
|
if (len == 0 || strlen(pathname) == 0) {
|
||
|
fprintf(stderr, "No cached pathname for process %d\n", pid);
|
||
|
return;
|
||
|
}
|
||
|
printf(TRACE "pid %d path %s\n", pid, pathname);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
wait_info(int pid, int status, struct ptrace_lwpinfo *lwpinfo)
|
||
|
{
|
||
|
|
||
|
printf(TRACE "pid %d wait %s", pid,
|
||
|
decode_wait_status(status));
|
||
|
if (lwpinfo != NULL) {
|
||
|
printf(" event %s flags %s",
|
||
|
decode_pl_event(lwpinfo), decode_pl_flags(lwpinfo));
|
||
|
}
|
||
|
printf("\n");
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
trace_sc(int pid)
|
||
|
{
|
||
|
struct ptrace_lwpinfo lwpinfo;
|
||
|
int status;
|
||
|
|
||
|
if (ptrace(PT_TO_SCE, pid, (caddr_t)1, 0) < 0) {
|
||
|
perror("PT_TO_SCE");
|
||
|
ptrace(PT_KILL, pid, NULL, 0);
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
if (waitpid(pid, &status, 0) == -1) {
|
||
|
perror("waitpid");
|
||
|
return (-1);
|
||
|
}
|
||
|
if (WIFEXITED(status) || WIFSIGNALED(status)) {
|
||
|
wait_info(pid, status, NULL);
|
||
|
return (-1);
|
||
|
}
|
||
|
assert(WIFSTOPPED(status));
|
||
|
assert(WSTOPSIG(status) == SIGTRAP);
|
||
|
|
||
|
if (ptrace(PT_LWPINFO, pid, (caddr_t)&lwpinfo, sizeof(lwpinfo)) < 0) {
|
||
|
perror("PT_LWPINFO");
|
||
|
ptrace(PT_KILL, pid, NULL, 0);
|
||
|
return (-1);
|
||
|
}
|
||
|
wait_info(pid, status, &lwpinfo);
|
||
|
assert(lwpinfo.pl_flags & PL_FLAG_SCE);
|
||
|
|
||
|
if (ptrace(PT_TO_SCX, pid, (caddr_t)1, 0) < 0) {
|
||
|
perror("PT_TO_SCX");
|
||
|
ptrace(PT_KILL, pid, NULL, 0);
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
if (waitpid(pid, &status, 0) == -1) {
|
||
|
perror("waitpid");
|
||
|
return (-1);
|
||
|
}
|
||
|
if (WIFEXITED(status) || WIFSIGNALED(status)) {
|
||
|
wait_info(pid, status, NULL);
|
||
|
return (-1);
|
||
|
}
|
||
|
assert(WIFSTOPPED(status));
|
||
|
assert(WSTOPSIG(status) == SIGTRAP);
|
||
|
|
||
|
if (ptrace(PT_LWPINFO, pid, (caddr_t)&lwpinfo, sizeof(lwpinfo)) < 0) {
|
||
|
perror("PT_LWPINFO");
|
||
|
ptrace(PT_KILL, pid, NULL, 0);
|
||
|
return (-1);
|
||
|
}
|
||
|
wait_info(pid, status, &lwpinfo);
|
||
|
assert(lwpinfo.pl_flags & PL_FLAG_SCX);
|
||
|
|
||
|
if (lwpinfo.pl_flags & PL_FLAG_EXEC)
|
||
|
get_pathname(pid);
|
||
|
|
||
|
if (lwpinfo.pl_flags & PL_FLAG_FORKED) {
|
||
|
printf(TRACE "forked child %d\n", lwpinfo.pl_child_pid);
|
||
|
return (lwpinfo.pl_child_pid);
|
||
|
}
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
trace_cont(int pid)
|
||
|
{
|
||
|
struct ptrace_lwpinfo lwpinfo;
|
||
|
int status;
|
||
|
|
||
|
if (ptrace(PT_CONTINUE, pid, (caddr_t)1, 0) < 0) {
|
||
|
perror("PT_CONTINUE");
|
||
|
ptrace(PT_KILL, pid, NULL, 0);
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
if (waitpid(pid, &status, 0) == -1) {
|
||
|
perror("waitpid");
|
||
|
return (-1);
|
||
|
}
|
||
|
if (WIFEXITED(status) || WIFSIGNALED(status)) {
|
||
|
wait_info(pid, status, NULL);
|
||
|
return (-1);
|
||
|
}
|
||
|
assert(WIFSTOPPED(status));
|
||
|
assert(WSTOPSIG(status) == SIGTRAP);
|
||
|
|
||
|
if (ptrace(PT_LWPINFO, pid, (caddr_t)&lwpinfo, sizeof(lwpinfo)) < 0) {
|
||
|
perror("PT_LWPINFO");
|
||
|
ptrace(PT_KILL, pid, NULL, 0);
|
||
|
return (-1);
|
||
|
}
|
||
|
wait_info(pid, status, &lwpinfo);
|
||
|
|
||
|
if ((lwpinfo.pl_flags & (PL_FLAG_EXEC | PL_FLAG_SCX)) ==
|
||
|
(PL_FLAG_EXEC | PL_FLAG_SCX))
|
||
|
get_pathname(pid);
|
||
|
|
||
|
if ((lwpinfo.pl_flags & (PL_FLAG_FORKED | PL_FLAG_SCX)) ==
|
||
|
(PL_FLAG_FORKED | PL_FLAG_SCX)) {
|
||
|
printf(TRACE "forked child %d\n", lwpinfo.pl_child_pid);
|
||
|
return (lwpinfo.pl_child_pid);
|
||
|
}
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static int trace_syscalls = 1;
|
||
|
|
||
|
static int
|
||
|
trace(pid_t pid)
|
||
|
{
|
||
|
|
||
|
return (trace_syscalls ? trace_sc(pid) : trace_cont(pid));
|
||
|
}
|
||
|
|
||
|
|
||
|
int
|
||
|
main(int argc, char *argv[])
|
||
|
{
|
||
|
struct ptrace_lwpinfo lwpinfo;
|
||
|
int c, status, use_vfork;
|
||
|
pid_t pid, pid1;
|
||
|
|
||
|
trace_syscalls = 1;
|
||
|
use_vfork = 0;
|
||
|
while ((c = getopt(argc, argv, "csv")) != -1) {
|
||
|
switch (c) {
|
||
|
case 'c':
|
||
|
trace_syscalls = 0;
|
||
|
break;
|
||
|
case 's':
|
||
|
trace_syscalls = 1;
|
||
|
break;
|
||
|
case 'v':
|
||
|
use_vfork = 1;
|
||
|
break;
|
||
|
default:
|
||
|
case '?':
|
||
|
fprintf(stderr, "Usage: %s [-c] [-s] [-v]\n", argv[0]);
|
||
|
return (2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ((pid = fork()) < 0) {
|
||
|
perror("fork");
|
||
|
return 1;
|
||
|
}
|
||
|
else if (pid == 0) {
|
||
|
if (ptrace(PT_TRACE_ME, 0, NULL, 0) < 0) {
|
||
|
perror("PT_TRACE_ME");
|
||
|
_exit(1);
|
||
|
}
|
||
|
kill(getpid(), SIGSTOP);
|
||
|
getpid();
|
||
|
if ((pid1 = use_vfork ? vfork() : fork()) < 0) {
|
||
|
perror("fork1");
|
||
|
return (1);
|
||
|
} else if (pid1 == 0) {
|
||
|
printf("Hi from child %d\n", getpid());
|
||
|
execl("/bin/ls", "ls", "/", (char *)NULL);
|
||
|
}
|
||
|
}
|
||
|
else { /* parent */
|
||
|
if (waitpid(pid, &status, 0) == -1) {
|
||
|
perror("waitpid");
|
||
|
return (-1);
|
||
|
}
|
||
|
assert(WIFSTOPPED(status));
|
||
|
assert(WSTOPSIG(status) == SIGSTOP);
|
||
|
|
||
|
if (ptrace(PT_LWPINFO, pid, (caddr_t)&lwpinfo,
|
||
|
sizeof(lwpinfo)) < 0) {
|
||
|
perror("PT_LWPINFO");
|
||
|
ptrace(PT_KILL, pid, NULL, 0);
|
||
|
return (-1);
|
||
|
}
|
||
|
wait_info(pid, status, &lwpinfo);
|
||
|
|
||
|
if (ptrace(PT_FOLLOW_FORK, pid, 0, 1) < 0) {
|
||
|
perror("PT_FOLLOW_FORK");
|
||
|
ptrace(PT_KILL, pid, NULL, 0);
|
||
|
return (2);
|
||
|
}
|
||
|
|
||
|
while ((pid1 = trace(pid)) >= 0) {
|
||
|
if (pid1 != 0) {
|
||
|
printf(TRACE "attached to pid %d\n", pid1);
|
||
|
#if 0
|
||
|
kill(pid1, SIGCONT);
|
||
|
#endif
|
||
|
if (waitpid(pid1, &status, 0) == -1) {
|
||
|
perror("waitpid");
|
||
|
return (-1);
|
||
|
}
|
||
|
printf(TRACE "nested loop, pid %d status %s\n",
|
||
|
pid1, decode_wait_status(status));
|
||
|
assert(WIFSTOPPED(status));
|
||
|
assert(WSTOPSIG(status) == SIGSTOP);
|
||
|
if (ptrace(PT_LWPINFO, pid1, (caddr_t)&lwpinfo,
|
||
|
sizeof(lwpinfo)) < 0) {
|
||
|
perror("PT_LWPINFO");
|
||
|
ptrace(PT_KILL, pid1, NULL, 0);
|
||
|
return (-1);
|
||
|
}
|
||
|
wait_info(pid1, status, &lwpinfo);
|
||
|
|
||
|
while (trace(pid1) >= 0)
|
||
|
;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ptrace(PT_CONTINUE, pid, (caddr_t)1, 0);
|
||
|
}
|
||
|
return (0);
|
||
|
}
|