/* Interface GDB to Mach 3.0 operating systems. (Most) Mach 3.0 related routines live in this file. Copyright (C) 1992, 1996, 1999, 2000, 2001 Free Software Foundation, Inc. This file is part of GDB. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * Author: Jukka Virtanen * Computing Centre * Helsinki University of Technology * Finland * * Thanks to my friends who helped with ideas and testing: * * Johannes Helander, Antti Louko, Tero Mononen, * jvh@cs.hut.fi alo@hut.fi tmo@cs.hut.fi * * Tero Kivinen and Eamonn McManus * kivinen@cs.hut.fi emcmanus@gr.osf.org * */ #include #include #include #include #include #include #include #include #include #include "defs.h" #include "inferior.h" #include "symtab.h" #include "value.h" #include "language.h" #include "target.h" #include "gdb_wait.h" #include "gdbcmd.h" #include "gdbcore.h" #if 0 #include #else #define MACH_TYPE_TASK 1 #define MACH_TYPE_THREAD 2 #endif /* Included only for signal names and NSIG * note: There are many problems in signal handling with * gdb in Mach 3.0 in general. */ #include #define SIG_UNKNOWN 0 /* Exception that has no matching unix signal */ #include /* This is what a cproc looks like. This is here partly because cthread_internals.h is not a header we can just #include, partly with an eye towards perhaps getting this to work with cross-debugging someday. Best solution is if CMU publishes a real interface to this stuff. */ #define CPROC_NEXT_OFFSET 0 #define CPROC_NEXT_SIZE (TARGET_PTR_BIT / HOST_CHAR_BIT) #define CPROC_INCARNATION_OFFSET (CPROC_NEXT_OFFSET + CPROC_NEXT_SIZE) #define CPROC_INCARNATION_SIZE (sizeof (cthread_t)) #define CPROC_LIST_OFFSET (CPROC_INCARNATION_OFFSET + CPROC_INCARNATION_SIZE) #define CPROC_LIST_SIZE (TARGET_PTR_BIT / HOST_CHAR_BIT) #define CPROC_WAIT_OFFSET (CPROC_LIST_OFFSET + CPROC_LIST_SIZE) #define CPROC_WAIT_SIZE (TARGET_PTR_BIT / HOST_CHAR_BIT) #define CPROC_REPLY_OFFSET (CPROC_WAIT_OFFSET + CPROC_WAIT_SIZE) #define CPROC_REPLY_SIZE (sizeof (mach_port_t)) #define CPROC_CONTEXT_OFFSET (CPROC_REPLY_OFFSET + CPROC_REPLY_SIZE) #define CPROC_CONTEXT_SIZE (TARGET_INT_BIT / HOST_CHAR_BIT) #define CPROC_LOCK_OFFSET (CPROC_CONTEXT_OFFSET + CPROC_CONTEXT_SIZE) #define CPROC_LOCK_SIZE (sizeof (spin_lock_t)) #define CPROC_STATE_OFFSET (CPROC_LOCK_OFFSET + CPROC_LOCK_SIZE) #define CPROC_STATE_SIZE (TARGET_INT_BIT / HOST_CHAR_BIT) #define CPROC_WIRED_OFFSET (CPROC_STATE_OFFSET + CPROC_STATE_SIZE) #define CPROC_WIRED_SIZE (sizeof (mach_port_t)) #define CPROC_BUSY_OFFSET (CPROC_WIRED_OFFSET + CPROC_WIRED_SIZE) #define CPROC_BUSY_SIZE (TARGET_INT_BIT / HOST_CHAR_BIT) #define CPROC_MSG_OFFSET (CPROC_BUSY_OFFSET + CPROC_BUSY_SIZE) #define CPROC_MSG_SIZE (sizeof (mach_msg_header_t)) #define CPROC_BASE_OFFSET (CPROC_MSG_OFFSET + CPROC_MSG_SIZE) #define CPROC_BASE_SIZE (TARGET_INT_BIT / HOST_CHAR_BIT) #define CPROC_SIZE_OFFSET (CPROC_BASE_OFFSET + CPROC_BASE_SIZE) #define CPROC_SIZE_SIZE (TARGET_INT_BIT / HOST_CHAR_BIT) #define CPROC_SIZE (CPROC_SIZE_OFFSET + CPROC_SIZE_SIZE) /* Values for the state field in the cproc. */ #define CPROC_RUNNING 0 #define CPROC_SWITCHING 1 #define CPROC_BLOCKED 2 #define CPROC_CONDWAIT 4 /* For cproc and kernel thread mapping */ typedef struct gdb_thread { mach_port_t name; CORE_ADDR sp; CORE_ADDR pc; CORE_ADDR fp; boolean_t in_emulator; int slotid; /* This is for the mthreads list. It points to the cproc list. Perhaps the two lists should be merged (or perhaps it was a mistake to make them both use a struct gdb_thread). */ struct gdb_thread *cproc; /* These are for the cproc list, which is linked through the next field of the struct gdb_thread. */ char raw_cproc[CPROC_SIZE]; /* The cthread which is pointed to by the incarnation field from the cproc. This points to the copy we've read into GDB. */ cthread_t cthread; /* Point back to the mthreads list. */ int reverse_map; struct gdb_thread *next; } *gdb_thread_t; /* * Actions for Mach exceptions. * * sigmap field maps the exception to corresponding Unix signal. * * I do not know how to map the exception to unix signal * if SIG_UNKNOWN is specified. */ struct exception_list { char *name; boolean_t forward; boolean_t print; int sigmap; } exception_map[] = { { "not_mach3_exception", FALSE, TRUE, SIG_UNKNOWN } , { "EXC_BAD_ACCESS", FALSE, TRUE, SIGSEGV } , { "EXC_BAD_INSTRUCTION", FALSE, TRUE, SIGILL } , { "EXC_ARITHMETIC", FALSE, TRUE, SIGFPE } , { "EXC_EMULATION", FALSE, TRUE, SIGEMT } , /* ??? */ { "EXC_SOFTWARE", FALSE, TRUE, SIG_UNKNOWN } , { "EXC_BREAKPOINT", FALSE, FALSE, SIGTRAP } }; /* Mach exception table size */ int max_exception = sizeof (exception_map) / sizeof (struct exception_list) - 1; #define MAX_EXCEPTION max_exception WAITTYPE wait_status; /* If you define this, intercepted bsd server calls will be * dumped while waiting the inferior to EXEC the correct * program */ /* #define DUMP_SYSCALL /* debugging interceptor */ /* xx_debug() outputs messages if this is nonzero. * If > 1, DUMP_SYSCALL will dump message contents. */ int debug_level = 0; /* "Temporary" debug stuff */ void xx_debug (char *fmt, int a, int b, int c) { if (debug_level) warning (fmt, a, b, c); } /* This is in libmach.a */ extern mach_port_t name_server_port; /* Set in catch_exception_raise */ int stop_exception, stop_code, stop_subcode; int stopped_in_exception; /* Thread that was the active thread when we stopped */ thread_t stop_thread = MACH_PORT_NULL; char *hostname = ""; /* Set when task is attached or created */ boolean_t emulator_present = FALSE; task_t inferior_task; thread_t current_thread; /* Exception ports for inferior task */ mach_port_t inferior_exception_port = MACH_PORT_NULL; mach_port_t inferior_old_exception_port = MACH_PORT_NULL; /* task exceptions and notifications */ mach_port_t inferior_wait_port_set = MACH_PORT_NULL; mach_port_t our_notify_port = MACH_PORT_NULL; /* This is "inferior_wait_port_set" when not single stepping, and * "singlestepped_thread_port" when we are single stepping. * * This is protected by a cleanup function: discard_single_step() */ mach_port_t currently_waiting_for = MACH_PORT_NULL; /* A port for external messages to gdb. * External in the meaning that they do not come * from the inferior_task, but rather from external * tasks. * * As a debugging feature: * A debugger debugging another debugger can stop the * inferior debugger by the following command sequence * (without running external programs) * * (top-gdb) set stop_inferior_gdb () * (top-gdb) continue */ mach_port_t our_message_port = MACH_PORT_NULL; /* For single stepping */ mach_port_t thread_exception_port = MACH_PORT_NULL; mach_port_t thread_saved_exception_port = MACH_PORT_NULL; mach_port_t singlestepped_thread_port = MACH_PORT_NULL; /* For machid calls */ mach_port_t mid_server = MACH_PORT_NULL; mach_port_t mid_auth = MACH_PORT_NULL; /* If gdb thinks the inferior task is not suspended, it * must take suspend/abort the threads when it reads the state. */ int must_suspend_thread = 0; /* When single stepping, we switch the port that mach_really_wait() listens to. * This cleanup is a guard to prevent the port set from being left to * the singlestepped_thread_port when error() is called. * This is nonzero only when we are single stepping. */ #define NULL_CLEANUP (struct cleanup *)0 struct cleanup *cleanup_step = NULL_CLEANUP; static struct target_ops m3_ops; static void m3_kill_inferior (); #if 0 #define MACH_TYPE_EXCEPTION_PORT -1 #endif /* Chain of ports to remember requested notifications. */ struct port_chain { struct port_chain *next; mach_port_t port; int type; int mid; /* Now only valid with MACH_TYPE_THREAD and */ /* MACH_TYPE_THREAD */ }; typedef struct port_chain *port_chain_t; /* Room for chain nodes comes from pchain_obstack */ struct obstack pchain_obstack; struct obstack *port_chain_obstack = &pchain_obstack; /* For thread handling */ struct obstack Cproc_obstack; struct obstack *cproc_obstack = &Cproc_obstack; /* the list of notified ports */ port_chain_t notify_chain = (port_chain_t) NULL; port_chain_t port_chain_insert (port_chain_t list, mach_port_t name, int type) { kern_return_t ret; port_chain_t new; int mid; if (!MACH_PORT_VALID (name)) return list; if (type == MACH_TYPE_TASK || type == MACH_TYPE_THREAD) { if (!MACH_PORT_VALID (mid_server)) { warning ("Machid server port invalid, can not map port 0x%x to MID", name); mid = name; } else { ret = machid_mach_register (mid_server, mid_auth, name, type, &mid); if (ret != KERN_SUCCESS) { warning ("Can not map name (0x%x) to MID with machid", name); mid = name; } } } else abort (); new = (port_chain_t) obstack_alloc (port_chain_obstack, sizeof (struct port_chain)); new->next = list; new->port = name; new->type = type; new->mid = mid; return new; } port_chain_t port_chain_delete (port_chain_t list, mach_port_t elem) { if (list) if (list->port == elem) list = list->next; else while (list->next) { if (list->next->port == elem) list->next = list->next->next; /* GCd with obstack_free() */ else list = list->next; } return list; } void port_chain_destroy (struct obstack *ostack) { obstack_free (ostack, 0); obstack_init (ostack); } port_chain_t port_chain_member (port_chain_t list, mach_port_t elem) { while (list) { if (list->port == elem) return list; list = list->next; } return (port_chain_t) NULL; } int map_port_name_to_mid (mach_port_t name, int type) { port_chain_t elem; if (!MACH_PORT_VALID (name)) return -1; elem = port_chain_member (notify_chain, name); if (elem && (elem->type == type)) return elem->mid; if (elem) return -1; if (!MACH_PORT_VALID (mid_server)) { warning ("Machid server port invalid, can not map port 0x%x to mid", name); return -1; } else { int mid; kern_return_t ret; ret = machid_mach_register (mid_server, mid_auth, name, type, &mid); if (ret != KERN_SUCCESS) { warning ("Can not map name (0x%x) to mid with machid", name); return -1; } return mid; } } /* Guard for currently_waiting_for and singlestepped_thread_port */ static void discard_single_step (thread_t thread) { currently_waiting_for = inferior_wait_port_set; cleanup_step = NULL_CLEANUP; if (MACH_PORT_VALID (thread) && MACH_PORT_VALID (singlestepped_thread_port)) setup_single_step (thread, FALSE); } setup_single_step (thread_t thread, boolean_t start_step) { kern_return_t ret; if (!MACH_PORT_VALID (thread)) error ("Invalid thread supplied to setup_single_step"); else { mach_port_t teport; /* Get the current thread exception port */ ret = thread_get_exception_port (thread, &teport); CHK ("Getting thread's exception port", ret); if (start_step) { if (MACH_PORT_VALID (singlestepped_thread_port)) { warning ("Singlestepped_thread_port (0x%x) is still valid?", singlestepped_thread_port); singlestepped_thread_port = MACH_PORT_NULL; } /* If we are already stepping this thread */ if (MACH_PORT_VALID (teport) && teport == thread_exception_port) { ret = mach_port_deallocate (mach_task_self (), teport); CHK ("Could not deallocate thread exception port", ret); } else { ret = thread_set_exception_port (thread, thread_exception_port); CHK ("Setting exception port for thread", ret); #if 0 /* Insert thread exception port to wait port set */ ret = mach_port_move_member (mach_task_self (), thread_exception_port, inferior_wait_port_set); CHK ("Moving thread exception port to inferior_wait_port_set", ret); #endif thread_saved_exception_port = teport; } thread_trace (thread, TRUE); singlestepped_thread_port = thread_exception_port; currently_waiting_for = singlestepped_thread_port; cleanup_step = make_cleanup (discard_single_step, thread); } else { if (!MACH_PORT_VALID (teport)) error ("Single stepped thread had an invalid exception port?"); if (teport != thread_exception_port) error ("Single stepped thread had an unknown exception port?"); ret = mach_port_deallocate (mach_task_self (), teport); CHK ("Couldn't deallocate thread exception port", ret); #if 0 /* Remove thread exception port from wait port set */ ret = mach_port_move_member (mach_task_self (), thread_exception_port, MACH_PORT_NULL); CHK ("Removing thread exception port from inferior_wait_port_set", ret); #endif /* Restore thread's old exception port */ ret = thread_set_exception_port (thread, thread_saved_exception_port); CHK ("Restoring stepped thread's exception port", ret); if (MACH_PORT_VALID (thread_saved_exception_port)) (void) mach_port_deallocate (mach_task_self (), thread_saved_exception_port); thread_trace (thread, FALSE); singlestepped_thread_port = MACH_PORT_NULL; currently_waiting_for = inferior_wait_port_set; if (cleanup_step) discard_cleanups (cleanup_step); } } } static request_notify (mach_port_t name, mach_msg_id_t variant, int type) { kern_return_t ret; mach_port_t previous_port_dummy = MACH_PORT_NULL; if (!MACH_PORT_VALID (name)) return; if (port_chain_member (notify_chain, name)) return; ret = mach_port_request_notification (mach_task_self (), name, variant, 1, our_notify_port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous_port_dummy); CHK ("Serious: request_notify failed", ret); (void) mach_port_deallocate (mach_task_self (), previous_port_dummy); notify_chain = port_chain_insert (notify_chain, name, type); } reverse_msg_bits (mach_msg_header_t *msgp, int type) { int rbits, lbits; rbits = MACH_MSGH_BITS_REMOTE (msgp->msgh_bits); lbits = type; msgp->msgh_bits = (msgp->msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) | MACH_MSGH_BITS (lbits, rbits); } /* On the third day He said: Let this be global and then it was global. When creating the inferior fork, the child code in inflow.c sets the name of the bootstrap_port in its address space to this variable. The name is transferred to our address space with mach3_read_inferior(). Thou shalt not do this with task_get_bootstrap_port() in this task, since the name in the inferior task is different than the one we get. For blessed are the meek, as they shall inherit the address space. */ mach_port_t original_server_port_name = MACH_PORT_NULL; /* Called from inferior after FORK but before EXEC */ static void m3_trace_me (void) { kern_return_t ret; /* Get the NAME of the bootstrap port in this task so that GDB can read it */ ret = task_get_bootstrap_port (mach_task_self (), &original_server_port_name); if (ret != KERN_SUCCESS) abort (); ret = mach_port_deallocate (mach_task_self (), original_server_port_name); if (ret != KERN_SUCCESS) abort (); /* Suspend this task to let the parent change my ports. Resumed by the debugger */ ret = task_suspend (mach_task_self ()); if (ret != KERN_SUCCESS) abort (); } /* * Intercept system calls to Unix server. * After EXEC_COUNTER calls to exec(), return. * * Pre-assertion: Child is suspended. (Not verified) * Post-condition: Child is suspended after EXEC_COUNTER exec() calls. */ void intercept_exec_calls (int exec_counter) { int terminal_initted = 0; struct syscall_msg_t { mach_msg_header_t header; mach_msg_type_t type; char room[2000]; /* Enuff space */ }; struct syscall_msg_t syscall_in, syscall_out; mach_port_t fake_server; mach_port_t original_server_send; mach_port_t original_exec_reply; mach_port_t exec_reply; mach_port_t exec_reply_send; mach_msg_type_name_t acquired; mach_port_t emulator_server_port_name; struct task_basic_info info; mach_msg_type_number_t info_count; kern_return_t ret; if (exec_counter <= 0) return; /* We are already set up in the correct program */ ret = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &fake_server); CHK ("create inferior_fake_server port failed", ret); /* Wait for inferior_task to suspend itself */ while (1) { info_count = sizeof (info); ret = task_info (inferior_task, TASK_BASIC_INFO, (task_info_t) & info, &info_count); CHK ("Task info", ret); if (info.suspend_count) break; /* Note that the definition of the parameter was undefined * at the time of this writing, so I just use an `ad hoc' value. */ (void) swtch_pri (42); /* Universal Priority Value */ } /* Read the inferior's bootstrap port name */ if (!mach3_read_inferior (&original_server_port_name, &original_server_port_name, sizeof (original_server_port_name))) error ("Can't read inferior task bootstrap port name"); /* @@ BUG: If more than 1 send right GDB will FAIL!!! */ /* Should get refs, and set them back when restoring */ /* Steal the original bsd server send right from inferior */ ret = mach_port_extract_right (inferior_task, original_server_port_name, MACH_MSG_TYPE_MOVE_SEND, &original_server_send, &acquired); CHK ("mach_port_extract_right (bsd server send)", ret); if (acquired != MACH_MSG_TYPE_PORT_SEND) error ("Incorrect right extracted, send right to bsd server expected"); ret = mach_port_insert_right (inferior_task, original_server_port_name, fake_server, MACH_MSG_TYPE_MAKE_SEND); CHK ("mach_port_insert_right (fake server send)", ret); xx_debug ("inferior task bsd server ports set up \nfs %x, ospn %x, oss %x\n", fake_server, original_server_port_name, original_server_send); /* A receive right to the reply generated by unix server exec() request */ ret = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &exec_reply); CHK ("create intercepted_reply_port port failed", ret); /* Pass this send right to Unix server so it replies to us after exec() */ ret = mach_port_extract_right (mach_task_self (), exec_reply, MACH_MSG_TYPE_MAKE_SEND_ONCE, &exec_reply_send, &acquired); CHK ("mach_port_extract_right (exec_reply)", ret); if (acquired != MACH_MSG_TYPE_PORT_SEND_ONCE) error ("Incorrect right extracted, send once expected for exec reply"); ret = mach_port_move_member (mach_task_self (), fake_server, inferior_wait_port_set); CHK ("Moving fake syscall port to inferior_wait_port_set", ret); xx_debug ("syscall fake server set up, resuming inferior\n"); ret = task_resume (inferior_task); CHK ("task_resume (startup)", ret); /* Read requests from the inferior. Pass directly through everything else except exec() calls. */ while (exec_counter > 0) { ret = mach_msg (&syscall_in.header, /* header */ MACH_RCV_MSG, /* options */ 0, /* send size */ sizeof (struct syscall_msg_t), /* receive size */ inferior_wait_port_set, /* receive_name */ MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); CHK ("mach_msg (intercepted sycall)", ret); #ifdef DUMP_SYSCALL print_msg (&syscall_in.header); #endif /* ASSERT : msgh_local_port == fake_server */ if (notify_server (&syscall_in.header, &syscall_out.header)) error ("received a notify while intercepting syscalls"); if (syscall_in.header.msgh_id == MIG_EXEC_SYSCALL_ID) { xx_debug ("Received EXEC SYSCALL, counter = %d\n", exec_counter); if (exec_counter == 1) { original_exec_reply = syscall_in.header.msgh_remote_port; syscall_in.header.msgh_remote_port = exec_reply_send; } if (!terminal_initted) { /* Now that the child has exec'd we know it has already set its process group. On POSIX systems, tcsetpgrp will fail with EPERM if we try it before the child's setpgid. */ /* Set up the "saved terminal modes" of the inferior based on what modes we are starting it with. */ target_terminal_init (); /* Install inferior's terminal modes. */ target_terminal_inferior (); terminal_initted = 1; } exec_counter--; } syscall_in.header.msgh_local_port = syscall_in.header.msgh_remote_port; syscall_in.header.msgh_remote_port = original_server_send; reverse_msg_bits (&syscall_in.header, MACH_MSG_TYPE_COPY_SEND); ret = mach_msg_send (&syscall_in.header); CHK ("Forwarded syscall", ret); } ret = mach_port_move_member (mach_task_self (), fake_server, MACH_PORT_NULL); CHK ("Moving fake syscall out of inferior_wait_port_set", ret); ret = mach_port_move_member (mach_task_self (), exec_reply, inferior_wait_port_set); CHK ("Moving exec_reply to inferior_wait_port_set", ret); ret = mach_msg (&syscall_in.header, /* header */ MACH_RCV_MSG, /* options */ 0, /* send size */ sizeof (struct syscall_msg_t), /* receive size */ inferior_wait_port_set, /* receive_name */ MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); CHK ("mach_msg (exec reply)", ret); ret = task_suspend (inferior_task); CHK ("Suspending inferior after last exec", ret); must_suspend_thread = 0; xx_debug ("Received exec reply from bsd server, suspended inferior task\n"); #ifdef DUMP_SYSCALL print_msg (&syscall_in.header); #endif /* Message should appear as if it came from the unix server */ syscall_in.header.msgh_local_port = MACH_PORT_NULL; /* and go to the inferior task original reply port */ syscall_in.header.msgh_remote_port = original_exec_reply; reverse_msg_bits (&syscall_in.header, MACH_MSG_TYPE_MOVE_SEND_ONCE); ret = mach_msg_send (&syscall_in.header); CHK ("Forwarding exec reply to inferior", ret); /* Garbage collect */ ret = mach_port_deallocate (inferior_task, original_server_port_name); CHK ("deallocating fake server send right", ret); ret = mach_port_insert_right (inferior_task, original_server_port_name, original_server_send, MACH_MSG_TYPE_MOVE_SEND); CHK ("Restoring the original bsd server send right", ret); ret = mach_port_destroy (mach_task_self (), fake_server); fake_server = MACH_PORT_DEAD; CHK ("mach_port_destroy (fake_server)", ret); ret = mach_port_destroy (mach_task_self (), exec_reply); exec_reply = MACH_PORT_DEAD; CHK ("mach_port_destroy (exec_reply)", ret); xx_debug ("Done with exec call interception\n"); } void consume_send_rights (thread_array_t thread_list, int thread_count) { int index; if (!thread_count) return; for (index = 0; index < thread_count; index++) { /* Since thread kill command kills threads, don't check ret */ (void) mach_port_deallocate (mach_task_self (), thread_list[index]); } } /* suspend/abort/resume a thread. */ setup_thread (mach_port_t thread, int what) { kern_return_t ret; if (what) { ret = thread_suspend (thread); CHK ("setup_thread thread_suspend", ret); ret = thread_abort (thread); CHK ("setup_thread thread_abort", ret); } else { ret = thread_resume (thread); CHK ("setup_thread thread_resume", ret); } } int map_slot_to_mid (int slot, thread_array_t threads, int thread_count) { kern_return_t ret; int deallocate = 0; int index; int mid; if (!threads) { deallocate++; ret = task_threads (inferior_task, &threads, &thread_count); CHK ("Can not select a thread from a dead task", ret); } if (slot < 0 || slot >= thread_count) { if (deallocate) { consume_send_rights (threads, thread_count); (void) vm_deallocate (mach_task_self (), (vm_address_t) threads, (thread_count * sizeof (mach_port_t))); } if (slot < 0) error ("invalid slot number"); else return -(slot + 1); } mid = map_port_name_to_mid (threads[slot], MACH_TYPE_THREAD); if (deallocate) { consume_send_rights (threads, thread_count); (void) vm_deallocate (mach_task_self (), (vm_address_t) threads, (thread_count * sizeof (mach_port_t))); } return mid; } static int parse_thread_id (char *arg, int thread_count, int slots) { kern_return_t ret; int mid; int slot; int index; if (arg == 0) return 0; while (*arg && (*arg == ' ' || *arg == '\t')) arg++; if (!*arg) return 0; /* Currently parse MID and @SLOTNUMBER */ if (*arg != '@') { mid = atoi (arg); if (mid <= 0) error ("valid thread mid expected"); return mid; } arg++; slot = atoi (arg); if (slot < 0) error ("invalid slot number"); /* If you want slot numbers to remain slot numbers, set slots. * Well, since 0 is reserved, return the ordinal number * of the thread rather than the slot number. Awk, this * counts as a kludge. */ if (slots) return -(slot + 1); if (thread_count && slot >= thread_count) return -(slot + 1); mid = map_slot_to_mid (slot); return mid; } /* THREAD_ID 0 is special; it selects the first kernel * thread from the list (i.e. SLOTNUMBER 0) * This is used when starting the program with 'run' or when attaching. * * If FLAG is 0 the context is not changed, and the registers, frame, etc * will continue to describe the old thread. * * If FLAG is nonzero, really select the thread. * If FLAG is 2, the THREAD_ID is a slotnumber instead of a mid. * */ kern_return_t select_thread (mach_port_t task, int thread_id, int flag) { thread_array_t thread_list; int thread_count; kern_return_t ret; int index; thread_t new_thread = MACH_PORT_NULL; if (thread_id < 0) error ("Can't select cprocs without kernel thread"); ret = task_threads (task, &thread_list, &thread_count); if (ret != KERN_SUCCESS) { warning ("Can not select a thread from a dead task"); m3_kill_inferior (); return KERN_FAILURE; } if (thread_count == 0) { /* The task can not do anything anymore, but it still * exists as a container for memory and ports. */ registers_changed (); warning ("Task %d has no threads", map_port_name_to_mid (task, MACH_TYPE_TASK)); current_thread = MACH_PORT_NULL; (void) vm_deallocate (mach_task_self (), (vm_address_t) thread_list, (thread_count * sizeof (mach_port_t))); return KERN_FAILURE; } if (!thread_id || flag == 2) { /* First thread or a slotnumber */ if (!thread_id) new_thread = thread_list[0]; else { if (thread_id < thread_count) new_thread = thread_list[thread_id]; else { (void) vm_deallocate (mach_task_self (), (vm_address_t) thread_list, (thread_count * sizeof (mach_port_t))); error ("No such thread slot number : %d", thread_id); } } } else { for (index = 0; index < thread_count; index++) if (thread_id == map_port_name_to_mid (thread_list[index], MACH_TYPE_THREAD)) { new_thread = thread_list[index]; index = -1; break; } if (index != -1) error ("No thread with mid %d", thread_id); } /* Notify when the selected thread dies */ request_notify (new_thread, MACH_NOTIFY_DEAD_NAME, MACH_TYPE_THREAD); ret = vm_deallocate (mach_task_self (), (vm_address_t) thread_list, (thread_count * sizeof (mach_port_t))); CHK ("vm_deallocate", ret); if (!flag) current_thread = new_thread; else { #if 0 if (MACH_PORT_VALID (current_thread)) { /* Store the gdb's view of the thread we are deselecting * @@ I think gdb updates registers immediately when they are * changed, so don't do this. */ ret = thread_abort (current_thread); CHK ("Could not abort system calls when saving state of old thread", ret); target_prepare_to_store (); target_store_registers (-1); } #endif registers_changed (); current_thread = new_thread; ret = thread_abort (current_thread); CHK ("Could not abort system calls when selecting a thread", ret); stop_pc = read_pc (); flush_cached_frames (); select_frame (get_current_frame (), 0); } return KERN_SUCCESS; } /* * Switch to use thread named NEW_THREAD. * Return it's MID */ int switch_to_thread (thread_t new_thread) { thread_t saved_thread = current_thread; int mid; mid = map_port_name_to_mid (new_thread, MACH_TYPE_THREAD); if (mid == -1) warning ("Can't map thread name 0x%x to mid", new_thread); else if (select_thread (inferior_task, mid, 1) != KERN_SUCCESS) { if (current_thread) current_thread = saved_thread; error ("Could not select thread %d", mid); } return mid; } /* Do this in gdb after doing FORK but before STARTUP_INFERIOR. * Note that the registers are not yet valid in the inferior task. */ static int m3_trace_him (int pid) { kern_return_t ret; push_target (&m3_ops); inferior_task = task_by_pid (pid); if (!MACH_PORT_VALID (inferior_task)) error ("Can not map Unix pid %d to Mach task", pid); /* Clean up previous notifications and create new ones */ setup_notify_port (1); /* When notification appears, the inferior task has died */ request_notify (inferior_task, MACH_NOTIFY_DEAD_NAME, MACH_TYPE_TASK); emulator_present = have_emulator_p (inferior_task); /* By default, select the first thread, * If task has no threads, gives a warning * Does not fetch registers, since they are not yet valid. */ select_thread (inferior_task, 0, 0); inferior_exception_port = MACH_PORT_NULL; setup_exception_port (); xx_debug ("Now the debugged task is created\n"); /* One trap to exec the shell, one to exec the program being debugged. */ intercept_exec_calls (2); return pid; } setup_exception_port (void) { kern_return_t ret; ret = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &inferior_exception_port); CHK ("mach_port_allocate", ret); /* add send right */ ret = mach_port_insert_right (mach_task_self (), inferior_exception_port, inferior_exception_port, MACH_MSG_TYPE_MAKE_SEND); CHK ("mach_port_insert_right", ret); ret = mach_port_move_member (mach_task_self (), inferior_exception_port, inferior_wait_port_set); CHK ("mach_port_move_member", ret); ret = task_get_special_port (inferior_task, TASK_EXCEPTION_PORT, &inferior_old_exception_port); CHK ("task_get_special_port(old exc)", ret); ret = task_set_special_port (inferior_task, TASK_EXCEPTION_PORT, inferior_exception_port); CHK ("task_set_special_port", ret); ret = mach_port_deallocate (mach_task_self (), inferior_exception_port); CHK ("mack_port_deallocate", ret); #if 0 /* When notify appears, the inferior_task's exception * port has been destroyed. * * Not used, since the dead_name_notification already * appears when task dies. * */ request_notify (inferior_exception_port, MACH_NOTIFY_NO_SENDERS, MACH_TYPE_EXCEPTION_PORT); #endif } /* Nonzero if gdb is waiting for a message */ int mach_really_waiting; /* Wait for the inferior to stop for some reason. - Loop on notifications until inferior_task dies. - Loop on exceptions until stopped_in_exception comes true. (e.g. we receive a single step trace trap) - a message arrives to gdb's message port There is no other way to exit this loop. Returns the inferior_pid for rest of gdb. Side effects: Set *OURSTATUS. */ int mach_really_wait (int pid, struct target_waitstatus *ourstatus) { kern_return_t ret; int w; struct msg { mach_msg_header_t header; mach_msg_type_t foo; int data[8000]; } in_msg, out_msg; /* Either notify (death), exception or message can stop the inferior */ stopped_in_exception = FALSE; while (1) { QUIT; stop_exception = stop_code = stop_subcode = -1; stop_thread = MACH_PORT_NULL; mach_really_waiting = 1; ret = mach_msg (&in_msg.header, /* header */ MACH_RCV_MSG, /* options */ 0, /* send size */ sizeof (struct msg), /* receive size */ currently_waiting_for, /* receive name */ MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); mach_really_waiting = 0; CHK ("mach_msg (receive)", ret); /* Check if we received a notify of the childs' death */ if (notify_server (&in_msg.header, &out_msg.header)) { /* If inferior_task is null then the inferior has gone away and we want to return to command level. Otherwise it was just an informative message and we need to look to see if there are any more. */ if (inferior_task != MACH_PORT_NULL) continue; else { /* Collect Unix exit status for gdb */ wait3 (&w, WNOHANG, 0); /* This mess is here to check that the rest of * gdb knows that the inferior died. It also * tries to hack around the fact that Mach 3.0 (mk69) * unix server (ux28) does not always know what * has happened to it's children when mach-magic * is applied on them. */ if ((!WIFEXITED (w) && WIFSTOPPED (w)) || (WIFEXITED (w) && WEXITSTATUS (w) > 0377)) { WSETEXIT (w, 0); warning ("Using exit value 0 for terminated task"); } else if (!WIFEXITED (w)) { int sig = WTERMSIG (w); /* Signals cause problems. Warn the user. */ if (sig != SIGKILL) /* Bad luck if garbage matches this */ warning ("The terminating signal stuff may be nonsense"); else if (sig > NSIG) { WSETEXIT (w, 0); warning ("Using exit value 0 for terminated task"); } } store_waitstatus (ourstatus, w); return inferior_pid; } } /* Hmm. Check for exception, as it was not a notification. exc_server() does an upcall to catch_exception_raise() if this rpc is an exception. Further actions are decided there. */ if (!exc_server (&in_msg.header, &out_msg.header)) { /* Not an exception, check for message. * Messages don't come from the inferior, or if they * do they better be asynchronous or it will hang. */ if (gdb_message_server (&in_msg.header)) continue; error ("Unrecognized message received in mach_really_wait"); } /* Send the reply of the exception rpc to the suspended task */ ret = mach_msg_send (&out_msg.header); CHK ("mach_msg_send (exc reply)", ret); if (stopped_in_exception) { /* Get unix state. May be changed in mach3_exception_actions() */ wait3 (&w, WNOHANG, 0); mach3_exception_actions (&w, FALSE, "Task"); store_waitstatus (ourstatus, w); return inferior_pid; } } } /* Called by macro DO_QUIT() in utils.c(quit). * This is called just before calling error() to return to command level */ void mach3_quit (void) { int mid; kern_return_t ret; if (mach_really_waiting) { ret = task_suspend (inferior_task); if (ret != KERN_SUCCESS) { warning ("Could not suspend task for interrupt: %s", mach_error_string (ret)); mach_really_waiting = 0; return; } } must_suspend_thread = 0; mach_really_waiting = 0; mid = map_port_name_to_mid (current_thread, MACH_TYPE_THREAD); if (mid == -1) { warning ("Selecting first existing kernel thread"); mid = 0; } current_thread = MACH_PORT_NULL; /* Force setup */ select_thread (inferior_task, mid, 1); return; } #if 0 /* bogus bogus bogus. It is NOT OK to quit out of target_wait. */ /* If ^C is typed when we are waiting for a message * and your Unix server is able to notice that we * should quit now. * * Called by REQUEST_QUIT() from utils.c(request_quit) */ void mach3_request_quit (void) { if (mach_really_waiting) immediate_quit = 1; } #endif /* * Gdb message server. * Currently implemented is the STOP message, that causes * gdb to return to the command level like ^C had been typed from terminal. */ int gdb_message_server (mach_msg_header_t *InP) { kern_return_t ret; int mid; if (InP->msgh_local_port == our_message_port) { /* A message coming to our_message_port. Check validity */ switch (InP->msgh_id) { case GDB_MESSAGE_ID_STOP: ret = task_suspend (inferior_task); if (ret != KERN_SUCCESS) warning ("Could not suspend task for stop message: %s", mach_error_string (ret)); /* QUIT in mach_really_wait() loop. */ request_quit (0); break; default: warning ("Invalid message id %d received, ignored.", InP->msgh_id); break; } return 1; } /* Message not handled by this server */ return 0; } /* NOTE: This is not an RPC call. It is a simpleroutine. * This is not called from this gdb code. * * It may be called by another debugger to cause this * debugger to enter command level: * * (gdb) set stop_inferior_gdb () * (gdb) continue * * External program "stop-gdb" implements this also. */ void stop_inferior_gdb (void) { kern_return_t ret; /* Code generated by mig, with minor cleanups :-) * simpleroutine stop_inferior_gdb (our_message_port : mach_port_t); */ typedef struct { mach_msg_header_t Head; } Request; Request Mess; register Request *InP = &Mess; InP->Head.msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_COPY_SEND, 0); /* msgh_size passed as argument */ InP->Head.msgh_remote_port = our_message_port; InP->Head.msgh_local_port = MACH_PORT_NULL; InP->Head.msgh_seqno = 0; InP->Head.msgh_id = GDB_MESSAGE_ID_STOP; ret = mach_msg (&InP->Head, MACH_SEND_MSG | MACH_MSG_OPTION_NONE, sizeof (Request), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); } #ifdef THREAD_ALLOWED_TO_BREAK /* * Return 1 if the MID specifies the thread that caused the * last exception. * Since catch_exception_raise() selects the thread causing * the last exception to current_thread, we just check that * it is selected and the last exception was a breakpoint. */ int mach_thread_for_breakpoint (int mid) { int cmid = map_port_name_to_mid (current_thread, MACH_TYPE_THREAD); if (mid < 0) { mid = map_slot_to_mid (-(mid + 1), 0, 0); if (mid < 0) return 0; /* Don't stop, no such slot */ } if (!mid || cmid == -1) return 1; /* stop */ return cmid == mid && stop_exception == EXC_BREAKPOINT; } #endif /* THREAD_ALLOWED_TO_BREAK */ #ifdef THREAD_PARSE_ID /* * Map a thread id string (MID or a @SLOTNUMBER) * to a thread-id. * * 0 matches all threads. * Otherwise the meaning is defined only in this file. * (mach_thread_for_breakpoint uses it) * * @@ This allows non-existent MIDs to be specified. * It now also allows non-existent slots to be * specified. (Slot numbers stored are negative, * and the magnitude is one greater than the actual * slot index. (Since 0 is reserved)) */ int mach_thread_parse_id (char *arg) { int mid; if (arg == 0) error ("thread id expected"); mid = parse_thread_id (arg, 0, 1); return mid; } #endif /* THREAD_PARSE_ID */ #ifdef THREAD_OUTPUT_ID char * mach_thread_output_id (int mid) { static char foobar[20]; if (mid > 0) sprintf (foobar, "mid %d", mid); else if (mid < 0) sprintf (foobar, "@%d", -(mid + 1)); else sprintf (foobar, "*any thread*"); return foobar; } #endif /* THREAD_OUTPUT_ID */ /* Called with hook PREPARE_TO_PROCEED() from infrun.c. * If we have switched threads and stopped at breakpoint return 1 otherwise 0. * * if SELECT_IT is nonzero, reselect the thread that was active when * we stopped at a breakpoint. * */ mach3_prepare_to_proceed (int select_it) { if (stop_thread && stop_thread != current_thread && stop_exception == EXC_BREAKPOINT) { int mid; if (!select_it) return 1; mid = switch_to_thread (stop_thread); return 1; } return 0; } /* this stuff here is an upcall via libmach/excServer.c and mach_really_wait which does the actual upcall. The code will pass the exception to the inferior if: - The task that signaled is not the inferior task (e.g. when debugging another debugger) - The user has explicitely requested to pass on the exceptions. (e.g to the default unix exception handler, which maps exceptions to signals, or the user has her own exception handler) - If the thread that signaled is being single-stepped and it has set it's own exception port and the exception is not EXC_BREAKPOINT. (Maybe this is not desirable?) */ kern_return_t catch_exception_raise (mach_port_t port, thread_t thread, task_t task, int exception, int code, int subcode) { kern_return_t ret; boolean_t signal_thread; int mid = map_port_name_to_mid (thread, MACH_TYPE_THREAD); if (!MACH_PORT_VALID (thread)) { /* If the exception was sent and thread dies before we receive it, THREAD will be MACH_PORT_DEAD */ current_thread = thread = MACH_PORT_NULL; error ("Received exception from nonexistent thread"); } /* Check if the task died in transit. * @@ Isn't the thread also invalid in such case? */ if (!MACH_PORT_VALID (task)) { current_thread = thread = MACH_PORT_NULL; error ("Received exception from nonexistent task"); } if (exception < 0 || exception > MAX_EXCEPTION) internal_error (__FILE__, __LINE__, "catch_exception_raise: unknown exception code %d thread %d", exception, mid); if (!MACH_PORT_VALID (inferior_task)) error ("got an exception, but inferior_task is null or dead"); stop_exception = exception; stop_code = code; stop_subcode = subcode; stop_thread = thread; signal_thread = exception != EXC_BREAKPOINT && port == singlestepped_thread_port && MACH_PORT_VALID (thread_saved_exception_port); /* If it was not our inferior or if we want to forward * the exception to the inferior's handler, do it here * * Note: If you have forwarded EXC_BREAKPOINT I trust you know why. */ if (task != inferior_task || signal_thread || exception_map[exception].forward) { mach_port_t eport = inferior_old_exception_port; if (signal_thread) { /* GDB now forwards the exeption to thread's original handler, since the user propably knows what he is doing. Give a message, though. */ mach3_exception_actions ((WAITTYPE *) NULL, TRUE, "Thread"); eport = thread_saved_exception_port; } /* Send the exception to the original handler */ ret = exception_raise (eport, thread, task, exception, code, subcode); (void) mach_port_deallocate (mach_task_self (), task); (void) mach_port_deallocate (mach_task_self (), thread); /* If we come here, we don't want to trace any more, since we * will never stop for tracing anyway. */ discard_single_step (thread); /* Do not stop the inferior */ return ret; } /* Now gdb handles the exception */ stopped_in_exception = TRUE; ret = task_suspend (task); CHK ("Error suspending inferior after exception", ret); must_suspend_thread = 0; if (current_thread != thread) { if (MACH_PORT_VALID (singlestepped_thread_port)) /* Cleanup discards single stepping */ error ("Exception from thread %d while singlestepping thread %d", mid, map_port_name_to_mid (current_thread, MACH_TYPE_THREAD)); /* Then select the thread that caused the exception */ if (select_thread (inferior_task, mid, 0) != KERN_SUCCESS) error ("Could not select thread %d causing exception", mid); else warning ("Gdb selected thread %d", mid); } /* If we receive an exception that is not breakpoint * exception, we interrupt the single step and return to * debugger. Trace condition is cleared. */ if (MACH_PORT_VALID (singlestepped_thread_port)) { if (stop_exception != EXC_BREAKPOINT) warning ("Single step interrupted by exception"); else if (port == singlestepped_thread_port) { /* Single step exception occurred, remove trace bit * and return to gdb. */ if (!MACH_PORT_VALID (current_thread)) error ("Single stepped thread is not valid"); /* Resume threads, but leave the task suspended */ resume_all_threads (0); } else warning ("Breakpoint while single stepping?"); discard_single_step (current_thread); } (void) mach_port_deallocate (mach_task_self (), task); (void) mach_port_deallocate (mach_task_self (), thread); return KERN_SUCCESS; } int port_valid (mach_port_t port, int mask) { kern_return_t ret; mach_port_type_t type; ret = mach_port_type (mach_task_self (), port, &type); if (ret != KERN_SUCCESS || (type & mask) != mask) return 0; return 1; } /* @@ No vm read cache implemented yet */ boolean_t vm_read_cache_valid = FALSE; /* * Read inferior task's LEN bytes from ADDR and copy it to MYADDR * in gdb's address space. * * Return 0 on failure; number of bytes read otherwise. */ int mach3_read_inferior (CORE_ADDR addr, char *myaddr, int length) { kern_return_t ret; vm_address_t low_address = (vm_address_t) trunc_page (addr); vm_size_t aligned_length = (vm_size_t) round_page (addr + length) - low_address; pointer_t copied_memory; int copy_count; /* Get memory from inferior with page aligned addresses */ ret = vm_read (inferior_task, low_address, aligned_length, &copied_memory, ©_count); if (ret != KERN_SUCCESS) { /* the problem is that the inferior might be killed for whatever reason * before we go to mach_really_wait. This is one place that ought to * catch many of those errors. * @@ A better fix would be to make all external events to GDB * to arrive via a SINGLE port set. (Including user input!) */ if (!port_valid (inferior_task, MACH_PORT_TYPE_SEND)) { m3_kill_inferior (); error ("Inferior killed (task port invalid)"); } else { #ifdef OSF extern int errno; /* valprint.c gives nicer format if this does not screw it. Eamonn seems to like this, so I enable it if OSF is defined... */ warning ("[read inferior %x failed: %s]", addr, mach_error_string (ret)); errno = 0; #endif return 0; } } memcpy (myaddr, (char *) addr - low_address + copied_memory, length); ret = vm_deallocate (mach_task_self (), copied_memory, copy_count); CHK ("mach3_read_inferior vm_deallocate failed", ret); return length; } #ifdef __STDC__ #define CHK_GOTO_OUT(str,ret) \ do if (ret != KERN_SUCCESS) { errstr = #str; goto out; } while(0) #else #define CHK_GOTO_OUT(str,ret) \ do if (ret != KERN_SUCCESS) { errstr = str; goto out; } while(0) #endif struct vm_region_list { struct vm_region_list *next; vm_prot_t protection; vm_address_t start; vm_size_t length; }; struct obstack region_obstack; /* * Write inferior task's LEN bytes from ADDR and copy it to MYADDR * in gdb's address space. */ int mach3_write_inferior (CORE_ADDR addr, char *myaddr, int length) { kern_return_t ret; vm_address_t low_address = (vm_address_t) trunc_page (addr); vm_size_t aligned_length = (vm_size_t) round_page (addr + length) - low_address; pointer_t copied_memory; int copy_count; int deallocate = 0; char *errstr = "Bug in mach3_write_inferior"; struct vm_region_list *region_element; struct vm_region_list *region_head = (struct vm_region_list *) NULL; /* Get memory from inferior with page aligned addresses */ ret = vm_read (inferior_task, low_address, aligned_length, &copied_memory, ©_count); CHK_GOTO_OUT ("mach3_write_inferior vm_read failed", ret); deallocate++; memcpy ((char *) addr - low_address + copied_memory, myaddr, length); obstack_init (®ion_obstack); /* Do writes atomically. * First check for holes and unwritable memory. */ { vm_size_t remaining_length = aligned_length; vm_address_t region_address = low_address; struct vm_region_list *scan; while (region_address < low_address + aligned_length) { vm_prot_t protection; vm_prot_t max_protection; vm_inherit_t inheritance; boolean_t shared; mach_port_t object_name; vm_offset_t offset; vm_size_t region_length = remaining_length; vm_address_t old_address = region_address; ret = vm_region (inferior_task, ®ion_address, ®ion_length, &protection, &max_protection, &inheritance, &shared, &object_name, &offset); CHK_GOTO_OUT ("vm_region failed", ret); /* Check for holes in memory */ if (old_address != region_address) { warning ("No memory at 0x%x. Nothing written", old_address); ret = KERN_SUCCESS; length = 0; goto out; } if (!(max_protection & VM_PROT_WRITE)) { warning ("Memory at address 0x%x is unwritable. Nothing written", old_address); ret = KERN_SUCCESS; length = 0; goto out; } /* Chain the regions for later use */ region_element = (struct vm_region_list *) obstack_alloc (®ion_obstack, sizeof (struct vm_region_list)); region_element->protection = protection; region_element->start = region_address; region_element->length = region_length; /* Chain the regions along with protections */ region_element->next = region_head; region_head = region_element; region_address += region_length; remaining_length = remaining_length - region_length; } /* If things fail after this, we give up. * Somebody is messing up inferior_task's mappings. */ /* Enable writes to the chained vm regions */ for (scan = region_head; scan; scan = scan->next) { boolean_t protection_changed = FALSE; if (!(scan->protection & VM_PROT_WRITE)) { ret = vm_protect (inferior_task, scan->start, scan->length, FALSE, scan->protection | VM_PROT_WRITE); CHK_GOTO_OUT ("vm_protect: enable write failed", ret); } } ret = vm_write (inferior_task, low_address, copied_memory, aligned_length); CHK_GOTO_OUT ("vm_write failed", ret); /* Set up the original region protections, if they were changed */ for (scan = region_head; scan; scan = scan->next) { boolean_t protection_changed = FALSE; if (!(scan->protection & VM_PROT_WRITE)) { ret = vm_protect (inferior_task, scan->start, scan->length, FALSE, scan->protection); CHK_GOTO_OUT ("vm_protect: enable write failed", ret); } } } out: if (deallocate) { obstack_free (®ion_obstack, 0); (void) vm_deallocate (mach_task_self (), copied_memory, copy_count); } if (ret != KERN_SUCCESS) { warning ("%s %s", errstr, mach_error_string (ret)); return 0; } return length; } /* Return 0 on failure, number of bytes handled otherwise. TARGET is ignored. */ static int m3_xfer_memory (CORE_ADDR memaddr, char *myaddr, int len, int write, struct target_ops *target) { int result; if (write) result = mach3_write_inferior (memaddr, myaddr, len); else result = mach3_read_inferior (memaddr, myaddr, len); return result; } 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 ("?"); } } static char * translate_cstate (int state) { switch (state) { case CPROC_RUNNING: return "R"; case CPROC_SWITCHING: return "S"; case CPROC_BLOCKED: return "B"; case CPROC_CONDWAIT: return "C"; case CPROC_CONDWAIT | CPROC_SWITCHING: return "CS"; default: return "?"; } } /* type == MACH_MSG_TYPE_COPY_SEND || type == MACH_MSG_TYPE_MAKE_SEND */ mach_port_t /* no mach_port_name_t found in include files. */ map_inferior_port_name (mach_port_t inferior_name, mach_msg_type_name_t type) { kern_return_t ret; mach_msg_type_name_t acquired; mach_port_t iport; ret = mach_port_extract_right (inferior_task, inferior_name, type, &iport, &acquired); CHK ("mach_port_extract_right (map_inferior_port_name)", ret); if (acquired != MACH_MSG_TYPE_PORT_SEND) error ("Incorrect right extracted, (map_inferior_port_name)"); ret = mach_port_deallocate (mach_task_self (), iport); CHK ("Deallocating mapped port (map_inferior_port_name)", ret); return iport; } /* * Naming convention: * Always return user defined name if found. * _K == A kernel thread with no matching CPROC * _C == A cproc with no current cthread * _t == A cthread with no user defined name * * The digits that follow the _names are the SLOT number of the * kernel thread if there is such a thing, otherwise just a negation * of the sequential number of such cprocs. */ static char buf[7]; static char * get_thread_name (gdb_thread_t one_cproc, int id) { if (one_cproc) if (one_cproc->cthread == NULL) { /* cproc not mapped to any cthread */ sprintf (buf, "_C%d", id); } else if (!one_cproc->cthread->name) { /* cproc and cthread, but no name */ sprintf (buf, "_t%d", id); } else return (char *) (one_cproc->cthread->name); else { if (id < 0) warning ("Inconsistency in thread name id %d", id); /* Kernel thread without cproc */ sprintf (buf, "_K%d", id); } return buf; } int fetch_thread_info (mach_port_t task, gdb_thread_t *mthreads_out) { kern_return_t ret; thread_array_t th_table; int th_count; gdb_thread_t mthreads = NULL; int index; ret = task_threads (task, &th_table, &th_count); if (ret != KERN_SUCCESS) { warning ("Error getting inferior's thread list:%s", mach_error_string (ret)); m3_kill_inferior (); return -1; } mthreads = (gdb_thread_t) obstack_alloc (cproc_obstack, th_count * sizeof (struct gdb_thread)); for (index = 0; index < th_count; index++) { thread_t saved_thread = MACH_PORT_NULL; int mid; if (must_suspend_thread) setup_thread (th_table[index], 1); if (th_table[index] != current_thread) { saved_thread = current_thread; mid = switch_to_thread (th_table[index]); } mthreads[index].name = th_table[index]; mthreads[index].cproc = NULL; /* map_cprocs_to_kernel_threads() */ mthreads[index].in_emulator = FALSE; mthreads[index].slotid = index; mthreads[index].sp = read_register (SP_REGNUM); mthreads[index].fp = read_register (FP_REGNUM); mthreads[index].pc = read_pc (); if (MACH_PORT_VALID (saved_thread)) mid = switch_to_thread (saved_thread); if (must_suspend_thread) setup_thread (th_table[index], 0); } consume_send_rights (th_table, th_count); ret = vm_deallocate (mach_task_self (), (vm_address_t) th_table, (th_count * sizeof (mach_port_t))); if (ret != KERN_SUCCESS) { warning ("Error trying to deallocate thread list : %s", mach_error_string (ret)); } *mthreads_out = mthreads; return th_count; } /* * Current emulator always saves the USP on top of * emulator stack below struct emul_stack_top stuff. */ CORE_ADDR fetch_usp_from_emulator_stack (CORE_ADDR sp) { CORE_ADDR stack_pointer; sp = (sp & ~(EMULATOR_STACK_SIZE - 1)) + EMULATOR_STACK_SIZE - sizeof (struct emul_stack_top); if (mach3_read_inferior (sp, &stack_pointer, sizeof (CORE_ADDR)) != sizeof (CORE_ADDR)) { warning ("Can't read user sp from emulator stack address 0x%x", sp); return 0; } return stack_pointer; } #ifdef MK67 /* get_emulation_vector() interface was changed after mk67 */ #define EMUL_VECTOR_COUNT 400 /* Value does not matter too much */ #endif /* MK67 */ /* Check if the emulator exists at task's address space. */ boolean_t have_emulator_p (task_t task) { kern_return_t ret; #ifndef EMUL_VECTOR_COUNT vm_offset_t *emulation_vector; int n; #else vm_offset_t emulation_vector[EMUL_VECTOR_COUNT]; int n = EMUL_VECTOR_COUNT; #endif int i; int vector_start; ret = task_get_emulation_vector (task, &vector_start, #ifndef EMUL_VECTOR_COUNT &emulation_vector, #else emulation_vector, #endif &n); CHK ("task_get_emulation_vector", ret); xx_debug ("%d vectors from %d at 0x%08x\n", n, vector_start, emulation_vector); for (i = 0; i < n; i++) { vm_offset_t entry = emulation_vector[i]; if (EMULATOR_BASE <= entry && entry <= EMULATOR_END) return TRUE; else if (entry) { static boolean_t informed = FALSE; if (!informed) { warning ("Emulation vector address 0x08%x outside emulator space", entry); informed = TRUE; } } } return FALSE; } /* Map cprocs to kernel threads and vice versa. */ void map_cprocs_to_kernel_threads (gdb_thread_t cprocs, gdb_thread_t mthreads, int thread_count) { int index; gdb_thread_t scan; boolean_t all_mapped = TRUE; LONGEST stack_base; LONGEST stack_size; for (scan = cprocs; scan; scan = scan->next) { /* Default to: no kernel thread for this cproc */ scan->reverse_map = -1; /* Check if the cproc is found by its stack */ for (index = 0; index < thread_count; index++) { stack_base = extract_signed_integer (scan->raw_cproc + CPROC_BASE_OFFSET, CPROC_BASE_SIZE); stack_size = extract_signed_integer (scan->raw_cproc + CPROC_SIZE_OFFSET, CPROC_SIZE_SIZE); if ((mthreads + index)->sp > stack_base && (mthreads + index)->sp <= stack_base + stack_size) { (mthreads + index)->cproc = scan; scan->reverse_map = index; break; } } all_mapped &= (scan->reverse_map != -1); } /* Check for threads that are currently in the emulator. * If so, they have a different stack, and the still unmapped * cprocs may well get mapped to these threads. * * If: * - cproc stack does not match any kernel thread stack pointer * - there is at least one extra kernel thread * that has no cproc mapped above. * - some kernel thread stack pointer points to emulator space * then we find the user stack pointer saved in the emulator * stack, and try to map that to the cprocs. * * Also set in_emulator for kernel threads. */ if (emulator_present) { for (index = 0; index < thread_count; index++) { CORE_ADDR emul_sp; CORE_ADDR usp; gdb_thread_t mthread = (mthreads + index); emul_sp = mthread->sp; if (mthread->cproc == NULL && EMULATOR_BASE <= emul_sp && emul_sp <= EMULATOR_END) { mthread->in_emulator = emulator_present; if (!all_mapped && cprocs) { usp = fetch_usp_from_emulator_stack (emul_sp); /* @@ Could be more accurate */ if (!usp) error ("Zero stack pointer read from emulator?"); /* Try to match this stack pointer to the cprocs that * don't yet have a kernel thread. */ for (scan = cprocs; scan; scan = scan->next) { /* Check is this unmapped CPROC stack contains * the user stack pointer saved in the * emulator. */ if (scan->reverse_map == -1) { stack_base = extract_signed_integer (scan->raw_cproc + CPROC_BASE_OFFSET, CPROC_BASE_SIZE); stack_size = extract_signed_integer (scan->raw_cproc + CPROC_SIZE_OFFSET, CPROC_SIZE_SIZE); if (usp > stack_base && usp <= stack_base + stack_size) { mthread->cproc = scan; scan->reverse_map = index; break; } } } } } } } } /* * Format of the thread_list command * * slot mid sel name emul ks susp cstate wired address */ #define TL_FORMAT "%-2.2s %5d%c %-10.10s %1.1s%s%-5.5s %-2.2s %-5.5s " #define TL_HEADER "\n@ MID Name KState CState Where\n" void print_tl_address (struct ui_file *stream, CORE_ADDR pc) { if (!lookup_minimal_symbol_by_pc (pc)) fprintf_filtered (stream, local_hex_format (), pc); else { extern int addressprint; extern int asm_demangle; int store = addressprint; addressprint = 0; print_address_symbolic (pc, stream, asm_demangle, ""); addressprint = store; } } /* For thread names, but also for gdb_message_port external name */ #define MAX_NAME_LEN 50 /* Returns the address of variable NAME or 0 if not found */ CORE_ADDR lookup_address_of_variable (char *name) { struct symbol *sym; CORE_ADDR symaddr = 0; struct minimal_symbol *msymbol; sym = lookup_symbol (name, (struct block *) NULL, VAR_NAMESPACE, (int *) NULL, (struct symtab **) NULL); if (sym) symaddr = SYMBOL_VALUE (sym); if (!symaddr) { msymbol = lookup_minimal_symbol (name, NULL, NULL); if (msymbol && msymbol->type == mst_data) symaddr = SYMBOL_VALUE_ADDRESS (msymbol); } return symaddr; } static gdb_thread_t get_cprocs (void) { gdb_thread_t cproc_head; gdb_thread_t cproc_copy; CORE_ADDR their_cprocs; char *buf; char *name; cthread_t cthread; CORE_ADDR symaddr; buf = alloca (TARGET_PTR_BIT / HOST_CHAR_BIT); symaddr = lookup_address_of_variable ("cproc_list"); if (!symaddr) { /* cproc_list is not in a file compiled with debugging symbols, but don't give up yet */ symaddr = lookup_address_of_variable ("cprocs"); if (symaddr) { static int informed = 0; if (!informed) { informed++; warning ("Your program is loaded with an old threads library."); warning ("GDB does not know the old form of threads"); warning ("so things may not work."); } } } /* Stripped or no -lthreads loaded or "cproc_list" is in wrong segment. */ if (!symaddr) return NULL; /* Get the address of the first cproc in the task */ if (!mach3_read_inferior (symaddr, buf, TARGET_PTR_BIT / HOST_CHAR_BIT)) error ("Can't read cproc master list at address (0x%x).", symaddr); their_cprocs = extract_address (buf, TARGET_PTR_BIT / HOST_CHAR_BIT); /* Scan the CPROCs in the task. CPROCs are chained with LIST field, not NEXT field, which chains mutexes, condition variables and queues */ cproc_head = NULL; while (their_cprocs != (CORE_ADDR) 0) { CORE_ADDR cproc_copy_incarnation; cproc_copy = (gdb_thread_t) obstack_alloc (cproc_obstack, sizeof (struct gdb_thread)); if (!mach3_read_inferior (their_cprocs, &cproc_copy->raw_cproc[0], CPROC_SIZE)) error ("Can't read next cproc at 0x%x.", their_cprocs); their_cprocs = extract_address (cproc_copy->raw_cproc + CPROC_LIST_OFFSET, CPROC_LIST_SIZE); cproc_copy_incarnation = extract_address (cproc_copy->raw_cproc + CPROC_INCARNATION_OFFSET, CPROC_INCARNATION_SIZE); if (cproc_copy_incarnation == (CORE_ADDR) 0) cproc_copy->cthread = NULL; else { /* This CPROC has an attached CTHREAD. Get its name */ cthread = (cthread_t) obstack_alloc (cproc_obstack, sizeof (struct cthread)); if (!mach3_read_inferior (cproc_copy_incarnation, cthread, sizeof (struct cthread))) error ("Can't read next thread at 0x%x.", cproc_copy_incarnation); cproc_copy->cthread = cthread; if (cthread->name) { name = (char *) obstack_alloc (cproc_obstack, MAX_NAME_LEN); if (!mach3_read_inferior (cthread->name, name, MAX_NAME_LEN)) error ("Can't read next thread's name at 0x%x.", cthread->name); cthread->name = name; } } /* insert in front */ cproc_copy->next = cproc_head; cproc_head = cproc_copy; } return cproc_head; } #ifndef FETCH_CPROC_STATE /* * Check if your machine does not grok the way this routine * fetches the FP,PC and SP of a cproc that is not * currently attached to any kernel thread (e.g. its cproc.context * field points to the place in stack where the context * is saved). * * If it doesn't, define your own routine. */ #define FETCH_CPROC_STATE(mth) mach3_cproc_state (mth) int mach3_cproc_state (gdb_thread_t mthread) { int context; if (!mthread || !mthread->cproc) return -1; context = extract_signed_integer (mthread->cproc->raw_cproc + CPROC_CONTEXT_OFFSET, CPROC_CONTEXT_SIZE); if (context == 0) return -1; mthread->sp = context + MACHINE_CPROC_SP_OFFSET; if (mach3_read_inferior (context + MACHINE_CPROC_PC_OFFSET, &mthread->pc, sizeof (CORE_ADDR)) != sizeof (CORE_ADDR)) { warning ("Can't read cproc pc from inferior"); return -1; } if (mach3_read_inferior (context + MACHINE_CPROC_FP_OFFSET, &mthread->fp, sizeof (CORE_ADDR)) != sizeof (CORE_ADDR)) { warning ("Can't read cproc fp from inferior"); return -1; } return 0; } #endif /* FETCH_CPROC_STATE */ void thread_list_command (void) { thread_basic_info_data_t ths; int thread_count; gdb_thread_t cprocs; gdb_thread_t scan; int index; char *name; char selected; char *wired; int infoCnt; kern_return_t ret; mach_port_t mid_or_port; gdb_thread_t their_threads; gdb_thread_t kthread; int neworder = 1; char *fmt = "There are %d kernel threads in task %d.\n"; int tmid = map_port_name_to_mid (inferior_task, MACH_TYPE_TASK); MACH_ERROR_NO_INFERIOR; thread_count = fetch_thread_info (inferior_task, &their_threads); if (thread_count == -1) return; if (thread_count == 1) fmt = "There is %d kernel thread in task %d.\n"; printf_filtered (fmt, thread_count, tmid); puts_filtered (TL_HEADER); cprocs = get_cprocs (); map_cprocs_to_kernel_threads (cprocs, their_threads, thread_count); for (scan = cprocs; scan; scan = scan->next) { int mid; char buf[10]; char slot[3]; int cproc_state = extract_signed_integer (scan->raw_cproc + CPROC_STATE_OFFSET, CPROC_STATE_SIZE); selected = ' '; /* a wired cproc? */ wired = (extract_address (scan->raw_cproc + CPROC_WIRED_OFFSET, CPROC_WIRED_SIZE) ? "wired" : ""); if (scan->reverse_map != -1) kthread = (their_threads + scan->reverse_map); else kthread = NULL; if (kthread) { /* These cprocs have a kernel thread */ mid = map_port_name_to_mid (kthread->name, MACH_TYPE_THREAD); infoCnt = THREAD_BASIC_INFO_COUNT; ret = thread_info (kthread->name, THREAD_BASIC_INFO, (thread_info_t) & ths, &infoCnt); if (ret != KERN_SUCCESS) { warning ("Unable to get basic info on thread %d : %s", mid, mach_error_string (ret)); continue; } /* Who is the first to have more than 100 threads */ sprintf (slot, "%d", kthread->slotid % 100); if (kthread->name == current_thread) selected = '*'; if (ths.suspend_count) sprintf (buf, "%d", ths.suspend_count); else buf[0] = '\000'; #if 0 if (ths.flags & TH_FLAGS_SWAPPED) strcat (buf, "S"); #endif if (ths.flags & TH_FLAGS_IDLE) strcat (buf, "I"); printf_filtered (TL_FORMAT, slot, mid, selected, get_thread_name (scan, kthread->slotid), kthread->in_emulator ? "E" : "", translate_state (ths.run_state), buf, translate_cstate (cproc_state), wired); print_tl_address (gdb_stdout, kthread->pc); } else { /* These cprocs don't have a kernel thread. * find out the calling frame with * FETCH_CPROC_STATE. */ struct gdb_thread state; #if 0 /* jtv -> emcmanus: why do you want this here? */ if (scan->incarnation == NULL) continue; /* EMcM */ #endif printf_filtered (TL_FORMAT, "-", -neworder, /* Pseudo MID */ selected, get_thread_name (scan, -neworder), "", "-", /* kernel state */ "", translate_cstate (cproc_state), ""); state.cproc = scan; if (FETCH_CPROC_STATE (&state) == -1) puts_filtered ("???"); else print_tl_address (gdb_stdout, state.pc); neworder++; } puts_filtered ("\n"); } /* Scan for kernel threads without cprocs */ for (index = 0; index < thread_count; index++) { if (!their_threads[index].cproc) { int mid; char buf[10]; char slot[3]; mach_port_t name = their_threads[index].name; mid = map_port_name_to_mid (name, MACH_TYPE_THREAD); infoCnt = THREAD_BASIC_INFO_COUNT; ret = thread_info (name, THREAD_BASIC_INFO, (thread_info_t) & ths, &infoCnt); if (ret != KERN_SUCCESS) { warning ("Unable to get basic info on thread %d : %s", mid, mach_error_string (ret)); continue; } sprintf (slot, "%d", index % 100); if (name == current_thread) selected = '*'; else selected = ' '; if (ths.suspend_count) sprintf (buf, "%d", ths.suspend_count); else buf[0] = '\000'; #if 0 if (ths.flags & TH_FLAGS_SWAPPED) strcat (buf, "S"); #endif if (ths.flags & TH_FLAGS_IDLE) strcat (buf, "I"); printf_filtered (TL_FORMAT, slot, mid, selected, get_thread_name (NULL, index), their_threads[index].in_emulator ? "E" : "", translate_state (ths.run_state), buf, "", /* No cproc state */ ""); /* Can't be wired */ print_tl_address (gdb_stdout, their_threads[index].pc); puts_filtered ("\n"); } } obstack_free (cproc_obstack, 0); obstack_init (cproc_obstack); } void thread_select_command (char *args, int from_tty) { int mid; thread_array_t thread_list; int thread_count; kern_return_t ret; int is_slot = 0; MACH_ERROR_NO_INFERIOR; if (!args) error_no_arg ("MID or @SLOTNUMBER to specify a thread to select"); while (*args == ' ' || *args == '\t') args++; if (*args == '@') { is_slot++; args++; } mid = atoi (args); if (mid == 0) if (!is_slot || *args != '0') /* Rudimentary checks */ error ("You must select threads by MID or @SLOTNUMBER"); if (select_thread (inferior_task, mid, is_slot ? 2 : 1) != KERN_SUCCESS) return; if (from_tty) printf_filtered ("Thread %d selected\n", is_slot ? map_port_name_to_mid (current_thread, MACH_TYPE_THREAD) : mid); } thread_trace (mach_port_t thread, boolean_t set) { int flavor = TRACE_FLAVOR; unsigned int stateCnt = TRACE_FLAVOR_SIZE; kern_return_t ret; thread_state_data_t state; if (!MACH_PORT_VALID (thread)) { warning ("thread_trace: invalid thread"); return; } if (must_suspend_thread) setup_thread (thread, 1); ret = thread_get_state (thread, flavor, state, &stateCnt); CHK ("thread_trace: error reading thread state", ret); if (set) { TRACE_SET (thread, state); } else { if (!TRACE_CLEAR (thread, state)) { if (must_suspend_thread) setup_thread (thread, 0); return; } } ret = thread_set_state (thread, flavor, state, stateCnt); CHK ("thread_trace: error writing thread state", ret); if (must_suspend_thread) setup_thread (thread, 0); } #ifdef FLUSH_INFERIOR_CACHE /* When over-writing code on some machines the I-Cache must be flushed explicitly, because it is not kept coherent by the lazy hardware. This definitely includes breakpoints, for instance, or else we end up looping in mysterious Bpt traps */ flush_inferior_icache (CORE_ADDR pc, int amount) { vm_machine_attribute_val_t flush = MATTR_VAL_ICACHE_FLUSH; kern_return_t ret; ret = vm_machine_attribute (inferior_task, pc, amount, MATTR_CACHE, &flush); if (ret != KERN_SUCCESS) warning ("Error flushing inferior's cache : %s", mach_error_string (ret)); } #endif /* FLUSH_INFERIOR_CACHE */ static suspend_all_threads (int from_tty) { kern_return_t ret; thread_array_t thread_list; int thread_count, index; int infoCnt; thread_basic_info_data_t th_info; ret = task_threads (inferior_task, &thread_list, &thread_count); if (ret != KERN_SUCCESS) { warning ("Could not suspend inferior threads."); m3_kill_inferior (); return_to_top_level (RETURN_ERROR); } for (index = 0; index < thread_count; index++) { int mid; mid = map_port_name_to_mid (thread_list[index], MACH_TYPE_THREAD); ret = thread_suspend (thread_list[index]); if (ret != KERN_SUCCESS) warning ("Error trying to suspend thread %d : %s", mid, mach_error_string (ret)); if (from_tty) { infoCnt = THREAD_BASIC_INFO_COUNT; ret = thread_info (thread_list[index], THREAD_BASIC_INFO, (thread_info_t) & th_info, &infoCnt); CHK ("suspend can't get thread info", ret); warning ("Thread %d suspend count is %d", mid, th_info.suspend_count); } } consume_send_rights (thread_list, thread_count); ret = vm_deallocate (mach_task_self (), (vm_address_t) thread_list, (thread_count * sizeof (int))); CHK ("Error trying to deallocate thread list", ret); } void thread_suspend_command (char *args, int from_tty) { kern_return_t ret; int mid; mach_port_t saved_thread; int infoCnt; thread_basic_info_data_t th_info; MACH_ERROR_NO_INFERIOR; if (!strcasecmp (args, "all")) { suspend_all_threads (from_tty); return; } saved_thread = current_thread; mid = parse_thread_id (args, 0, 0); if (mid < 0) error ("You can suspend only existing kernel threads with MID or @SLOTNUMBER"); if (mid == 0) mid = map_port_name_to_mid (current_thread, MACH_TYPE_THREAD); else if (select_thread (inferior_task, mid, 0) != KERN_SUCCESS) { if (current_thread) current_thread = saved_thread; error ("Could not select thread %d", mid); } ret = thread_suspend (current_thread); if (ret != KERN_SUCCESS) warning ("thread_suspend failed : %s", mach_error_string (ret)); infoCnt = THREAD_BASIC_INFO_COUNT; ret = thread_info (current_thread, THREAD_BASIC_INFO, (thread_info_t) & th_info, &infoCnt); CHK ("suspend can't get thread info", ret); warning ("Thread %d suspend count is %d", mid, th_info.suspend_count); current_thread = saved_thread; } resume_all_threads (int from_tty) { kern_return_t ret; thread_array_t thread_list; int thread_count, index; int mid; int infoCnt; thread_basic_info_data_t th_info; ret = task_threads (inferior_task, &thread_list, &thread_count); if (ret != KERN_SUCCESS) { m3_kill_inferior (); error ("task_threads", mach_error_string (ret)); } for (index = 0; index < thread_count; index++) { infoCnt = THREAD_BASIC_INFO_COUNT; ret = thread_info (thread_list[index], THREAD_BASIC_INFO, (thread_info_t) & th_info, &infoCnt); CHK ("resume_all can't get thread info", ret); mid = map_port_name_to_mid (thread_list[index], MACH_TYPE_THREAD); if (!th_info.suspend_count) { if (mid != -1 && from_tty) warning ("Thread %d is not suspended", mid); continue; } ret = thread_resume (thread_list[index]); if (ret != KERN_SUCCESS) warning ("Error trying to resume thread %d : %s", mid, mach_error_string (ret)); else if (mid != -1 && from_tty) warning ("Thread %d suspend count is %d", mid, --th_info.suspend_count); } consume_send_rights (thread_list, thread_count); ret = vm_deallocate (mach_task_self (), (vm_address_t) thread_list, (thread_count * sizeof (int))); CHK ("Error trying to deallocate thread list", ret); } void thread_resume_command (char *args, int from_tty) { int mid; mach_port_t saved_thread; kern_return_t ret; thread_basic_info_data_t th_info; int infoCnt = THREAD_BASIC_INFO_COUNT; MACH_ERROR_NO_INFERIOR; if (!strcasecmp (args, "all")) { resume_all_threads (from_tty); return; } saved_thread = current_thread; mid = parse_thread_id (args, 0, 0); if (mid < 0) error ("You can resume only existing kernel threads with MID or @SLOTNUMBER"); if (mid == 0) mid = map_port_name_to_mid (current_thread, MACH_TYPE_THREAD); else if (select_thread (inferior_task, mid, 0) != KERN_SUCCESS) { if (current_thread) current_thread = saved_thread; return_to_top_level (RETURN_ERROR); } ret = thread_info (current_thread, THREAD_BASIC_INFO, (thread_info_t) & th_info, &infoCnt); CHK ("resume can't get thread info", ret); if (!th_info.suspend_count) { warning ("Thread %d is not suspended", mid); goto out; } ret = thread_resume (current_thread); if (ret != KERN_SUCCESS) warning ("thread_resume failed : %s", mach_error_string (ret)); else { th_info.suspend_count--; warning ("Thread %d suspend count is %d", mid, th_info.suspend_count); } out: current_thread = saved_thread; } void thread_kill_command (char *args, int from_tty) { int mid; kern_return_t ret; int thread_count; thread_array_t thread_table; int index; mach_port_t thread_to_kill = MACH_PORT_NULL; MACH_ERROR_NO_INFERIOR; if (!args) error_no_arg ("thread mid to kill from the inferior task"); mid = parse_thread_id (args, 0, 0); if (mid < 0) error ("You can kill only existing kernel threads with MID or @SLOTNUMBER"); if (mid) { ret = machid_mach_port (mid_server, mid_auth, mid, &thread_to_kill); CHK ("thread_kill_command: machid_mach_port map failed", ret); } else mid = map_port_name_to_mid (current_thread, MACH_TYPE_THREAD); /* Don't allow gdb to kill *any* thread in the system. Use mkill program for that */ ret = task_threads (inferior_task, &thread_table, &thread_count); CHK ("Error getting inferior's thread list", ret); if (thread_to_kill == current_thread) { ret = thread_terminate (thread_to_kill); CHK ("Thread could not be terminated", ret); if (select_thread (inferior_task, 0, 1) != KERN_SUCCESS) warning ("Last thread was killed, use \"kill\" command to kill task"); } else for (index = 0; index < thread_count; index++) if (thread_table[index] == thread_to_kill) { ret = thread_terminate (thread_to_kill); CHK ("Thread could not be terminated", ret); } if (thread_count > 1) consume_send_rights (thread_table, thread_count); ret = vm_deallocate (mach_task_self (), (vm_address_t) thread_table, (thread_count * sizeof (mach_port_t))); CHK ("Error trying to deallocate thread list", ret); warning ("Thread %d killed", mid); } /* Task specific commands; add more if you like */ void task_resume_command (char *args, int from_tty) { kern_return_t ret; task_basic_info_data_t ta_info; int infoCnt = TASK_BASIC_INFO_COUNT; int mid = map_port_name_to_mid (inferior_task, MACH_TYPE_TASK); MACH_ERROR_NO_INFERIOR; /* Would be trivial to change, but is it desirable? */ if (args) error ("Currently gdb can resume only it's inferior task"); ret = task_info (inferior_task, TASK_BASIC_INFO, (task_info_t) & ta_info, &infoCnt); CHK ("task_resume_command: task_info failed", ret); if (ta_info.suspend_count == 0) error ("Inferior task %d is not suspended", mid); else if (ta_info.suspend_count == 1 && from_tty && !query ("Suspend count is now 1. Do you know what you are doing? ")) error ("Task not resumed"); ret = task_resume (inferior_task); CHK ("task_resume_command: task_resume", ret); if (ta_info.suspend_count == 1) { warning ("Inferior task %d is no longer suspended", mid); must_suspend_thread = 1; /* @@ This is not complete: Registers change all the time when not suspended! */ registers_changed (); } else warning ("Inferior task %d suspend count is now %d", mid, ta_info.suspend_count - 1); } void task_suspend_command (char *args, int from_tty) { kern_return_t ret; task_basic_info_data_t ta_info; int infoCnt = TASK_BASIC_INFO_COUNT; int mid = map_port_name_to_mid (inferior_task, MACH_TYPE_TASK); MACH_ERROR_NO_INFERIOR; /* Would be trivial to change, but is it desirable? */ if (args) error ("Currently gdb can suspend only it's inferior task"); ret = task_suspend (inferior_task); CHK ("task_suspend_command: task_suspend", ret); must_suspend_thread = 0; ret = task_info (inferior_task, TASK_BASIC_INFO, (task_info_t) & ta_info, &infoCnt); CHK ("task_suspend_command: task_info failed", ret); warning ("Inferior task %d suspend count is now %d", mid, ta_info.suspend_count); } static char * get_size (int bytes) { static char size[30]; int zz = bytes / 1024; if (zz / 1024) sprintf (size, "%-2.1f M", ((float) bytes) / (1024.0 * 1024.0)); else sprintf (size, "%d K", zz); return size; } /* Does this require the target task to be suspended?? I don't think so. */ void task_info_command (char *args, int from_tty) { int mid = -5; mach_port_t task; kern_return_t ret; task_basic_info_data_t ta_info; int infoCnt = TASK_BASIC_INFO_COUNT; int page_size = round_page (1); int thread_count = 0; if (MACH_PORT_VALID (inferior_task)) mid = map_port_name_to_mid (inferior_task, MACH_TYPE_TASK); task = inferior_task; if (args) { int tmid = atoi (args); if (tmid <= 0) error ("Invalid mid %d for task info", tmid); if (tmid != mid) { mid = tmid; ret = machid_mach_port (mid_server, mid_auth, tmid, &task); CHK ("task_info_command: machid_mach_port map failed", ret); } } if (mid < 0) error ("You have to give the task MID as an argument"); ret = task_info (task, TASK_BASIC_INFO, (task_info_t) & ta_info, &infoCnt); CHK ("task_info_command: task_info failed", ret); printf_filtered ("\nTask info for task %d:\n\n", mid); printf_filtered (" Suspend count : %d\n", ta_info.suspend_count); printf_filtered (" Base priority : %d\n", ta_info.base_priority); printf_filtered (" Virtual size : %s\n", get_size (ta_info.virtual_size)); printf_filtered (" Resident size : %s\n", get_size (ta_info.resident_size)); { thread_array_t thread_list; ret = task_threads (task, &thread_list, &thread_count); CHK ("task_info_command: task_threads", ret); printf_filtered (" Thread count : %d\n", thread_count); consume_send_rights (thread_list, thread_count); ret = vm_deallocate (mach_task_self (), (vm_address_t) thread_list, (thread_count * sizeof (int))); CHK ("Error trying to deallocate thread list", ret); } if (have_emulator_p (task)) printf_filtered (" Emulator at : 0x%x..0x%x\n", EMULATOR_BASE, EMULATOR_END); else printf_filtered (" No emulator.\n"); if (thread_count && task == inferior_task) printf_filtered ("\nUse the \"thread list\" command to see the threads\n"); } /* You may either FORWARD the exception to the inferior, or KEEP * it and return to GDB command level. * * exception mid [ forward | keep ] */ static void exception_command (char *args, int from_tty) { char *scan = args; int exception; int len; if (!args) error_no_arg ("exception number action"); while (*scan == ' ' || *scan == '\t') scan++; if ('0' <= *scan && *scan <= '9') while ('0' <= *scan && *scan <= '9') scan++; else error ("exception number action"); exception = atoi (args); if (exception <= 0 || exception > MAX_EXCEPTION) error ("Allowed exception numbers are in range 1..%d", MAX_EXCEPTION); if (*scan != ' ' && *scan != '\t') error ("exception number must be followed by a space"); else while (*scan == ' ' || *scan == '\t') scan++; args = scan; len = 0; while (*scan) { len++; scan++; } if (!len) error ("exception number action"); if (!strncasecmp (args, "forward", len)) exception_map[exception].forward = TRUE; else if (!strncasecmp (args, "keep", len)) exception_map[exception].forward = FALSE; else error ("exception action is either \"keep\" or \"forward\""); } static void print_exception_info (int exception) { boolean_t forward = exception_map[exception].forward; printf_filtered ("%s\t(%d): ", exception_map[exception].name, exception); if (!forward) if (exception_map[exception].sigmap != SIG_UNKNOWN) printf_filtered ("keep and handle as signal %d\n", exception_map[exception].sigmap); else printf_filtered ("keep and handle as unknown signal %d\n", exception_map[exception].sigmap); else printf_filtered ("forward exception to inferior\n"); } void exception_info (char *args, int from_tty) { int exception; if (!args) for (exception = 1; exception <= MAX_EXCEPTION; exception++) print_exception_info (exception); else { exception = atoi (args); if (exception <= 0 || exception > MAX_EXCEPTION) error ("Invalid exception number, values from 1 to %d allowed", MAX_EXCEPTION); print_exception_info (exception); } } /* Check for actions for mach exceptions. */ mach3_exception_actions (WAITTYPE *w, boolean_t force_print_only, char *who) { boolean_t force_print = FALSE; if (force_print_only || exception_map[stop_exception].sigmap == SIG_UNKNOWN) force_print = TRUE; else WSETSTOP (*w, exception_map[stop_exception].sigmap); if (exception_map[stop_exception].print || force_print) { target_terminal_ours (); printf_filtered ("\n%s received %s exception : ", who, exception_map[stop_exception].name); wrap_here (" "); switch (stop_exception) { case EXC_BAD_ACCESS: printf_filtered ("referencing address 0x%x : %s\n", stop_subcode, mach_error_string (stop_code)); break; case EXC_BAD_INSTRUCTION: printf_filtered ("illegal or undefined instruction. code %d subcode %d\n", stop_code, stop_subcode); break; case EXC_ARITHMETIC: printf_filtered ("code %d\n", stop_code); break; case EXC_EMULATION: printf_filtered ("code %d subcode %d\n", stop_code, stop_subcode); break; case EXC_SOFTWARE: printf_filtered ("%s specific, code 0x%x\n", stop_code < 0xffff ? "hardware" : "os emulation", stop_code); break; case EXC_BREAKPOINT: printf_filtered ("type %d (machine dependent)\n", stop_code); break; default: internal_error (__FILE__, __LINE__, "Unknown exception"); } } } setup_notify_port (int create_new) { kern_return_t ret; if (MACH_PORT_VALID (our_notify_port)) { ret = mach_port_destroy (mach_task_self (), our_notify_port); CHK ("Could not destroy our_notify_port", ret); } our_notify_port = MACH_PORT_NULL; notify_chain = (port_chain_t) NULL; port_chain_destroy (port_chain_obstack); if (create_new) { ret = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &our_notify_port); if (ret != KERN_SUCCESS) internal_error (__FILE__, __LINE__, "Creating notify port %s", mach_error_string (ret)); ret = mach_port_move_member (mach_task_self (), our_notify_port, inferior_wait_port_set); if (ret != KERN_SUCCESS) internal_error (__FILE__, __LINE__, "initial move member %s", mach_error_string (ret)); } } /* * Register our message port to the net name server * * Currently used only by the external stop-gdb program * since ^C does not work if you would like to enter * gdb command level while debugging your program. * * NOTE: If the message port is sometimes used for other * purposes also, the NAME must not be a guessable one. * Then, there should be a way to change it. */ char registered_name[MAX_NAME_LEN]; void message_port_info (char *args, int from_tty) { if (registered_name[0]) printf_filtered ("gdb's message port name: '%s'\n", registered_name); else printf_filtered ("gdb's message port is not currently registered\n"); } void gdb_register_port (char *name, mach_port_t port) { kern_return_t ret; static int already_signed = 0; int len; if (!MACH_PORT_VALID (port) || !name || !*name) { warning ("Invalid registration request"); return; } if (!already_signed) { ret = mach_port_insert_right (mach_task_self (), our_message_port, our_message_port, MACH_MSG_TYPE_MAKE_SEND); CHK ("Failed to create a signature to our_message_port", ret); already_signed = 1; } else if (already_signed > 1) { ret = netname_check_out (name_server_port, registered_name, our_message_port); CHK ("Failed to check out gdb's message port", ret); registered_name[0] = '\000'; already_signed = 1; } ret = netname_check_in (name_server_port, /* Name server port */ name, /* Name of service */ our_message_port, /* Signature */ port); /* Creates a new send right */ CHK ("Failed to check in the port", ret); len = 0; while (len < MAX_NAME_LEN && *(name + len)) { registered_name[len] = *(name + len); len++; } registered_name[len] = '\000'; already_signed = 2; } struct cmd_list_element *cmd_thread_list; struct cmd_list_element *cmd_task_list; /*ARGSUSED */ static void thread_command (char *arg, int from_tty) { printf_unfiltered ("\"thread\" must be followed by the name of a thread command.\n"); help_list (cmd_thread_list, "thread ", -1, gdb_stdout); } /*ARGSUSED */ static void task_command (char *arg, int from_tty) { printf_unfiltered ("\"task\" must be followed by the name of a task command.\n"); help_list (cmd_task_list, "task ", -1, gdb_stdout); } add_mach_specific_commands (void) { /* Thread handling commands */ /* FIXME: Move our thread support into the generic thread.c stuff so we can share that code. */ add_prefix_cmd ("mthread", class_stack, thread_command, "Generic command for handling Mach threads in the debugged task.", &cmd_thread_list, "thread ", 0, &cmdlist); add_com_alias ("th", "mthread", class_stack, 1); add_cmd ("select", class_stack, thread_select_command, "Select and print MID of the selected thread", &cmd_thread_list); add_cmd ("list", class_stack, thread_list_command, "List info of task's threads. Selected thread is marked with '*'", &cmd_thread_list); add_cmd ("suspend", class_run, thread_suspend_command, "Suspend one or all of the threads in the selected task.", &cmd_thread_list); add_cmd ("resume", class_run, thread_resume_command, "Resume one or all of the threads in the selected task.", &cmd_thread_list); add_cmd ("kill", class_run, thread_kill_command, "Kill the specified thread MID from inferior task.", &cmd_thread_list); #if 0 /* The rest of this support (condition_thread) was not merged. It probably should not be merged in this form, but instead added to the generic GDB thread support. */ add_cmd ("break", class_breakpoint, condition_thread, "Breakpoint N will only be effective for thread MID or @SLOT\n\ If MID/@SLOT is omitted allow all threads to break at breakpoint", &cmd_thread_list); #endif /* Thread command shorthands (for backward compatibility) */ add_alias_cmd ("ts", "mthread select", 0, 0, &cmdlist); add_alias_cmd ("tl", "mthread list", 0, 0, &cmdlist); /* task handling commands */ add_prefix_cmd ("task", class_stack, task_command, "Generic command for handling debugged task.", &cmd_task_list, "task ", 0, &cmdlist); add_com_alias ("ta", "task", class_stack, 1); add_cmd ("suspend", class_run, task_suspend_command, "Suspend the inferior task.", &cmd_task_list); add_cmd ("resume", class_run, task_resume_command, "Resume the inferior task.", &cmd_task_list); add_cmd ("info", no_class, task_info_command, "Print information about the specified task.", &cmd_task_list); /* Print my message port name */ add_info ("message-port", message_port_info, "Returns the name of gdb's message port in the netnameserver"); /* Exception commands */ add_info ("exceptions", exception_info, "What debugger does when program gets various exceptions.\n\ Specify an exception number as argument to print info on that\n\ exception only."); add_com ("exception", class_run, exception_command, "Specify how to handle an exception.\n\ Args are exception number followed by \"forward\" or \"keep\".\n\ `Forward' means forward the exception to the program's normal exception\n\ handler.\n\ `Keep' means reenter debugger if this exception happens, and GDB maps\n\ the exception to some signal (see info exception)\n\ Normally \"keep\" is used to return to GDB on exception."); } kern_return_t do_mach_notify_dead_name (mach_port_t notify, mach_port_t name) { kern_return_t kr = KERN_SUCCESS; /* Find the thing that notified */ port_chain_t element = port_chain_member (notify_chain, name); /* Take name of from unreceived dead name notification list */ notify_chain = port_chain_delete (notify_chain, name); if (!element) error ("Received a dead name notify from unchained port (0x%x)", name); switch (element->type) { case MACH_TYPE_THREAD: target_terminal_ours_for_output (); if (name == current_thread) { printf_filtered ("\nCurrent thread %d died", element->mid); current_thread = MACH_PORT_NULL; } else printf_filtered ("\nThread %d died", element->mid); break; case MACH_TYPE_TASK: target_terminal_ours_for_output (); if (name != inferior_task) printf_filtered ("Task %d died, but it was not the selected task", element->mid); else { printf_filtered ("Current task %d died", element->mid); mach_port_destroy (mach_task_self (), name); inferior_task = MACH_PORT_NULL; if (notify_chain) warning ("There were still unreceived dead_name_notifications???"); /* Destroy the old notifications */ setup_notify_port (0); } break; default: error ("Unregistered dead_name 0x%x notification received. Type is %d, mid is 0x%x", name, element->type, element->mid); break; } return KERN_SUCCESS; } kern_return_t do_mach_notify_msg_accepted (mach_port_t notify, mach_port_t name) { warning ("do_mach_notify_msg_accepted : notify %x, name %x", notify, name); return KERN_SUCCESS; } kern_return_t do_mach_notify_no_senders (mach_port_t notify, mach_port_mscount_t mscount) { warning ("do_mach_notify_no_senders : notify %x, mscount %x", notify, mscount); return KERN_SUCCESS; } kern_return_t do_mach_notify_port_deleted (mach_port_t notify, mach_port_t name) { warning ("do_mach_notify_port_deleted : notify %x, name %x", notify, name); return KERN_SUCCESS; } kern_return_t do_mach_notify_port_destroyed (mach_port_t notify, mach_port_t rights) { warning ("do_mach_notify_port_destroyed : notify %x, rights %x", notify, rights); return KERN_SUCCESS; } kern_return_t do_mach_notify_send_once (mach_port_t notify) { #ifdef DUMP_SYSCALL /* MANY of these are generated. */ warning ("do_mach_notify_send_once : notify %x", notify); #endif return KERN_SUCCESS; } /* Kills the inferior. It's gone when you call this */ static void kill_inferior_fast (void) { WAITTYPE w; if (inferior_pid == 0 || inferior_pid == 1) return; /* kill() it, since the Unix server does not otherwise notice when * killed with task_terminate(). */ if (inferior_pid > 0) kill (inferior_pid, SIGKILL); /* It's propably terminate already */ (void) task_terminate (inferior_task); inferior_task = MACH_PORT_NULL; current_thread = MACH_PORT_NULL; wait3 (&w, WNOHANG, 0); setup_notify_port (0); } static void m3_kill_inferior (void) { kill_inferior_fast (); target_mourn_inferior (); } /* Clean up after the inferior dies. */ static void m3_mourn_inferior (void) { unpush_target (&m3_ops); generic_mourn_inferior (); } /* Fork an inferior process, and start debugging it. */ static void m3_create_inferior (char *exec_file, char *allargs, char **env) { fork_inferior (exec_file, allargs, env, m3_trace_me, m3_trace_him, NULL, NULL); /* We are at the first instruction we care about. */ /* Pedal to the metal... */ proceed ((CORE_ADDR) -1, 0, 0); } /* Mark our target-struct as eligible for stray "run" and "attach" commands. */ static int m3_can_run (void) { return 1; } /* Mach 3.0 does not need ptrace for anything * Make sure nobody uses it on mach. */ ptrace (int a, int b, int c, int d) { error ("Lose, Lose! Somebody called ptrace\n"); } /* Resume execution of the inferior process. If STEP is nonzero, single-step it. If SIGNAL is nonzero, give it that signal. */ void m3_resume (int pid, int step, enum target_signal signal) { kern_return_t ret; if (step) { thread_basic_info_data_t th_info; unsigned int infoCnt = THREAD_BASIC_INFO_COUNT; /* There is no point in single stepping when current_thread * is dead. */ if (!MACH_PORT_VALID (current_thread)) error ("No thread selected; can not single step"); /* If current_thread is suspended, tracing it would never return. */ ret = thread_info (current_thread, THREAD_BASIC_INFO, (thread_info_t) & th_info, &infoCnt); CHK ("child_resume: can't get thread info", ret); if (th_info.suspend_count) error ("Can't trace a suspended thread. Use \"thread resume\" command to resume it"); } vm_read_cache_valid = FALSE; if (signal && inferior_pid > 0) /* Do not signal, if attached by MID */ kill (inferior_pid, target_signal_to_host (signal)); if (step) { suspend_all_threads (0); setup_single_step (current_thread, TRUE); ret = thread_resume (current_thread); CHK ("thread_resume", ret); } ret = task_resume (inferior_task); if (ret == KERN_FAILURE) warning ("Task was not suspended"); else CHK ("Resuming task", ret); /* HACK HACK This is needed by the multiserver system HACK HACK */ while ((ret = task_resume (inferior_task)) == KERN_SUCCESS) /* make sure it really runs */ ; /* HACK HACK This is needed by the multiserver system HACK HACK */ } #ifdef ATTACH_DETACH /* Start debugging the process with the given task */ void task_attach (task_t tid) { kern_return_t ret; inferior_task = tid; ret = task_suspend (inferior_task); CHK ("task_attach: task_suspend", ret); must_suspend_thread = 0; setup_notify_port (1); request_notify (inferior_task, MACH_NOTIFY_DEAD_NAME, MACH_TYPE_TASK); setup_exception_port (); emulator_present = have_emulator_p (inferior_task); attach_flag = 1; } /* Well, we can call error also here and leave the * target stack inconsistent. Sigh. * Fix this sometime (the only way to fail here is that * the task has no threads at all, which is rare, but * possible; or if the target task has died, which is also * possible, but unlikely, since it has been suspended. * (Someone must have killed it)) */ void attach_to_thread (void) { if (select_thread (inferior_task, 0, 1) != KERN_SUCCESS) error ("Could not select any threads to attach to"); } mid_attach (int mid) { kern_return_t ret; ret = machid_mach_port (mid_server, mid_auth, mid, &inferior_task); CHK ("mid_attach: machid_mach_port", ret); task_attach (inferior_task); return mid; } /* * Start debugging the process whose unix process-id is PID. * A negative "pid" value is legal and signifies a mach_id not a unix pid. * * Prevent (possible unwanted) dangerous operations by enabled users * like "atta 0" or "atta foo" (equal to the previous :-) and * "atta pidself". Anyway, the latter is allowed by specifying a MID. */ static int m3_do_attach (int pid) { kern_return_t ret; if (pid == 0) error ("MID=0, Debugging the master unix server does not compute"); /* Foo. This assumes gdb has a unix pid */ if (pid == getpid ()) error ("I will debug myself only by mid. (Gdb would suspend itself!)"); if (pid < 0) { mid_attach (-(pid)); /* inferior_pid will be NEGATIVE! */ inferior_pid = pid; return inferior_pid; } inferior_task = task_by_pid (pid); if (!MACH_PORT_VALID (inferior_task)) error ("Cannot map Unix pid %d to Mach task port", pid); task_attach (inferior_task); inferior_pid = pid; return inferior_pid; } /* Attach to process PID, then initialize for debugging it and wait for the trace-trap that results from attaching. */ static void m3_attach (char *args, int from_tty) { char *exec_file; int pid; if (!args) error_no_arg ("process-id to attach"); pid = atoi (args); if (pid == getpid ()) /* Trying to masturbate? */ error ("I refuse to debug myself!"); if (from_tty) { exec_file = (char *) get_exec_file (0); if (exec_file) printf_unfiltered ("Attaching to program `%s', %s\n", exec_file, target_pid_to_str (pid)); else printf_unfiltered ("Attaching to %s\n", target_pid_to_str (pid)); gdb_flush (gdb_stdout); } m3_do_attach (pid); inferior_pid = pid; push_target (&m3_ops); } void deallocate_inferior_ports (void) { kern_return_t ret; thread_array_t thread_list; int thread_count, index; if (!MACH_PORT_VALID (inferior_task)) return; ret = task_threads (inferior_task, &thread_list, &thread_count); if (ret != KERN_SUCCESS) { warning ("deallocate_inferior_ports: task_threads", mach_error_string (ret)); return; } /* Get rid of send rights to task threads */ for (index = 0; index < thread_count; index++) { int rights; ret = mach_port_get_refs (mach_task_self (), thread_list[index], MACH_PORT_RIGHT_SEND, &rights); CHK ("deallocate_inferior_ports: get refs", ret); if (rights > 0) { ret = mach_port_mod_refs (mach_task_self (), thread_list[index], MACH_PORT_RIGHT_SEND, -rights); CHK ("deallocate_inferior_ports: mod refs", ret); } } ret = mach_port_mod_refs (mach_task_self (), inferior_exception_port, MACH_PORT_RIGHT_RECEIVE, -1); CHK ("deallocate_inferior_ports: cannot get rid of exception port", ret); ret = mach_port_deallocate (mach_task_self (), inferior_task); CHK ("deallocate_task_port: deallocating inferior_task", ret); current_thread = MACH_PORT_NULL; inferior_task = MACH_PORT_NULL; } /* Stop debugging the process whose number is PID and continue it with signal number SIGNAL. SIGNAL = 0 means just continue it. */ static void m3_do_detach (int signal) { kern_return_t ret; MACH_ERROR_NO_INFERIOR; if (current_thread != MACH_PORT_NULL) { /* Store the gdb's view of the thread we are deselecting * before we detach. * @@ I am really not sure if this is ever needeed. */ target_prepare_to_store (); target_store_registers (-1); } ret = task_set_special_port (inferior_task, TASK_EXCEPTION_PORT, inferior_old_exception_port); CHK ("task_set_special_port", ret); /* Discard all requested notifications */ setup_notify_port (0); if (remove_breakpoints ()) warning ("Could not remove breakpoints when detaching"); if (signal && inferior_pid > 0) kill (inferior_pid, signal); /* the task might be dead by now */ (void) task_resume (inferior_task); deallocate_inferior_ports (); attach_flag = 0; } /* Take a program previously attached to and detaches it. The program resumes execution and will no longer stop on signals, etc. We'd better not have left any breakpoints in the program or it'll die when it hits one. For this to work, it may be necessary for the process to have been previously attached. It *might* work if the program was started via fork. */ static void m3_detach (char *args, int from_tty) { int siggnal = 0; if (from_tty) { char *exec_file = get_exec_file (0); if (exec_file == 0) exec_file = ""; printf_unfiltered ("Detaching from program: %s %s\n", exec_file, target_pid_to_str (inferior_pid)); gdb_flush (gdb_stdout); } if (args) siggnal = atoi (args); m3_do_detach (siggnal); inferior_pid = 0; unpush_target (&m3_ops); /* Pop out of handling an inferior */ } #endif /* ATTACH_DETACH */ /* Get ready to modify the registers array. On machines which store individual registers, this doesn't need to do anything. On machines which store all the registers in one fell swoop, this makes sure that registers contains all the registers from the program being debugged. */ static void m3_prepare_to_store (void) { #ifdef CHILD_PREPARE_TO_STORE CHILD_PREPARE_TO_STORE (); #endif } /* Print status information about what we're accessing. */ static void m3_files_info (struct target_ops *ignore) { /* FIXME: should print MID and all that crap. */ printf_unfiltered ("\tUsing the running image of %s %s.\n", attach_flag ? "attached" : "child", target_pid_to_str (inferior_pid)); } static void m3_open (char *arg, int from_tty) { error ("Use the \"run\" command to start a Unix child process."); } #ifdef DUMP_SYSCALL #ifdef __STDC__ #define STR(x) #x #else #define STR(x) "x" #endif char *bsd1_names[] = { "execve", "fork", "take_signal", "sigreturn", "getrusage", "chdir", "chroot", "open", "creat", "mknod", "link", "symlink", "unlink", "access", "stat", "readlink", "chmod", "chown", "utimes", "truncate", "rename", "mkdir", "rmdir", "xutimes", "mount", "umount", "acct", "setquota", "write_short", "write_long", "send_short", "send_long", "sendto_short", "sendto_long", "select", "task_by_pid", "recvfrom_short", "recvfrom_long", "setgroups", "setrlimit", "sigvec", "sigstack", "settimeofday", "adjtime", "setitimer", "sethostname", "bind", "accept", "connect", "setsockopt", "getsockopt", "getsockname", "getpeername", "init_process", "table_set", "table_get", "pioctl", "emulator_error", "readwrite", "share_wakeup", 0, "maprw_request_it", "maprw_release_it", "maprw_remap", "pid_by_task", }; int bsd1_nnames = sizeof (bsd1_names) / sizeof (bsd1_names[0]); char * name_str (int name, char *buf) { switch (name) { case MACH_MSG_TYPE_BOOLEAN: return "boolean"; case MACH_MSG_TYPE_INTEGER_16: return "short"; case MACH_MSG_TYPE_INTEGER_32: return "long"; case MACH_MSG_TYPE_CHAR: return "char"; case MACH_MSG_TYPE_BYTE: return "byte"; case MACH_MSG_TYPE_REAL: return "real"; case MACH_MSG_TYPE_STRING: return "string"; default: sprintf (buf, "%d", name); return buf; } } char * id_str (int id, char *buf) { char *p; if (id >= 101000 && id < 101000 + bsd1_nnames) { if (p = bsd1_names[id - 101000]) return p; } if (id == 102000) return "psignal_retry"; if (id == 100000) return "syscall"; sprintf (buf, "%d", id); return buf; } print_msg (mach_msg_header_t *mp) { char *fmt_x = "%20s : 0x%08x\n"; char *fmt_d = "%20s : %10d\n"; char *fmt_s = "%20s : %s\n"; char buf[100]; puts_filtered ("\n"); #define pr(fmt,h,x) printf_filtered(fmt,STR(x),(h).x) pr (fmt_x, (*mp), msgh_bits); pr (fmt_d, (*mp), msgh_size); pr (fmt_x, (*mp), msgh_remote_port); pr (fmt_x, (*mp), msgh_local_port); pr (fmt_d, (*mp), msgh_kind); printf_filtered (fmt_s, STR (msgh_id), id_str (mp->msgh_id, buf)); if (debug_level > 1) { char *p, *ep, *dp; int plen; p = (char *) mp; ep = p + mp->msgh_size; p += sizeof (*mp); for (; p < ep; p += plen) { mach_msg_type_t *tp; mach_msg_type_long_t *tlp; int name, size, number; tp = (mach_msg_type_t *) p; if (tp->msgt_longform) { tlp = (mach_msg_type_long_t *) tp; name = tlp->msgtl_name; size = tlp->msgtl_size; number = tlp->msgtl_number; plen = sizeof (*tlp); } else { name = tp->msgt_name; size = tp->msgt_size; number = tp->msgt_number; plen = sizeof (*tp); } printf_filtered ("name=%-16s size=%2d number=%7d inline=%d long=%d deal=%d\n", name_str (name, buf), size, number, tp->msgt_inline, tp->msgt_longform, tp->msgt_deallocate); dp = p + plen; if (tp->msgt_inline) { int l; l = size * number / 8; l = (l + sizeof (long) - 1) & ~((sizeof (long)) - 1); plen += l; print_data (dp, size, number); } else { plen += sizeof (int *); } printf_filtered ("plen=%d\n", plen); } } } print_data (char *p, int size, int number) { int *ip; short *sp; int i; switch (size) { case 8: for (i = 0; i < number; i++) { printf_filtered (" %02x", p[i]); } break; case 16: sp = (short *) p; for (i = 0; i < number; i++) { printf_filtered (" %04x", sp[i]); } break; case 32: ip = (int *) p; for (i = 0; i < number; i++) { printf_filtered (" %08x", ip[i]); } break; } puts_filtered ("\n"); } #endif /* DUMP_SYSCALL */ static void m3_stop (void) { error ("to_stop target function not implemented"); } static char * m3_pid_to_exec_file (int pid) { error ("to_pid_to_exec_file target function not implemented"); return NULL; /* To keep all compilers happy. */ } static void init_m3_ops (void) { m3_ops.to_shortname = "mach"; m3_ops.to_longname = "Mach child process"; m3_ops.to_doc = "Mach child process (started by the \"run\" command)."; m3_ops.to_open = m3_open; m3_ops.to_attach = m3_attach; m3_ops.to_detach = m3_detach; m3_ops.to_resume = m3_resume; m3_ops.to_wait = mach_really__wait; m3_ops.to_fetch_registers = fetch_inferior_registers; m3_ops.to_store_registers = store_inferior_registers; m3_ops.to_prepare_to_store = m3_prepare_to_store; m3_ops.to_xfer_memory = m3_xfer_memory; m3_ops.to_files_info = m3_files_info; m3_ops.to_insert_breakpoint = memory_insert_breakpoint; m3_ops.to_remove_breakpoint = memory_remove_breakpoint; m3_ops.to_terminal_init = terminal_init_inferior; m3_ops.to_terminal_inferior = terminal_inferior; m3_ops.to_terminal_ours_for_output = terminal_ours_for_output; m3_ops.to_terminal_ours = terminal_ours; m3_ops.to_terminal_info = child_terminal_info; m3_ops.to_kill = m3_kill_inferior; m3_ops.to_create_inferior = m3_create_inferior; m3_ops.to_mourn_inferior = m3_mourn_inferior; m3_ops.to_can_run = m3_can_run; m3_ops.to_stop = m3_stop; m3_ops.to_pid_to_exec_file = m3_pid_to_exec_file; m3_ops.to_stratum = process_stratum; m3_ops.to_has_all_memory = 1; m3_ops.to_has_memory = 1; m3_ops.to_has_stack = 1; m3_ops.to_has_registers = 1; m3_ops.to_has_execution = 1; m3_ops.to_magic = OPS_MAGIC; } void _initialize_m3_nat (void) { kern_return_t ret; init_m3_ops (); add_target (&m3_ops); ret = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_PORT_SET, &inferior_wait_port_set); if (ret != KERN_SUCCESS) internal_error (__FILE__, __LINE__, "initial port set %s", mach_error_string (ret)); /* mach_really_wait now waits for this */ currently_waiting_for = inferior_wait_port_set; ret = netname_look_up (name_server_port, hostname, "MachID", &mid_server); if (ret != KERN_SUCCESS) { mid_server = MACH_PORT_NULL; warning ("initialize machid: netname_lookup_up(MachID) : %s", mach_error_string (ret)); warning ("Some (most?) features disabled..."); } mid_auth = mach_privileged_host_port (); if (mid_auth == MACH_PORT_NULL) mid_auth = mach_task_self (); obstack_init (port_chain_obstack); ret = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &thread_exception_port); CHK ("Creating thread_exception_port for single stepping", ret); ret = mach_port_insert_right (mach_task_self (), thread_exception_port, thread_exception_port, MACH_MSG_TYPE_MAKE_SEND); CHK ("Inserting send right to thread_exception_port", ret); /* Allocate message port */ ret = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, &our_message_port); if (ret != KERN_SUCCESS) warning ("Creating message port %s", mach_error_string (ret)); else { char buf[MAX_NAME_LEN]; ret = mach_port_move_member (mach_task_self (), our_message_port, inferior_wait_port_set); if (ret != KERN_SUCCESS) warning ("message move member %s", mach_error_string (ret)); /* @@@@ No way to change message port name currently */ /* Foo. This assumes gdb has a unix pid */ sprintf (buf, "gdb-%d", getpid ()); gdb_register_port (buf, our_message_port); } /* Heap for thread commands */ obstack_init (cproc_obstack); add_mach_specific_commands (); }