/* Print and select threads for GDB, the GNU debugger, on Mach TT systems.
   Copyright (C) 1986, 1987 Free Software Foundation, Inc.

GDB is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY.  No author or distributor accepts responsibility to anyone
for the consequences of using it or for whether it serves any
particular purpose or works at all, unless he says so in writing.
Refer to the GDB General Public License for full details.

Everyone is granted permission to copy, modify and redistribute GDB,
but only under the conditions described in the GDB General Public
License.  A copy of this license is supposed to have been given to you
along with GDB so you can know your rights and responsibilities.  It
should be in a file named COPYING.  Among other things, the copyright
notice and this notice must be preserved on all copies.

In other words, go ahead and share GDB, but don't try to stop
anyone else from sharing it farther.  Help stamp out software hoarding!
*/
#import "gdb.h"

#import "stdio.h"

#define KERNEL_FEATURES
#import <sys/param.h>
#import <sys/dir.h>
/* #import <bsd/hppa/label_t.h> */
#import <sys/user.h>

#import <mach/cthreads.h>
#define	MTHREAD
#import "cthread_internals.h"

extern RegionManager *regionManager;

static int otherThreadsSuspended = FALSE;
static thread_array_t threadList = NULL;
static int threadCount = 0;
static int abortThreads = 0;
CORE_ADDR dontSuspendThreadAddress = 0xFFFFFFFF;
static thread_t exceptionThread = 0;

extern task_t inferior_task;
extern thread_t current_thread, step_thread, last_current_thread;

#import "threads.h"

#ifdef NO_SINGLE_STEP
extern int one_stepped;		/* From machine dependent code */
extern void single_step ();	/* Same. */
#endif /* NO_SINGLE_STEP */

static char
translate_state(int state)
{
    switch (state) {
	case TH_STATE_RUNNING:	return('R');
	case TH_STATE_STOPPED:	return('S');
	case TH_STATE_WAITING:	return('W');
	case TH_STATE_UNINTERRUPTIBLE: return('U');
	case TH_STATE_HALTED:		return('H');
	default:			return('?');
    }
}

char *
getThreadNameFromState(USER_REG_STRUCT *userRegState, int n)
{
    static char buf[10];
    ur_cthread_t userReg;
    cthread_t incarnation;
    char *name;

    if ((userReg = WARP((ur_cthread_t)(USER_REG(*userRegState))))
        && (incarnation = WARP(userReg->incarnation))
	&& (name = WARPSTRING((char *)incarnation->name)))
 	return name;
    else {
	sprintf(buf, "_t%d", n);
	return buf;
    }
}

static char *
get_thread_name(thread_t th, int n)
{
    USER_REG_STRUCT threadState;
    unsigned int threadStateCount = USER_REG_COUNT;
    
    mach_call(
      thread_get_state(th,
                       USER_REG_STATE,
                       (thread_state_t)&threadState,
		       &threadStateCount),
      "thread_get_state", "get_thread_name");
    return getThreadNameFromState(&threadState, n);
}

CORE_ADDR
getThreadPCAndNameFromState(REGS_STRUCT *regsState, char **pName)
{
    struct minimal_symbol *minSym
	= lookup_minimal_symbol_by_pc(PC_FIELD(*regsState));;
    
    *pName = minSym ? SYMBOL_NAME(minSym) : "??";
    return PC_FIELD(*regsState);
}

static CORE_ADDR
get_thread_pc_and_name(thread_t th, char **pName)
{
    REGS_STRUCT regsState;
    unsigned regsCount = REGS_COUNT;

    mach_call(thread_get_state(th, REGS_STATE, 
                               (thread_state_t)&regsState, &regsCount),
	      "thread_get_state", "get_thread_pc_and_name");
    return getThreadPCAndNameFromState(&regsState, pName);
}

void
thread_list_command()
{
    int i, current_thread_index;
    thread_basic_info_data_t ths;
    char *name, *fName;
    CORE_ADDR pc;
    unsigned int size;
    extern thread_t get_debug_thread();
    thread_t debug_thread = 0;	/* Identify the dyld debugging thread */


    if (!inferior_pid) {
      core_thread_list();
      return;
    }

    if (!threadList)
	mach_call(task_threads(inferior_task, &threadList, &threadCount),
		  "task_threads", "thread_list_command");

    if (threadCount == 1)
	printf("There is 1 thread.\n");
    else {
	printf("There are %d threads.\n", threadCount);
	if (threadCount == 0)
	    return;	/* nothing more to do. */
	debug_thread = get_debug_thread(inferior_task);
    }
    printf("Thread State Suspend Port       PC         Name            Function\n");         
    for (i = 0; i < threadCount; i++) {
	if (threadList[i] == current_thread)
	    current_thread_index = i;

	size = THREAD_BASIC_INFO_COUNT;
	mach_call(thread_info(threadList[i], THREAD_BASIC_INFO,
			      (thread_info_t)&ths, &size),
	          "thread_info", "thread_list_command");
	
	name = get_thread_name(threadList[i], i);
	pc  = get_thread_pc_and_name(threadList[i], &fName);
	if (threadList[i] == debug_thread) {
	    name  = "gdb dynamic lib";	/* MVS: identify the dyld */
	    fName = "debugging thread";	/*      debugging thread */
	}
	printf ("%-6d %c     %-7d 0x%-8x 0x%-8x %-15s %-20s\n", i,
		translate_state (ths.run_state), ths.suspend_count,
		threadList[i], pc, name, fName);
    }

    printf("\nThe current thread is thread %d.\n", current_thread_index);
}

void
thread_select_command(char *args, int from_tty)
{
  int thread_index;
  kern_return_t ret;
  extern struct symbol *step_start_function;
  extern int stop_soon_quietly;
  extern int stop_print_frame;

  if (!inferior_pid) {
    core_thread_select(args, from_tty);
  } else {

	if (!args)
	error_no_arg ("thread index to select");
    
	thread_index = atoi(args);
	if (thread_index < 0) {
	    error ("no such thread");
	    return;
	}
	if (!threadList)
	    mach_call(task_threads(inferior_task, &threadList, &threadCount),
			"task_threads", "thread_select_command");

	if (thread_index >= threadCount) {
	    error ("no such thread");
	    return;
	}
    
	current_thread = last_current_thread = threadList[thread_index];
  }

  flush_cached_frames();
  registers_changed ();
  stop_pc = read_pc();
  step_start_function = find_pc_function(stop_pc);
  stop_soon_quietly = 1;
  stop_print_frame = 1;
  bpstat_clear (&stop_bpstat);
  normal_stop ();
}

void
set_trace_bit (thread_t thread)
{
    unsigned int	stateCnt = TRACE_COUNT;
    kern_return_t		ret;
    TRACE_STRUCT	state;
    
    step_thread = thread;

#ifdef NO_SINGLE_STEP
    one_stepped = 0; 
    single_step(0);
#else
    mach_call(thread_get_state(thread, TRACE_STATE,
			       (thread_state_t)&state, &stateCnt),
	      "thread_get_state", "set_trace_bit");
    if (!TRACE_BIT_SET(state)) {
        SET_TRACE_BIT(state);
	if (!stopped_in_ptrace)
	    mach_call(thread_abort(thread), "thread_abort", "set_trace_bit");
	mach_call(thread_set_state(thread, TRACE_STATE,
				   (thread_state_t)&state, stateCnt),
		  "thread_set_state", "set_trace_bit");
    }
#endif NO_SINGLE_STEP
}

void
clear_trace_bit (thread_t thread)
{
  unsigned int	stateCnt = TRACE_COUNT;
  kern_return_t		ret;
  TRACE_STRUCT	state;

  step_thread = 0;
  
#ifdef NO_SINGLE_STEP
  if (one_stepped) 
    single_step(0);
#else
  mach_call(thread_get_state(thread, TRACE_STATE,
                             (thread_state_t)&state, &stateCnt),
            "thread_get_state", "clear_trace_bit");

  if (TRACE_BIT_SET(state)) {
//	printf("Clearing trace bit.\n");
	CLEAR_TRACE_BIT(state);
	if (!stopped_in_ptrace)
	    mach_call(thread_abort(thread), "thread_abort", "clear_trace_bit");
	mach_call(thread_set_state(thread, TRACE_STATE,
                                   (thread_state_t)&state, stateCnt),
		  "thread_set_state", "clear_trace_bit");
  }
#endif NO_SINGLE_STEP
}

void clearThreadState()
{
    step_thread = 0;
    otherThreadsSuspended = FALSE;
}

void
prepare_threads_before_run(int step)
{
    int i;
    thread_basic_info_data_t ths;
    unsigned int size;
    thread_t thread;
    
    if (!threadList)
	mach_call(task_threads(inferior_task, &threadList, &threadCount),
		    "task_threads", "prepare_threads_before_run");
    if (step_range_end) {
	for (i = 0; i < threadCount; i++) {
	    thread = threadList[i];
	    size = THREAD_BASIC_INFO_COUNT;
	    mach_call(thread_info(thread, THREAD_BASIC_INFO,
				  (thread_info_t)&ths, &size),
		      "thread_info", "prepare_threads_before_run");
	    if (thread != exceptionThread) {
		if (thread == current_thread) {
		    if (ths.suspend_count)
			mach_call(thread_resume(thread),
				  "thread_resume",
				  "prepare_threads_before_run");
		} else {
		    if (!ths.suspend_count) {
			mach_call(thread_suspend(thread),
				  "thread_suspend",
				  "prepare_threads_before_run");
			otherThreadsSuspended = TRUE;
		    }
		}
	    }
	}
   } else {
	if (otherThreadsSuspended) {
	    for (i = 0; i < threadCount; i++)
		thread_resume(threadList[i]);
	    otherThreadsSuspended = FALSE;
	}
    }

/* This can be used to set anything interesting about the thread
 * just before it gets run.
 */
#ifdef SET_THREADSTATE_BEFORE_RUN
  for (i = 0; i < threadCount; i++)
    SET_THREADSTATE_BEFORE_RUN(threadList[i]);
#endif


    if (step_thread)
	clear_trace_bit(step_thread);
    if (step) 
	set_trace_bit(current_thread);
    vm_deallocate(task_self(),
		    (vm_address_t)threadList, 
		    (threadCount * sizeof(int)));
    threadList = NULL;
}

void
prepare_threads_after_stop()
{
    int	i;
    static int firstTime = NO;

    if (firstTime) {
	firstTime = NO;
    } else if (abortThreads) {
	mach_call(task_threads(inferior_task, &threadList, &threadCount),
		    "task_threads", "prepare_threads_after_stop");
	for (i = 0; i < threadCount; i++) {
	    if (!stopped_in_ptrace)
		mach_call(thread_abort(threadList[i]),
			    "thread_abort", "prepare_threads_after_stop");
	}
    }
}

void dontSuspendThread()
{
    exceptionThread = current_thread;
    thread_resume(exceptionThread);
    current_thread = last_current_thread;
}

const static char breakpoint_insn[] = BREAKPOINT;
static char oldBreakpointData[sizeof(breakpoint_insn) / sizeof(*breakpoint_insn)];

void insertNoSuspendBreakpoint()
{
    struct minimal_symbol *minSym;
    if (minSym = lookup_minimal_symbol("tellDebuggerNotToSuspend", NULL, NULL)) {
	dontSuspendThreadAddress = SYMBOL_VALUE_ADDRESS(minSym);
	[regionManager getDataAt: (void *)dontSuspendThreadAddress
				for: sizeof(breakpoint_insn)
			    into: (void *)oldBreakpointData];
	[regionManager putDataAt: (void *)dontSuspendThreadAddress
				for: sizeof(breakpoint_insn)
			    from: (void *)breakpoint_insn];
    }
}

void
_initialize_threads ()
{
    add_com ("thread-select", class_stack, thread_select_command,
	    "Select and print a thread.");
    add_com_alias ("ts", "thread-select", class_stack, 0);

    add_com ("thread-list", class_stack, thread_list_command,
	    "List all of threads.");
    add_com_alias ("tl", "thread-list", class_stack, 0);
    add_show_from_set(
      add_set_cmd("abort-threads", class_obscure, var_boolean, 
                (char *)&abortThreads,
                "Set if gdb should abort all threads after stop.",
		&setlist),
      &showlist);		
}
