Created
January 24, 2011 02:11
-
-
Save jesstess/792703 to your computer and use it in GitHub Desktop.
A minimal tracer. Attach to a running process or run a process under it. Step through instructions or syscalls. Generate instruction and syscall summaries.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* A minimal tracer. You can: | |
* - attach to a running process or run a process under it. | |
* - step through instructions. | |
* - step through syscalls. | |
* - generate a syscall summary. | |
* - count and time the number of instructions executed. | |
* | |
* If you are generating a summary with 'ci' or 'cs', send SIGTSTP (Ctl-Z) to | |
* pause the traced process and dump a summary (useful if you've attached to a | |
* long-running process to sample briefly. Afterwards, type 'q' to detach and | |
* let the traced process resume execution untraced). | |
* | |
* Example invocations: | |
* ./tracer hello | |
* sudo ./tracer -p 12345 | |
* | |
* Inspired by the DIY strace at | |
* http://blog.nelhage.com/2010/08/write-yourself-an-strace-in-70-lines-of-code/ | |
* and the DIY debugger at | |
* http://eli.thegreenplace.net/2011/01/23/how-debuggers-work-part-1/ | |
*/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <signal.h> | |
#include <time.h> | |
#include <sys/ptrace.h> | |
#include <sys/reg.h> /* EAX, ORIG_EAX */ | |
#include <sys/user.h> | |
/* Checking /usr/include/asm/unistd_64.h and /usr/include/asm/unistd_32.h on | |
this machine, the highest number is 337. Good enough for this toy program. */ | |
#define MAX_SYSCALL_NUM 337 | |
/* Signal numbers are listed in /usr/include/asm/signal.h. */ | |
int sigtstp_received = 0; | |
/* Use SIGTSTP (Ctl-Z) to say "stop gathering statistics and print what you | |
have". */ | |
void sigtstp_handler(int sig) { | |
printf("Received interrupt %d.\n", sig); | |
sigtstp_received = 1; | |
} | |
int run_target(const char* programname) | |
{ | |
printf("Tracing '%s'.\n", programname); | |
/* From the man page: Indicates that this process is to be traced by its | |
parent. Any signal (except SIGKILL) delivered to this process will cause | |
it to stop and its parent to be notified via wait(2). Also, all | |
subsequent calls to execve(2) by this process will cause a SIGTRAP to be | |
sent to it, giving the parent a chance to gain control before the new | |
program begins execution. */ | |
if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) { | |
perror("ptrace"); | |
return 1; | |
} | |
return execl(programname, programname, 0, NULL); | |
} | |
int run_debugger(pid_t child_pid) | |
{ | |
int wait_status; | |
char command[20]; | |
printf("Starting debugger.\n"); | |
/* Wait for child to stop on its first instruction. */ | |
wait(&wait_status); | |
while (1) { | |
/* Give us a prompt. */ | |
fputs("> ", stdout); | |
fflush(stdout); | |
fgets(command, sizeof(command), stdin); | |
if (!(strncmp(command, "q", 1))) { | |
/* Detach from the traced process so our exiting doesn't kill it. */ | |
ptrace(PTRACE_DETACH, child_pid, NULL, NULL); | |
return 0; | |
} else if (!(strncmp(command, "ci", 2))) { | |
/* I wanted to combine the syscall summary and the instruction summary | |
in 1 run, but when using PTRACE_SINGLESTEP, even with | |
PTRACE_O_TRACESYSGOOD set I couldn't get the status to have bits set | |
to distinguish syscalls from other traps. */ | |
/* Count the number of instructions, and time how long it takes to | |
step through the program. */ | |
(void) signal(SIGTSTP, sigtstp_handler); | |
time_t start_time, end_time; | |
time(&start_time); | |
long long counter = 0; | |
int syscall; | |
while (WIFSTOPPED(wait_status) && !sigtstp_received) { | |
counter += 1; | |
if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) { | |
perror("ptrace"); | |
return 1; | |
} | |
wait(&wait_status); | |
} | |
time(&end_time); | |
printf("\n%lld instructions executed in %d seconds.\n", counter, | |
(int)(end_time - start_time)); | |
sigtstp_received = 0; /* Reset so we can keep tracing. */ | |
} else if (!strncmp(command, "cs", 2)) { | |
/* Print the syscalls and their return values. At the end, summarize | |
the syscalls. */ | |
(void) signal(SIGTSTP, sigtstp_handler); | |
int syscall_counts[MAX_SYSCALL_NUM] = {0}; | |
long long counter = 0; | |
while (WIFSTOPPED(wait_status) && !sigtstp_received) { | |
if (ptrace(PTRACE_SYSCALL, child_pid, 0, 0) < 0) { | |
perror("ptrace"); | |
return 1; | |
} | |
wait(&wait_status); | |
int syscall = ptrace(PTRACE_PEEKUSER, child_pid, sizeof(long)*ORIG_EAX); | |
if (ptrace(PTRACE_SYSCALL, child_pid, 0, 0) < 0) { | |
perror("ptrace"); | |
return 1; | |
} | |
wait(&wait_status); | |
int retval = ptrace(PTRACE_PEEKUSER, child_pid, sizeof(long)*EAX); | |
printf("syscall(%d) = %d\n", syscall, retval); | |
counter += 1; | |
syscall_counts[syscall] = syscall_counts[syscall] + 1; | |
} | |
/* Print a syscall summary. */ | |
printf("\n%lld syscalls performed.\n", counter); | |
printf("Counts by syscall number:\n"); | |
printf("=========================\n"); | |
int i; | |
for (i = 0; i < MAX_SYSCALL_NUM; i++) { | |
if (syscall_counts[i] > 0) { | |
printf("syscall(%d): %d\n", i, syscall_counts[i]); | |
} | |
} | |
sigtstp_received = 0; /* Reset so we can keep tracing. */ | |
} else if (!(strncmp(command, "i", 1))) { | |
/* Step to the next instruction, and print the instruction pointer | |
and instruction. */ | |
if (ptrace(PTRACE_SINGLESTEP, child_pid, 0, 0) < 0) { | |
perror("ptrace"); | |
return 1; | |
} | |
wait(&wait_status); | |
struct user_regs_struct regs; | |
ptrace(PTRACE_GETREGS, child_pid, 0, ®s); | |
long instr = ptrace(PTRACE_PEEKTEXT, child_pid, regs.eip, 0); | |
printf("EIP = %08lx, instr = %08lx\n", regs.eip, instr); | |
} else if (!(strncmp(command, "s", 1))) { | |
/* Stop at the next syscall and print the syscall number and return | |
value. With PTRACE_SYSCALL set, we'll stop upon entry, allowing | |
us to get the syscall number, and upon exit, allowing us to get | |
the return value. */ | |
if (ptrace(PTRACE_SYSCALL, child_pid, 0, 0) < 0) { | |
perror("ptrace"); | |
return 1; | |
} | |
wait(&wait_status); | |
int syscall = ptrace(PTRACE_PEEKUSER, child_pid, sizeof(long)*ORIG_EAX); | |
if (ptrace(PTRACE_SYSCALL, child_pid, 0, 0) < 0) { | |
perror("ptrace"); | |
return 1; | |
} | |
wait(&wait_status); | |
int retval = ptrace(PTRACE_PEEKUSER, child_pid, sizeof(long)*EAX); | |
printf("syscall(%d) = %d\n", syscall, retval); | |
} else { | |
printf("Your options are:\n"); | |
printf(" q: detach and quit\n"); | |
printf(" i: step instruction\n"); | |
printf(" s: step syscall\n"); | |
printf(" ci: count instructions\n"); | |
printf(" cs: count syscalls\n"); | |
} | |
if (WIFEXITED(wait_status)) { | |
printf("Done!\n"); | |
return 0; | |
} | |
} | |
} | |
int fork_and_trace(char *programname) { | |
pid_t child_pid = fork(); | |
if (child_pid == 0) | |
return run_target(programname); | |
else if (child_pid > 0) | |
return run_debugger(child_pid); | |
else { | |
perror("fork"); | |
return 1; | |
} | |
} | |
int attach_and_trace(int pid) { | |
ptrace(PTRACE_ATTACH, pid, NULL, NULL); | |
return run_debugger(pid); | |
} | |
void usage(char *name) { | |
fprintf(stderr, "Usage: %s [PROGRAMENAME | -p PID]\n", name); | |
} | |
int main(int argc, char** argv) | |
{ | |
if (argc < 2) { | |
usage(argv[0]); | |
return 1; | |
} | |
if(argv[1][0] == '-') { | |
switch(argv[1][1]) { | |
case 'h': | |
usage(argv[0]); | |
return 1; | |
case 'p': | |
/* Attach to and trace running program. */ | |
return attach_and_trace(atoi(argv[2])); | |
default: | |
usage(argv[0]); | |
return 1; | |
} | |
} else { | |
/* Run and trace program. */ | |
return fork_and_trace(argv[1]); | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment