Friday, January 06, 2006

Darwin, ptrace(), and registers

Most Unix systems have a ptrace() system call which can be used to trace a process. This call is commonly used by debuggers to peek and poke in another process's memory. Darwin (the Unix foundation of Mac OS X) does offer a ptrace() call, but it's not as robust as on other platforms.

For example, on Linux I can read the registers of a target process using pseudo-code like:


struct user regs;
ptrace(PTRACE_ATTACH, pid, NULL, NULL);
ptrace(PTRACE_GETREGS, pid, NULL, ®s);

The problem is that Darwin doesn't offer a PTRACE_GETREGS equivalent. Darwin does, however, offer similar functionality through the use of some Mach API calls. Below is a sample program that takes a PID as an argument, and prints out some of the register values for its first Mach thread.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <mach/mach.h>

int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: getregs \n");
exit(EXIT_FAILURE);
}

pid_t pid = atoi(argv[1]);
mach_port_t task;
kern_return_t err;

/* Get the mach task for the target process */
err = task_for_pid(mach_task_self(), pid, &task);
if (err != KERN_SUCCESS) {
fprintf(stderr, "task_for_pid() failed\n");
exit(EXIT_FAILURE);
}

/* Suspend the target process */
err = task_suspend(task);
if (err != KERN_SUCCESS) {
fprintf(stderr, "task_suspend() failed\n");
exit(EXIT_FAILURE);
}

/* Get all threads in the specified task */
thread_act_port_array_t threadList;
mach_msg_type_number_t threadCount;
err = task_threads(task, &threadList, &threadCount);
if (err != KERN_SUCCESS) {
fprintf(stderr, "task_threads() failed\n");
exit(EXIT_FAILURE);
}
assert(threadCount > 0);

/* Get the thread state for the first thread */
ppc_thread_state_t state;
mach_msg_type_number_t stateCount;
stateCount = PPC_THREAD_STATE_COUNT;
err = thread_get_state(threadList[0],
PPC_THREAD_STATE,
(thread_state_t)&state,
&stateCount);
if (err != KERN_SUCCESS) {
fprintf(stderr, "thread_get_state() failed\n");
exit(EXIT_FAILURE);
}

/* Print some register values */
assert(stateCount == PPC_THREAD_STATE_COUNT);
printf("pc = %p\n", state.srr0);
printf("r0 = %p\n", state.r0);
printf("r1 = %p\n", state.r1);
printf("r2 = %p\n", state.r2);
printf("r3 = %p\n", state.r3);
printf("r4 = %p\n", state.r4);

/* Resume the task (process) */
err = task_resume(task);
if (err != KERN_SUCCESS) {
fprintf(stderr, "task_resume() failed\n");
exit(EXIT_FAILURE);
}

return EXIT_SUCCESS;
}


I haven't really tested the above program thoroughly, so it's possible it has some bugs. But regardless, I think the general idea is sound.

1 comment:

The Free Meme said...

Very cool. Is the definition of the state in mach.h generic or cpu-specific? In other words, is the code portable to an intel-based mac? (my wife has a PPC-based one, I guess I will snatch it and poke around the system header files for clues :))
Cheerios!