This is dyld_debug.c in view mode; [Download] [Up]
#import <string.h> #import <libc.h> #import <mach/mach.h> #import <mach/machine/vm_param.h> #import <mach/cthreads.h> #import <mach-o/loader.h> #import <mach-o/dyld_debug.h> #import <mach/machine/thread_status.h> #ifdef hppa #import <architecture/hppa/cframe.h> #endif /* * This is the state of the target task while we are sending a message to it. */ struct target_task_state { port_t debug_port; thread_t debug_thread; unsigned int debug_thread_resume_count; unsigned int task_resume_count; thread_array_t threads; unsigned int thread_count; }; /* * This is the layout of the (__DATA,__dyld) section. The start_debug_thread, * debug_port and debug_thread fields are used in here. This is defined in * crt1.o in assembly code and this structure is declared to match. */ struct dyld_data_section { void *stub_binding_helper_interface; void *_dyld_func_lookup; void *start_debug_thread; port_t debug_port; thread_t debug_thread; }; enum dyld_debug_return Xprepare_msg_send( task_t target_task, struct target_task_state *state); enum dyld_debug_return Xfinish_msg_send( task_t target_task, struct target_task_state *state); static enum dyld_debug_return get_or_start_debug_thread( task_t target_task, struct target_task_state *state); static enum dyld_debug_return get_dyld_data( task_t target_task, struct dyld_data_section *data); static enum dyld_debug_return task_suspend_threads( task_t target_task, thread_array_t *threads, unsigned int *count); static enum dyld_debug_return task_resume_threads( thread_array_t threads, unsigned int count); static enum dyld_debug_return translate_port( task_t target_task, port_name_t target_port, port_t *dest_port); static enum dyld_debug_return thread_start( task_t task, void *func, long arg, vm_offset_t stack_size, thread_t *thread); static enum dyld_debug_return thread_setup( thread_t thread, void *func, long arg, vm_address_t stack_copy, vm_address_t stack_base, vm_offset_t stack_size); enum dyld_debug_return Xprepare_msg_send( task_t target_task, struct target_task_state *state) { enum dyld_debug_return d; struct thread_basic_info info; unsigned int info_count; kern_return_t k; unsigned int i; /* * Call get_or_start_debug_thread() which will set the debug_thread and * debug_port in the target_task_state passed to it. It will start the * debug thread and allocate the debug port if the task has not already * had one started in it. */ d = get_or_start_debug_thread(target_task, state); if(d != DYLD_SUCCESS) return(d); /* * Suspend the target task, so that there is less chance that * something happens while we're preparing the msg_send. */ k = task_suspend(target_task); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* * Suspend all threads but debug_thread. This may actually cause * control in the target task to block a thread that has the dyld lock, * but was resumed by the debug thread. That's handled inside dyld. */ k = task_threads(target_task, &state->threads, &state->thread_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); for(i = 0; i < state->thread_count; i++){ if(state->threads[i] != state->debug_thread){ k = thread_suspend(state->threads[i]); if(k != KERN_SUCCESS) return(DYLD_FAILURE); } } /* make sure the debug_thread is runnable */ info_count = THREAD_BASIC_INFO_COUNT; k = thread_info(state->debug_thread, THREAD_BASIC_INFO, (thread_info_t)&info, &info_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); state->debug_thread_resume_count = 0; for(i = 0; i < info.suspend_count; i++){ k = thread_resume(state->debug_thread); if(k == KERN_SUCCESS) /* the thread has been resumed */ state->debug_thread_resume_count += 1; else if(k == KERN_FAILURE) /* the suspend count is already 0 */ break; else return(DYLD_FAILURE); } /* resume the target task */ k = task_resume(target_task); if(k != KERN_SUCCESS) return(DYLD_FAILURE); state->task_resume_count = 0; while(task_resume(target_task) == KERN_SUCCESS) state->task_resume_count++; return(DYLD_SUCCESS); } /* * finish_msg_send() is called after sending messages to the dynamic link * editor. It undoes what prepare_msg_send() did to the task and put it back * the way it was. */ enum dyld_debug_return Xfinish_msg_send( task_t target_task, struct target_task_state *state) { unsigned int i; kern_return_t k; while(state->task_resume_count--){ k = task_suspend(target_task); if(k != KERN_SUCCESS) return(DYLD_FAILURE); } /* re-suspend the debug thread if we resumed it above */ for(i = 0; i < state->debug_thread_resume_count; i++){ k = thread_suspend(state->debug_thread); if(k != KERN_SUCCESS) return(DYLD_FAILURE); } /* resume all the same threads we suspended before */ for(i = 0; i < state->thread_count; i++){ if(state->threads[i] != state->debug_thread){ k = thread_resume(state->threads[i]); if(k != KERN_SUCCESS) return(DYLD_FAILURE); } } k = vm_deallocate(task_self(), (vm_address_t)state->threads, sizeof(state->threads[0]) * state->thread_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); return(DYLD_SUCCESS); } /* * get_or_start_debug_thread() gets or starts the debug thread of the target * task. It fills in the debug_thread and debug_port of the target task state * passed to it. */ static enum dyld_debug_return get_or_start_debug_thread( task_t target_task, struct target_task_state *state) { enum dyld_debug_return d; struct dyld_data_section data; thread_array_t threads; unsigned int count, count1; boolean_t task_resumed; thread_t debug_thread; kern_return_t k; struct host_sched_info info; state->debug_thread = THREAD_NULL; state->debug_port = PORT_NULL; /* * get the contents of the (__DATA,__dyld) section from the target * task. If it has none we can't proceed. */ d = get_dyld_data(target_task, &data); if(d != DYLD_SUCCESS) return(d); /* * If the address of the starting point of the debug has not yet been * set by the dynamic linker the thread can't be started. */ if(data.start_debug_thread == 0) return(DYLD_FAILURE); /* * If there is not already a debug thread in the target task start one. */ if(data.debug_thread == THREAD_NULL || data.debug_port == PORT_NULL){ d = task_suspend_threads(target_task, &threads, &count); if(d != DYLD_SUCCESS) return(d); task_resumed = FALSE; if(task_resume(target_task) == KERN_SUCCESS) task_resumed = TRUE; /* start the debugging thread */ d = thread_start(target_task, data.start_debug_thread, /* function address */ FALSE, /* function parameter, start_from_core */ vm_page_size, /* TODO: will this do? stack size */ &debug_thread); if(d != DYLD_SUCCESS) return(d); /* * Wait for the debug thread in the target task to be up running * where it has set the debug_thread field in the (__DATA,__dyld) * section of that task. */ count1 = HOST_SCHED_INFO_COUNT; k = host_info(host_self(), HOST_SCHED_INFO, (host_info_t)(&info), &count1); if(k != KERN_SUCCESS) return(DYLD_FAILURE); do{ k = thread_switch(THREAD_NULL, SWITCH_OPTION_DEPRESS, info.min_timeout); if(k != KERN_SUCCESS) return(DYLD_FAILURE); d = get_dyld_data(target_task, &data); if(d != DYLD_SUCCESS) return(d); }while(data.debug_thread == THREAD_NULL || data.debug_port == PORT_NULL); if(task_resumed){ k = task_suspend(target_task); if(k != KERN_SUCCESS) return(DYLD_FAILURE); } d = task_resume_threads(threads, count); if(d != DYLD_SUCCESS) return(d); } /* translate to this task's port space */ d = translate_port(target_task, data.debug_thread,&state->debug_thread); if(d != DYLD_SUCCESS) return(d); d = translate_port(target_task, data.debug_port, &state->debug_port); if(d != DYLD_SUCCESS) return(d); return(DYLD_SUCCESS); } /* * get_debug_thread is an access function for outside callers. */ thread_t get_debug_thread(task_t task) { struct target_task_state state; if (get_or_start_debug_thread(task, &state) == DYLD_SUCCESS) return state.debug_thread; else return 0; } /* * get_dyld_data() gets the contents of the (__DATA,__dyld) section from the * target task if it has one. If it does, the contents are copied into * the memory pointed to by data and DYLD_SUCCESS is returned otherwise * DYLD_FAILURE is returned. */ static enum dyld_debug_return get_dyld_data( task_t target_task, struct dyld_data_section *dyld_data) { kern_return_t k; struct mach_header *mh; vm_address_t address; vm_size_t size; vm_prot_t protection, max_protection; vm_inherit_t inheritance; boolean_t shared; port_t objectName; vm_offset_t offset; pointer_t data; unsigned int data_count; const struct section *s; unsigned long addr; memset(dyld_data, '\0', sizeof(struct dyld_data_section)); /* * Look through the target task's region looking for the region that * has the mach header and load commands. This is a guess. It should * be possible to ask the kernel where the address of the mach header * is in a task if it has been mapped. */ mh = NULL; address = VM_MIN_ADDRESS; do{ k = vm_region(target_task, &address, &size, &protection, &max_protection, &inheritance, &shared, &objectName, &offset); if(k == KERN_SUCCESS){ k = vm_read(target_task, address, size, &data, &data_count); if(k == KERN_SUCCESS){ if(data_count > sizeof(struct mach_header)){ mh = (struct mach_header *)data; /* * If the magic number is right and the size of this * region is big enough to cover the mach header and * load commands assume it is correct. */ if(mh->magic != MH_MAGIC || data_count < sizeof(struct mach_header) + mh->sizeofcmds){ mh = NULL; } } if(mh == NULL){ k = vm_deallocate(task_self(), data, data_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); } } else if(k != KERN_PROTECTION_FAILURE) return(DYLD_FAILURE); address += size; } }while(k != KERN_NO_SPACE && mh == NULL); if(mh == NULL) return(DYLD_FAILURE); /* * Walk the mach header looking for the (__DATA,__dyld) section. */ s = getsectbynamefromheader(mh, "__DATA", "__dyld"); if(s == NULL) return(DYLD_FAILURE); /* if the size of this section is too small return failure. */ if(s->size < sizeof(struct dyld_data_section)){ k = vm_deallocate(task_self(), data, data_count); return(DYLD_FAILURE); } /* * Save the address of the (__DATA,__dyld) section and get rid of * the memory for the mach header and load commands. */ addr = s->addr; s = NULL; k = vm_deallocate(task_self(), data, data_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* get the region for the (__DATA,__dyld) section */ address = addr; k = vm_region(target_task, &address, &size, &protection, &max_protection, &inheritance, &shared, &objectName, &offset); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* * Make sure the address of the region we got is not past the * (__DATA,__dyld) section. */ if(address > addr + sizeof(struct dyld_data_section)) return(DYLD_FAILURE); /* read the region that contains the (__DATA,__dyld) section */ k = vm_read(target_task, address, size, &data, &data_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* make sure the region is large enough */ if(size - (addr - address) < sizeof(struct dyld_data_section)) return(DYLD_FAILURE); /* copy the (__DATA,__dyld) section contents */ memcpy(dyld_data, (char *)data + addr - address, sizeof(struct dyld_data_section)); /* * Get rid of the memory for the region that contains the * (__DATA,__dyld) section. */ k = vm_deallocate(task_self(), data, data_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); return(DYLD_SUCCESS); } /* * task_suspend_threads() does a single thread_supend() on all the threads in * the target task. It also returns indirectly the thread array and count of * all the threads in the task. */ static enum dyld_debug_return task_suspend_threads( task_t target_task, thread_array_t *threads, unsigned int *count) { kern_return_t k; unsigned int i; k = task_threads(target_task, threads, count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); for(i = 0; i < *count; i++){ k = thread_suspend((*threads)[i]); if(k != KERN_SUCCESS) return(DYLD_FAILURE); } return(DYLD_SUCCESS); } /* * task_resume_threads() does a single thread_resume on all the threads in the * thread array of size count. */ static enum dyld_debug_return task_resume_threads( thread_array_t threads, unsigned int count) { kern_return_t k; unsigned int i; for(i = 0; i < count; i++){ k = thread_resume((threads)[i]); if(k != KERN_SUCCESS) return(DYLD_FAILURE); } k = vm_deallocate(task_self(), (vm_address_t)threads, sizeof(threads[0]) * count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); return(DYLD_SUCCESS); } /* * translate_port() sets dest_port to the port in this task's name space * for target_port in target_task when target_port in target_task is valid and * returns DYLD_SUCCESS. Otherwise it sets dest_port is to PORT_NULL and * returns DYLD_FAILURE. */ static enum dyld_debug_return translate_port( task_t target_task, port_name_t target_port, port_t *dest_port) { port_name_t *names; unsigned int i; port_type_array_t types; unsigned int namesCount, typesCount; kern_return_t k; *dest_port = PORT_NULL; k = port_names(target_task, &names, &namesCount, &types, &typesCount); if(k != KERN_SUCCESS) return(DYLD_FAILURE); for(i = 0; i < namesCount; i++){ if(names[i] == target_port){ if(types[i] == PORT_TYPE_RECEIVE_OWN){ k = port_extract_receive(target_task, names[i], dest_port); if(k != KERN_SUCCESS) return(DYLD_FAILURE); k = port_insert_receive(target_task, *dest_port, names[i]); if(k != KERN_SUCCESS) return(DYLD_FAILURE); break; } else if(types[i] == PORT_TYPE_SEND){ k = port_extract_send(target_task, names[i], dest_port); if(k != KERN_SUCCESS) return(DYLD_FAILURE); k = port_insert_send(target_task, *dest_port, names[i]); if(k != KERN_SUCCESS) return(DYLD_FAILURE); break; } } } k = vm_deallocate(task_self(), (vm_address_t)names, sizeof(*names) * namesCount); if(k != KERN_SUCCESS) return(DYLD_FAILURE); k = vm_deallocate(task_self(), (vm_address_t)types, sizeof(*types) * typesCount); if(k != KERN_SUCCESS) return(DYLD_FAILURE); return(DYLD_SUCCESS); } /* * thread_start() starts a thread in the specified task at the function passing * the arg using stack_size as it's stack size. It returns the created thread * indirectly through thread. */ static enum dyld_debug_return thread_start( task_t task, void *func, long arg, vm_offset_t stack_size, thread_t *thread) { vm_address_t stack_base, stack_copy; kern_return_t k; enum dyld_debug_return d; /* create remote stack */ k = vm_allocate(task, &stack_base, stack_size, TRUE); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* create local stack */ k = vm_allocate(task_self(), &stack_copy, stack_size, TRUE); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* create a thread */ k = thread_create(task, thread); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* setup the thread */ d = thread_setup(*thread, func, arg, stack_copy, stack_base,stack_size); if(d != DYLD_SUCCESS) return(d); /* copy stack to remote task */ k = vm_write(task, stack_base, stack_copy, stack_size); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* release local copy of stack */ k = vm_deallocate(task_self(), stack_copy, stack_size); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* really start the remote thread */ k = thread_resume(*thread); if(k != KERN_SUCCESS) return(DYLD_FAILURE); return(DYLD_SUCCESS); } /* * thread_setup() sets up the machine specific thread and stack for the func * with the arg. */ #ifdef i386 static enum dyld_debug_return thread_setup( thread_t thread, void *func, long arg, vm_address_t stack_copy, vm_address_t stack_base, vm_offset_t stack_size) { long *ltop, *rtop; i386_thread_state_t state, *ts; unsigned int state_count; kern_return_t k; /* * On the i386 stack grows down, so it starts out at: * stack_base + stack top. */ ltop = (long *)((char *)stack_copy + stack_size); rtop = (long *)((char *)stack_base + stack_size); /* get the current state of the thread */ ts = &state; state_count = i386_THREAD_STATE_COUNT; k = thread_get_state(thread, i386_THREAD_STATE, (thread_state_t)&state, &state_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* setup the instruction pointer */ ts->eip = (long)func; /* push the argument on the stack */ *--ltop = (long)arg; rtop--; /* * The return address is set to 0, so we get a segfault * if the thread tries to return. */ *--ltop = 0; rtop--; /* setup the stack pointer */ ts->esp = (long)rtop; /* set the state of the thread */ k = thread_set_state(thread, i386_THREAD_STATE, (thread_state_t)&state, state_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); return(DYLD_SUCCESS); } #endif /* i386 */ #ifdef m68k static enum dyld_debug_return thread_setup( thread_t thread, void *func, long arg, vm_address_t stack_copy, vm_address_t stack_base, vm_offset_t stack_size) { long *ltop, *rtop; struct m68k_thread_state_regs state, *ts; unsigned int state_count; kern_return_t k; /* * On the m68k stack grows down, so it starts out at: * stack_base + stack top. */ ltop = (long *)((char *)stack_copy + stack_size); rtop = (long *)((char *)stack_base + stack_size); /* get the current state of the thread */ ts = &state; state_count = M68K_THREAD_STATE_REGS_COUNT; k = thread_get_state(thread, M68K_THREAD_STATE_REGS, (thread_state_t)&state, &state_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* setup the instruction pointer */ ts->pc = (long)func; /* push the argument on the stack */ *--ltop = (long)arg; rtop--; /* * The return address is set to 0, so we get a segfault * if the thread tries to return. */ *--ltop = 0; rtop--; /* setup the frame and stack pointer */ ts->areg[6] = (long) 0; ts->areg[7] = (long) rtop; /* set the state of the thread */ k = thread_set_state(thread, M68K_THREAD_STATE_REGS, (thread_state_t)&state, state_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); return(DYLD_SUCCESS); } #endif m68k #ifdef hppa static enum dyld_debug_return thread_setup( thread_t thread, void *func, long arg, vm_address_t stack_copy, vm_address_t stack_base, vm_offset_t stack_size) { long rtop; struct hp_pa_frame_thread_state frame_state; struct hp_pa_integer_thread_state integer_state; unsigned int frame_state_count, integer_state_count; kern_return_t k; /* on hppa the stack grows up, so it starts out at stack_base */ rtop = (long)stack_base; /* get the current state of the thread */ /* TODO: is this right? is a thread_get_state() call needed? */ memset(&frame_state, 0, sizeof (frame_state)); /* setup the instruction pointer */ frame_state.ts_pcoq_front = (long) func; frame_state.ts_pcoq_back = frame_state.ts_pcoq_front + 4; /* store the instruction pointer back into the thread */ frame_state_count = HPPA_FRAME_THREAD_STATE_COUNT; k = thread_set_state(thread, HPPA_FRAME_THREAD_STATE, (thread_state_t)&frame_state, frame_state_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* get the general purpose registers */ /* TODO: is this right? is a thread_get_state() call needed? */ memset(&integer_state, 0, sizeof (integer_state)); /* put argument in first argument register */ integer_state.ts_gr26 = (unsigned long)arg; /* * The return address is set to 0, so we get a segfault * if the thread tries to return. */ integer_state.ts_gr2 = 0; /* set stack pointer so that there is room for frame */ rtop += C_ARGSAVE_LEN; integer_state.ts_gr30 = (rtop + C_STACK_ALIGN-1) & ~(C_STACK_ALIGN - 1); /* set the general regs of the thread */ integer_state_count = HPPA_INTEGER_THREAD_STATE_COUNT; k = thread_set_state (thread, HPPA_INTEGER_THREAD_STATE, (thread_state_t)&integer_state, integer_state_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); return(DYLD_SUCCESS); } #endif /* hppa */ #ifdef sparc static enum dyld_debug_return thread_setup( thread_t thread, void *func, long arg, vm_address_t stack_copy, vm_address_t stack_base, vm_offset_t stack_size) { long *ltop, *rtop; struct sparc_thread_state_regs state, *ts; unsigned int state_count; kern_return_t k; /* * On the sparc stack grows down, so it starts out at: * stack_base + stack top. */ ltop = (long *)((char *)stack_copy + stack_size); rtop = (long *)((char *)stack_base + stack_size); /* get the current state of the thread */ ts = &state; state_count = SPARC_THREAD_STATE_REGS_COUNT; k = thread_get_state(thread, SPARC_THREAD_STATE_REGS, (thread_state_t)&state, &state_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); /* setup the instruction pointer */ ts->regs.r_pc = (int)func; ts->regs.r_npc = ts->regs.r_pc + 4; /* push the argument on the stack */ ts->regs.r_o0 = (int)arg; /* * The return address is set to 0, so we get a segfault * if the thread tries to return. */ ts->regs.r_o7 = (int) 0; /* setup the frame and stack pointer */ ts->regs.r_o6 = (int) rtop; /* set the state of the thread */ k = thread_set_state(thread, SPARC_THREAD_STATE_REGS, (thread_state_t)&state, state_count); if(k != KERN_SUCCESS) return(DYLD_FAILURE); return(DYLD_SUCCESS); } #endif /* sparc */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.