ftp.nice.ch/Attic/openStep/developer/bundles/GDBbundle.1.0.s.tgz#/GDBbundle-1.0.s/debug/gdb/gdb/next/dyld_debug.c

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.