Skip to content

Instantly share code, notes, and snippets.

@jesstess
Created January 24, 2011 02:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jesstess/792703 to your computer and use it in GitHub Desktop.
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.
/*
* 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, &regs);
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