xnu: change logic handle exception

instead of a thread waiting for incoming messages, i've implemented
wait functionality above mach_msg, now i have to make it work
with r2 and avoid while (1) in the code, understand better the
references and start thinking about edge cases
This commit is contained in:
Álvaro Felipe Melchor 2016-02-02 20:27:57 +01:00
parent 29d8456013
commit feea7f4d17
4 changed files with 184 additions and 119 deletions

View File

@ -271,9 +271,9 @@ static int r_debug_native_wait (RDebug *dbg, int pid) {
if (pid == -1) {
status = R_DEBUG_REASON_UNKNOWN;
} else {
#if __APPLE__ && (__arm__ || __arm64__ || __aarch64__)
#if __APPLE__
// eprintf ("No waitpid here :D\n");
status = R_DEBUG_REASON_UNKNOWN;
status = xnu_wait (dbg, pid);
#else
// XXX: this is blocking, ^C will be ignored
int ret = waitpid (pid, &status, 0);

View File

@ -18,7 +18,7 @@
#include <mach/mach_host.h>
#include <mach/host_priv.h>
static task_t task_dbg = -1;
static task_t task_dbg = 0;
#include "xnu_debug.h"
#include "xnu_threads.c"
#if XNU_USE_EXCTHR
@ -107,6 +107,10 @@ static task_t task_for_pid_workaround(int Pid) {
return 0;
}
int xnu_wait(RDebug *dbg, int pid) {
return __xnu_wait (dbg, pid);
}
bool xnu_step(RDebug *dbg) {
#if XNU_USE_PTRACE
int ret = ptrace (PT_STEP, dbg->pid, (caddr_t)1, 0) == 0; //SIGINT
@ -151,14 +155,6 @@ int xnu_attach(RDebug *dbg, int pid) {
eprintf ("error setting up exception thread\n");
return -1;
}
//task_suspend (pid_to_task (pid));
#if 0
if (ptrace (PT_ATTACHEXC, pid, 0, 0) == -1) {
perror ("ptrace (PT_ATTACHEXC)");
return -1;
}
usleep(250000);
#endif
return pid;
#endif
}
@ -177,7 +173,7 @@ int xnu_detach(RDebug *dbg, int pid) {
__FILE__, __LINE__);
}
//we mark the task as not longer available since we deallocated the ref
task_dbg = -2;
task_dbg = 0;
r_list_free (dbg->threads);
#endif
}
@ -192,6 +188,7 @@ int xnu_continue(RDebug *dbg, int pid, int tid, int sig) {
task_t task = pid_to_task (pid);
if (!task)
return false;
//TODO free refs count threads
xnu_thread_t *th = get_xnu_thread (dbg, getcurthread (dbg));
if (!th) {
eprintf ("failed to get thread in xnu_continue\n");
@ -394,15 +391,23 @@ int xnu_map_protect (RDebug *dbg, ut64 addr, int size, int perms) {
task_t pid_to_task (int pid) {
static int old_pid = -1;
kern_return_t kr;
task_t task = -1;
int err;
/* it means that we are done with the task*/
if (task_dbg == -2)
return 0;
if (task_dbg != -1 && old_pid == pid)
if (task_dbg != 0 && old_pid == pid) {
return task_dbg;
} else if (task_dbg != 0 && old_pid != pid) {
//we changed the process pid so deallocate a ref from the old_task
//since we are going to get a new task
kr = mach_port_deallocate (mach_task_self (), task_dbg);
if (kr != KERN_SUCCESS) {
eprintf ("fail to deallocate port %s:%d\n", __FILE__, __LINE__);
return 0;
}
}
err = task_for_pid (mach_task_self (), (pid_t)pid, &task);
if ((err != KERN_SUCCESS) || !MACH_PORT_VALID (task)) {
task = task_for_pid_workaround (pid);

View File

@ -228,9 +228,11 @@ static bool xnu_restore_exception_ports (int pid) {
return true;
}
//TODO review more closely we are failing here
static void encode_reply(mig_reply_error_t *reply, mach_msg_header_t *hdr, int code) {
mach_msg_header_t *rh = &reply->Head;
rh->msgh_bits = MACH_MSGH_BITS (MACH_MSGH_BITS_REMOTE (hdr->msgh_bits), 0);
rh->msgh_bits = MACH_MSGH_BITS (MACH_MSGH_BITS_REMOTE(hdr->msgh_bits), 0);
rh->msgh_remote_port = hdr->msgh_remote_port;
rh->msgh_size = (mach_msg_size_t) sizeof (mig_reply_error_t);
rh->msgh_local_port = MACH_PORT_NULL;
@ -266,7 +268,7 @@ static void decode_exception_type(int exception) {
}
static bool validate_mach_message (RDebug *dbg, exc_msg *msg) {
kern_return_t kret;
kern_return_t kr;
/*check if the message is for us*/
if (msg->hdr.msgh_local_port != ex.exception_port)
return false;
@ -290,8 +292,8 @@ static bool validate_mach_message (RDebug *dbg, exc_msg *msg) {
msg->NDR.float_rep != NDR_record.float_rep)
return false;
/*we got new rights to the task, get rid of it.*/
kret = mach_port_deallocate (mach_task_self (), msg->task.name);
if (kret != KERN_SUCCESS) {
kr = mach_port_deallocate (mach_task_self (), msg->task.name);
if (kr != KERN_SUCCESS) {
eprintf ("failed to deallocate task port %s-%d\n",
__FILE__, __LINE__);
}
@ -299,8 +301,8 @@ static bool validate_mach_message (RDebug *dbg, exc_msg *msg) {
//we receive a exception from an unknown process this could
//happen if the child fork, as the created process will inherit
//its exception port
kret = mach_port_deallocate (mach_task_self (), msg->thread.name);
if (kret != KERN_SUCCESS) {
kr = mach_port_deallocate (mach_task_self (), msg->thread.name);
if (kr != KERN_SUCCESS) {
eprintf ("failed to deallocated task port %s-%d\n",
__FILE__, __LINE__);
}
@ -309,61 +311,97 @@ static bool validate_mach_message (RDebug *dbg, exc_msg *msg) {
return true;
}
static bool handle_exception_message (RDebug *dbg, exc_msg *msg) {
kern_return_t kret;
decode_exception_type (msg->exception);
kret = mach_port_deallocate (mach_task_self (), msg->thread.name);
if (kret != KERN_SUCCESS) {
kern_return_t kr;
int ret = R_DEBUG_REASON_UNKNOWN;
switch (msg->exception) {
case EXC_BAD_ACCESS:
eprintf ("EXC_BAD_ACCESS\n");
break;
case EXC_BAD_INSTRUCTION:
eprintf ("EXC_BAD_INSTRUCTION\n");
break;
case EXC_ARITHMETIC:
eprintf ("EXC_ARITHMETIC\n");
break;
case EXC_EMULATION:
eprintf ("EXC_EMULATION\n");
break;
case EXC_SOFTWARE:
eprintf ("EXC_SOFTWARE\n");
break;
case EXC_BREAKPOINT:
ret = R_DEBUG_REASON_BREAKPOINT;
eprintf ("EXC_BREAKPOINT\n");
break;
default:
eprintf ("UNKNOWN\n");
break;
}
kr = mach_port_deallocate (mach_task_self (), msg->thread.name);
if (kr != KERN_SUCCESS) {
eprintf ("failed to deallocated task port %s-%d\n",
__FILE__, __LINE__);
}
return KERN_SUCCESS;
return ret;
}
static void *xnu_exception_thread (void *arg) {
static int __xnu_wait (RDebug *dbg, int pid) {
// here comes the important thing
RDebug *dbg;
kern_return_t kret;
kern_return_t kr;
int reason = R_DEBUG_REASON_UNKNOWN;
mig_reply_error_t reply;
bool ret;
exc_msg msg;
if (!arg)
if (!dbg)
return NULL;
dbg = (RDebug *)arg;
for (;;) {
//wait for a incoming messages
//XXX what to do on failure; do we continue processing or stop ?
//XXX some layer for error handling and review ports leak
kret = mach_msg (&msg.hdr, MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0,
sizeof (exc_msg), ex.exception_port, 0,
MACH_PORT_NULL);
if (kret != KERN_SUCCESS) {
eprintf ("fail to retrieve message exception thread\n");
kr = mach_msg (
&msg.hdr,
MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0,
sizeof (exc_msg), ex.exception_port, 1, MACH_PORT_NULL);
if (kr == MACH_RCV_INTERRUPTED ) {
eprintf ("message interrupted\n");
break;
}
if (kr == MACH_RCV_TIMED_OUT) {
eprintf ("message timed out\n");
break;
}
if (kr == MACH_MSG_SUCCESS) {
eprintf ("message interrupted\n");
break;
}
eprintf ("Received exception\n");
ret = validate_mach_message (dbg, &msg);
if (!ret || msg.hdr.msgh_id != 2405 || msg.hdr.msgh_id != 2401) {
if (!ret && (msg.hdr.msgh_id != 2405 || msg.hdr.msgh_id != 2401)) {
encode_reply (&reply, &msg.hdr, KERN_FAILURE);
kret = mach_msg (&reply.Head, MACH_SEND_MSG | MACH_SEND_INTERRUPT,
kr = mach_msg (&reply.Head, MACH_SEND_MSG | MACH_SEND_INTERRUPT,
reply.Head.msgh_size, 0,
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (kret != KERN_SUCCESS)
eprintf ("failed to reply mach_msg %s-%d\n", __FILE__, __LINE__);
if (kr != KERN_SUCCESS)
eprintf ("failed to reply mach_msg %s:%d\n", __FILE__, __LINE__);
continue;
}
kret = handle_exception_message (dbg, &msg);
if (kret == KERN_FAILURE)
eprintf ("failed to handle exception");
encode_reply (&reply, &msg.hdr, kret);
kret = mach_msg (&reply.Head, MACH_SEND_MSG | MACH_SEND_INTERRUPT,
reply.Head.msgh_size, 0,
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (kret != KERN_SUCCESS)
eprintf ("failed to reply mach_msg %s-%d\n", __FILE__, __LINE__);
reason = handle_exception_message (dbg, &msg);
if (reason == R_DEBUG_REASON_BREAKPOINT) {
encode_reply (&reply, &msg.hdr, KERN_SUCCESS);
kr = mach_msg (&reply.Head, MACH_SEND_MSG | MACH_SEND_INTERRUPT,
reply.Head.msgh_size, 0,
MACH_PORT_NULL, 0,
MACH_PORT_NULL);
eprintf ("REPLIED\n");
if (kr != MACH_MSG_SUCCESS)
eprintf ("failed to reply mach_msg %s:%d\n", __FILE__, __LINE__);
}
break;
}
return NULL;
return ret;
}
@ -404,12 +442,14 @@ bool xnu_create_exception_thread(RDebug *dbg) {
EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES,
THREAD_STATE_NONE);
RETURN_ON_MACH_ERROR ("error to set port to receive exceptions\n", R_FALSE);
#if 0
// Create the exception thread
ret = pthread_create (&ex.thread, NULL, &xnu_exception_thread, dbg);
if (ret) {
perror ("pthread_create");
return false;
}
#endif
ex.exception_port = exception_port;
return true;
}

View File

@ -115,6 +115,7 @@ static task_t pid_to_task(int pid) {
}
}
old_task = task;
old_pid = pid;
return task;
}
@ -186,7 +187,7 @@ static int __read(RIO *io, RIODesc *fd, ut8 *buf, int len) {
}
copied = getNextValid(io, fd, io->off) - io->off;
if (copied<0) copied = 0;
if (copied < 0) copied = 0;
while (copied < len) {
blen = R_MIN ((len - copied), blocksize);
@ -225,95 +226,111 @@ static int __read(RIO *io, RIODesc *fd, ut8 *buf, int len) {
return len;
}
static vm_address_t tsk_getpagebase(ut64 addr) {
vm_address_t a = addr;
a >>= 12;
a <<= 12;
return a;
static int tsk_getperm(RIO *io, task_t task, vm_address_t addr) {
kern_return_t kr;
mach_port_t object;
int prot;
if (io->bits == 32) {
vm_region_flavor_t flavor = VM_REGION_BASIC_INFO;
mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
vm_region_basic_info_data_t info;
mach_vm_size_t vmsize;
kr = mach_vm_region (task, &addr, &vmsize, flavor, (vm_region_info_t)&info, &info_count, &object);
return (kr != KERN_SUCCESS ? 0 : info.protection);
} else {
mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
vm_region_flavor_t flavor = VM_REGION_BASIC_INFO_64;
vm_region_basic_info_data_64_t info;
vm_size_t vmsize;
kr = vm_region_64 (task, &addr, &vmsize, flavor, (vm_region_info_t)&info, &info_count, &object);
return (kr != KERN_SUCCESS ? 0 : info.protection);
}
}
static int tsk_getperm(task_t task, vm_address_t addr) {
vm_size_t pagesize = 1;
int _basic64[VM_REGION_BASIC_INFO_COUNT_64];
vm_region_basic_info_64_t basic64 = (vm_region_basic_info_64_t)_basic64;
mach_msg_type_number_t infocnt = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t objname;
kern_return_t rc;
rc = vm_region_64 (task, &addr, &pagesize, VM_REGION_BASIC_INFO,
(vm_region_info_t)basic64, &infocnt, &objname);
if (rc == KERN_SUCCESS) {
return basic64[0].protection;
}
return 0;
}
static int tsk_pagesize(RIO *io, int len) {
#if __arm__ || __arm64__ || __aarch64__
int is_arm64 = (io && io->bits == 64);
int pagesize = is_arm64? 16384: 4096;
#else
int pagesize = getpagesize();
#endif
if (pagesize<1) pagesize = 4096;
if (len > pagesize) {
pagesize *= (1 + (len / pagesize));
}
static int tsk_pagesize(RIOMach *riom) {
//cache the pagesize
static ut64 pagesize = 0;
kern_return_t kr;
task_vm_info_data_t task_vm_info;
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
if (pagesize)
return pagesize;
kr = task_info (riom->task, TASK_VM_INFO, (task_info_t)&task_vm_info, &count);
if (kr != KERN_SUCCESS)
perror ("task_info");
pagesize = task_vm_info.page_size;
return pagesize;
}
static vm_address_t tsk_getpagebase(RIOMach *riom, ut64 addr) {
vm_address_t pagesize = tsk_pagesize (riom);
return (addr & ~(pagesize - 1));
}
static bool tsk_setperm(RIO *io, task_t task, vm_address_t addr, int len, int perm) {
mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
vm_region_flavor_t flavor = VM_REGION_BASIC_INFO;
vm_address_t region = (vm_address_t)addr;
vm_region_basic_info_data_t info;
vm_size_t region_size = tsk_pagesize(io, len);
#if 1
task_t t;
vm_region_64 (task, &region, &region_size, flavor, (vm_region_info_t)&info,
(mach_msg_type_number_t*)&info_count, (mach_port_t*)&t);
#endif
return vm_protect (task, region, region_size, FALSE, perm) == KERN_SUCCESS;
kern_return_t kr;
kr = vm_protect (task, addr, len, 0, perm);
if (kr != KERN_SUCCESS) {
eprintf ("failed to change perm %s:%d\n", __FILE__, __LINE__);
perror ("tsk_setperm");
return false;
}
return true;
}
static bool tsk_write(task_t task, vm_address_t addr, const ut8 *buf, int len) {
kern_return_t kr;
mach_msg_type_number_t _len = len;
vm_offset_t _buf = (vm_offset_t)buf;
return vm_write (task, addr, _buf, _len) == KERN_SUCCESS;
unsigned int count = 0;
kr = mach_port_get_refs (mach_task_self(), task, MACH_PORT_RIGHT_SEND, &count);
if (kr != KERN_SUCCESS)
perror ("get refs");
eprintf ("refs = %d\n", count);
kr = vm_write (task, addr, _buf, _len);
if (kr != KERN_SUCCESS)
//the memory is not mapped
return false;
return true;
}
static int mach_write_at(RIO *io, RIOMach *riom, const void *buf, int len, ut64 addr) {
vm_address_t vaddr = addr;
vm_address_t pageaddr;
vm_size_t pagesize;
vm_size_t total_size;
int operms = 0;
task_t task;
if (!riom || len <1) {
if (!riom || len < 1) {
return 0;
}
task = riom->task;
pageaddr = tsk_getpagebase (riom, addr);
pagesize = tsk_pagesize (riom);
if (len > pagesize)
total_size = pagesize * (1 + (len / pagesize));
else
total_size = pagesize;
pageaddr = tsk_getpagebase (addr);
pagesize = tsk_pagesize (io, len);
if (tsk_write (task, vaddr, buf, len)) {
if (tsk_write (task, vaddr, buf, len))
return len;
}
operms = tsk_getperm (task, pageaddr);
if (!tsk_setperm (io, task, pageaddr, pagesize, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY)) {
perror ("setperm");
operms = tsk_getperm (io, task, pageaddr);
if (!tsk_setperm (io, task, pageaddr, total_size, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE)) {
eprintf ("io.mach: Cannot set page perms for %d bytes at 0x%08"
PFMT64x"\n", (int)pagesize, (ut64)pageaddr);
//return -1;
return -1;
}
if (!tsk_write (task, vaddr, buf, len)) {
perror ("write");
eprintf ("io.mach: Cannot write on memory\n");
len = -1;
}
if (operms) {
if (!tsk_setperm (io, task, pageaddr, pagesize, operms)) {
if (!tsk_setperm (io, task, pageaddr, total_size, operms)) {
eprintf ("io.mach: Cannot restore page perms\n");
return -1;
}
@ -377,13 +394,12 @@ static RIODesc *__open(RIO *io, const char *file, int rw, int mode) {
riom->pid = pid;
riom->task = task;
// sleep 1s to get proper path (program name instead of ls) (racy)
if (pid == 0) {
if (!pid)
pidpath = strdup ("kernel");
} else {
else
pidpath = r_sys_pid_to_path (pid);
}
ret = r_io_desc_new (&r_io_plugin_mach, riom->pid,
pidpath, rw | R_IO_EXEC, mode, riom);
pidpath, rw | R_IO_EXEC, mode, riom);
free (pidpath);
return ret;
}
@ -404,19 +420,22 @@ static ut64 __lseek(RIO *io, RIODesc *fd, ut64 offset, int whence) {
}
static int __close(RIODesc *fd) {
int pid = RIOMACH_PID (fd->data);
RIOMach *riom= (RIOMach*)fd->data;
kern_return_t kr;
kr = mach_port_deallocate (mach_task_self (), riom->task);
if (kr != KERN_SUCCESS)
perror ("__close io_mach");
R_FREE (fd->data);
return ptrace (PT_DETACH, pid, 0, 0);
return kr == KERN_SUCCESS;
}
static int __system(RIO *io, RIODesc *fd, const char *cmd) {
RIOMach *riom = (RIOMach*)fd->data;
//printf("ptrace io command (%s)\n", cmd);
/* XXX ugly hack for testing purposes */
if (!strncmp (cmd, "perm", 4)) {
int perm = r_str_rwx (cmd+4);
int perm = r_str_rwx (cmd + 4);
if (perm) {
int pagesize = tsk_pagesize(io, 1);
int pagesize = tsk_pagesize(riom);
tsk_setperm (io, riom->task, io->off, pagesize, perm);
} else {
eprintf ("Usage: =!perm [rwx]\n");
@ -447,8 +466,9 @@ static int __system(RIO *io, RIODesc *fd, const char *cmd) {
}
}
eprintf ("io_mach_system: Invalid pid %d\n", pid);
} else
} else {
eprintf ("Try: '=!pid' or '=!perm'\n");
}
return 1;
}