/* Trace RPCs sent to selected ports Copyright (C) 1998, 1999, 2001, 2002, 2003, 2005, 2006 Free Software Foundation, Inc. This file is part of the GNU Hurd. The GNU Hurd 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, or (at your option) any later version. The GNU Hurd 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const char *argp_program_version = STANDARD_HURD_VERSION (rpctrace); #define STD_MSGIDS_DIR DATADIR "/msgids/" #define OPT_NOSTDINC -1 static const struct argp_option options[] = { {"output", 'o', "FILE", 0, "Send trace output to FILE instead of stderr."}, {"nostdinc", OPT_NOSTDINC, 0, 0, "Do not search inside the standard system directory, `" STD_MSGIDS_DIR "', for `.msgids' files."}, {"rpc-list", 'i', "FILE", 0, "Read FILE for assocations of message ID numbers to names."}, {0, 'I', "DIR", 0, "Add the directory DIR to the list of directories to be searched for files " "containing message ID numbers."}, {0} }; #define UNKNOWN_NAME MACH_PORT_NULL static const char args_doc[] = "COMMAND [ARG...]"; static const char doc[] = "Trace Mach Remote Procedure Calls."; /* The msgid_ihash table maps msgh_id values to names. */ struct msgid_info { char *name; char *subsystem; }; static void msgid_ihash_cleanup (void *element, void *arg) { struct msgid_info *info = element; free (info->name); free (info->subsystem); free (info); } static struct hurd_ihash msgid_ihash = HURD_IHASH_INITIALIZER (HURD_IHASH_NO_LOCP); /* all traced tasks. */ task_t *traced_tasks; size_t nb_traced_tasks; task_t unknown_task; void add_task (task_t task) { static size_t alloc_size = 100; if (traced_tasks == NULL) traced_tasks = malloc (sizeof (task_t) * alloc_size); else if (nb_traced_tasks == alloc_size) { alloc_size *= 2; traced_tasks = realloc (traced_tasks, alloc_size); } if (!traced_tasks) error (1, 0, "cannot allocate memory"); traced_tasks[nb_traced_tasks++] = task; } void print_tasks () { int i; for (i = 0; i < nb_traced_tasks; i++) { fprintf (stderr, "%d ", traced_tasks[i]); } fprintf (stderr, "\n"); } void remove_task (task_t task) { int i,j; for (i = 0, j = 0; j < nb_traced_tasks; j++) { traced_tasks[i] = traced_tasks[j]; if (traced_tasks[j] != task) i++; } nb_traced_tasks = i; } task_t get_task (int idx) { if (idx >= nb_traced_tasks) return -1; assert (traced_tasks); return traced_tasks[idx]; } /* Parse a file of RPC names and message IDs as output by mig's -list option: "subsystem base-id routine n request-id reply-id". Put each request-id value into `msgid_ihash' with the routine name as its value. */ static void parse_msgid_list (const char *filename) { FILE *fp; char *buffer = NULL; size_t bufsize = 0; unsigned int lineno = 0; char *name, *subsystem; unsigned int msgid; error_t err; fp = fopen (filename, "r"); if (fp == 0) { error (2, errno, "%s", filename); return; } while (getline (&buffer, &bufsize, fp) > 0) { ++lineno; if (buffer[0] == '#' || buffer[0] == '\0') continue; if (sscanf (buffer, "%as %*u %as %*u %u %*u\n", &subsystem, &name, &msgid) != 3) error (0, 0, "%s:%u: invalid format in RPC list file", filename, lineno); else { struct msgid_info *info = malloc (sizeof *info); if (info == 0) error (1, errno, "malloc"); info->name = name; info->subsystem = subsystem; err = hurd_ihash_add (&msgid_ihash, msgid, info); if (err) error (1, err, "hurd_ihash_add"); } } free (buffer); fclose (fp); } /* Look for a name describing MSGID. We check the table directly, and also check if this looks like the ID of a reply message whose request ID is already in the table. */ static const struct msgid_info * msgid_info (mach_msg_id_t msgid) { const struct msgid_info *info = hurd_ihash_find (&msgid_ihash, msgid); if (info == 0 && (msgid / 100) % 2 == 1) { /* This message ID is not in the table, and its number makes it what should be an RPC reply message ID. So look up the message ID of the corresponding RPC request and synthesize a name from that. Then stash that name in the table so the next time the lookup will match directly. */ info = hurd_ihash_find (&msgid_ihash, msgid - 100); if (info != 0) { struct msgid_info *reply_info = malloc (sizeof *info); if (reply_info != 0) { reply_info->subsystem = strdup (info->subsystem); reply_info->name = 0; asprintf (&reply_info->name, "%s-reply", info->name); hurd_ihash_add (&msgid_ihash, msgid, reply_info); info = reply_info; } else info = 0; } } return info; } static const char * msgid_name (mach_msg_id_t msgid) { const struct msgid_info *info = msgid_info (msgid); return info ? info->name : 0; } /* Return true if this message's data should be printed out. For a request message, that means the in parameters. For a reply messages, that means the return code and out parameters. */ static int msgid_display (const struct msgid_info *info) { return 1; } /* Return true if we should interpose on this RPC's reply port. If this returns false, we will pass the caller's original reply port through so we never see the reply message at all. */ static int msgid_trace_replies (const struct msgid_info *info) { return 1; } struct receive_info { char *name; /* null or a string describing this */ hurd_ihash_locp_t locp; /* position in the traced_names hash table */ mach_port_t portname; /* The port name in the owner task. */ task_t task; /* The task who has the right. */ mach_port_t forward; /* real port. */ struct receive_info *receive_right; /* other receive rights. */ struct send_info *next; /* The head of the send right list */ }; /* We keep one of these structures for each port right we are tracing. */ struct traced_info { struct port_info pi; mach_msg_type_name_t type; char *name; /* null or a string describing this */ }; struct send_info { struct traced_info pi; task_t task; /* The task who has the right. */ /* It is used to form the list of send rights for different tasks. * The head is the receive right. */ struct send_info *next; struct receive_info *receive_right; /* the corresponding receive right */ }; struct send_once_info { struct traced_info pi; mach_port_t forward; /* real port. */ struct send_once_info *nextfree; /* Link when on free list. */ }; #define INFO_SEND_ONCE(info) ((info)->type == MACH_MSG_TYPE_MOVE_SEND_ONCE) #define TRACED_INFO(info) ((struct traced_info *) info) #define SEND_INFO(info) ((struct send_info *) info) #define SEND_ONCE_INFO(info) ((struct send_once_info *) info) struct req_info { boolean_t valid; mach_msg_id_t req_id; mach_port_t reply_port; task_t from; task_t to; struct req_info *next; }; static struct req_info *req_head = NULL; static struct req_info * add_request (mach_msg_id_t req_id, mach_port_t reply_port, task_t from, task_t to) { struct req_info *req = malloc (sizeof (*req)); if (!req) error (1, 0, "cannot allocate memory"); req->req_id = req_id; req->from = from; req->to = to; req->reply_port = reply_port; req->valid = TRUE; req->next = req_head; req_head = req; return req; } static struct req_info * remove_request (mach_msg_id_t req_id, mach_port_t reply_port) { struct req_info **prev; struct req_info *req; prev = &req_head; while (*prev) { if ((*prev)->req_id == req_id && (*prev)->reply_port == reply_port) break; prev = &(*prev)->next; } if (*prev == NULL) return NULL; req = *prev; *prev = req->next; return req; } struct port_info *notify_pi; /* The list of receive wrappers, but only the ones for the traced tasks. */ struct receive_info *receive_right_list; static struct traced_info dummy_wrapper; static struct send_once_info *freelist; struct hurd_ihash traced_names = HURD_IHASH_INITIALIZER (offsetof (struct receive_info, locp)); struct port_class *traced_class; struct port_class *other_class; struct port_bucket *traced_bucket; FILE *ostream; /* These are the calls made from the tracing engine into the output formatting code. */ /* Called for a message that does not look like an RPC reply. The header has already been swapped into the sender's view with interposed ports. */ static void print_request_header (struct send_info *info, mach_msg_header_t *header); /* Called for a message that looks like an RPC reply. */ static void print_reply_header (struct send_once_info *info, mig_reply_header_t *header, struct req_info *req); /* Called for each data item (which might be an array). Always called after one of the above two. */ static void print_data (mach_msg_type_name_t type, const void *data, mach_msg_type_number_t nelt, mach_msg_type_number_t eltsize); /*** Mechanics of tracing messages and interposing on ports ***/ /* Create a new wrapper for the receive right. * The wrapper lives until the traced receive right dies. */ static struct receive_info * new_receive_wrapper (mach_port_t right, mach_port_t owner) { error_t err; struct receive_info *info; mach_port_t foo; info = malloc (sizeof (*info)); if (!info) error (1, 0, "cannot allocate memory"); info->forward = right; info->task = owner; info->portname = UNKNOWN_NAME; info->receive_right = NULL; info->next = NULL; if (owner != unknown_task) { info->receive_right = receive_right_list; receive_right_list = info; } info->name = 0; /* Request the dead-name notification, * so if the receive right is destroyed, * we can destroy the wrapper. */ err = mach_port_request_notification (mach_task_self (), right, MACH_NOTIFY_DEAD_NAME, 1, notify_pi->port_right, MACH_MSG_TYPE_MAKE_SEND_ONCE, &foo); assert_perror (err); mach_port_deallocate (mach_task_self (), foo); err = hurd_ihash_add (&traced_names, info->forward, info); assert_perror (err); return info; } static void destroy_receive_wrapper (struct receive_info *info) { struct send_info *send_wrapper; struct receive_info **prev; mach_port_deallocate (mach_task_self (), info->forward); /* Remove it from the receive right list. */ prev = &receive_right_list; while (*prev != info && *prev) prev = &((*prev)->receive_right); /* If we find the receive wrapper in the list. */ if (*prev) *prev = info->receive_right; send_wrapper = info->next; while (send_wrapper) { struct send_info *next = send_wrapper->next; /* We have to completely destroy the port, * so the tasks who have the send right can be notified. */ assert (TRACED_INFO (send_wrapper)->pi.weakrefcnt == 1); ports_port_deref_weak (send_wrapper); assert (TRACED_INFO (send_wrapper)->pi.refcnt == 1); ports_destroy_right (send_wrapper); send_wrapper = next; } hurd_ihash_locp_remove (&traced_names, info->locp); free (info); } /* Create a new wrapper port and do `ports_get_right' on it. * * The wrapper lives until there is no send right to it, * or the corresponding receive wrapper is destroyed. */ static struct send_info * new_send_wrapper (struct receive_info *receive, task_t task, mach_port_t *wrapper_right) { error_t err; struct send_info *info; /* Create a new wrapper port that forwards to *RIGHT. */ err = ports_create_port (traced_class, traced_bucket, sizeof *info, &info); assert_perror (err); TRACED_INFO (info)->name = 0; asprintf (&TRACED_INFO (info)->name, " %d<--%d(pid%d)", receive->forward, TRACED_INFO (info)->pi.port_right, task2pid (task)); TRACED_INFO (info)->type = MACH_MSG_TYPE_MOVE_SEND; info->task = task; info->receive_right = receive; info->next = receive->next; receive->next = info; ports_port_ref_weak (info); *wrapper_right = ports_get_right (info); ports_port_deref (info); return info; } /* Create a new wrapper port and do `ports_get_right' on it. */ static struct send_once_info * new_send_once_wrapper (mach_port_t right, mach_port_t *wrapper_right) { error_t err; struct send_once_info *info; /* Use a free send-once wrapper port if we have one. */ if (freelist) { info = freelist; freelist = info->nextfree; assert (TRACED_INFO (info)->pi.weakrefcnt == 0); } else { /* Create a new wrapper port that forwards to *RIGHT. */ err = ports_create_port (traced_class, traced_bucket, sizeof *info, &info); assert_perror (err); TRACED_INFO (info)->name = 0; } info->forward = right; TRACED_INFO (info)->type = MACH_MSG_TYPE_MOVE_SEND_ONCE; info->nextfree = NULL; /* Send-once rights never compare equal to any other right (even another send-once right), so there is no point in putting them in the reverse-lookup table. Since we never make send rights to this port, we don't want to use the normal libports mechanisms (ports_get_right) that are designed for send rights and no-senders notifications. Instead, we hold on to the initial hard ref to INFO until we receive a message on it. The kernel automatically sends a MACH_NOTIFY_SEND_ONCE message if the send-once right dies. */ *wrapper_right = TRACED_INFO (info)->pi.port_right; return info; } /* The function is called when the port_info is going to be destroyed. */ static void traced_clean (void *pi) { struct send_info **prev; struct send_info *info = pi; assert (TRACED_INFO (info)->type == MACH_MSG_TYPE_MOVE_SEND); free (TRACED_INFO (info)->name); if (info->receive_right) { /* Remove it from the send right list. */ prev = &info->receive_right->next; while (*prev != info && *prev) prev = &((*prev)->next); assert (*prev); *prev = info->next; info->next = NULL; info->receive_right = NULL; } } /* This gets called when a wrapper port has no hard refs (send rights), only weak refs. The only weak ref is the one held in the reverse-lookup hash table. */ static void traced_dropweak (void *pi) { struct send_info **prev; struct send_info *const info = pi; assert (TRACED_INFO (info)->type == MACH_MSG_TYPE_MOVE_SEND); free (TRACED_INFO (info)->name); TRACED_INFO (info)->name = 0; assert (TRACED_INFO (info)->pi.port_right); /* Remove it from the send right list. */ prev = &info->receive_right->next; assert (*prev); while (*prev != info && *prev) prev = &((*prev)->next); assert (*prev); *prev = info->next; /* If this is the last send wrapper, it means that our traced port * won't have any send rights. We notify the owner of the receive right * of that by deallocating the forward port. */ if (info->receive_right->next == NULL) destroy_receive_wrapper (info->receive_right); info->next = NULL; info->receive_right = NULL; ports_port_deref_weak (info); } boolean_t seen_receive_right (task_t task, mach_port_t name) { struct receive_info *info = receive_right_list; while (info) { if (info->task == task && info->portname == name) return TRUE; info = info->receive_right; } return FALSE; } /* This function is to find the receive right for the send right 'send' * among traced tasks. I assume that all receive rights are moved * under the control of rpctrace. * * Note: 'send' shouldn't be the send right to the wrapper. * * Note: the receive_info returned from the function * might not be the receive right in the traced tasks. * */ struct receive_info * discover_receive_right (mach_port_t send) { int i; error_t err; struct receive_info *info; info = hurd_ihash_find (&traced_names, send); /* If we have seen the send right or send once right. */ if (info /* If the receive right is in one of traced tasks, * but we don't know its name * (probably because the receive right has been moved), * we need to find it out. */ && !(info->task != unknown_task && info->portname == UNKNOWN_NAME)) return info; for (i = 0; i < nb_traced_tasks; i++) { int j; mach_port_t *portnames = NULL; mach_msg_type_number_t nportnames = 0; mach_port_type_t *porttypes = NULL; mach_msg_type_number_t nporttypes = 0; struct receive_info *receive_wrapper = NULL; task_t task = traced_tasks[i]; err = mach_port_names (task, &portnames, &nportnames, &porttypes, &nporttypes); assert_perror (err); for (j = 0; j < nportnames; j++) { mach_port_t send_right; mach_msg_type_name_t type; if (!(porttypes[j] & MACH_PORT_TYPE_RECEIVE) /* not a receive right */ || seen_receive_right (task, portnames[j])) continue; err = mach_port_extract_right (task, portnames[j], MACH_MSG_TYPE_MAKE_SEND, &send_right, &type); assert_perror (err); receive_wrapper = hurd_ihash_find (&traced_names, send_right); /* We have seen this send right before. */ if (receive_wrapper) mach_port_deallocate (mach_task_self (), send_right); else /* We haven't seen the receive right. * Now we have the send right to the port, * use it to create the receive wrapper. */ receive_wrapper = new_receive_wrapper (send_right, task); receive_wrapper->portname = portnames[j]; /* We have found the wrapper for the receive right. */ if (send_right == send) break; receive_wrapper = NULL; } if (portnames) vm_deallocate (mach_task_self (), (vm_address_t) portnames, nportnames * sizeof (*portnames)); if (porttypes) vm_deallocate (mach_task_self (), (vm_address_t) porttypes, nporttypes * sizeof (*porttypes)); if (receive_wrapper) return receive_wrapper; } return NULL; } /* get_send_wrapper searches for the wrapper of the send right for the target task. If it doesn't exist, create a new one. */ struct send_info *get_send_wrapper (struct receive_info *receive_wrapper, mach_port_t task, mach_port_t *right) { struct send_info *info = receive_wrapper->next; while (info) { if (info->task == task) { *right = ports_get_right (info); return info; } info = info->next; } /* no send wrapper is found. */ return new_send_wrapper (receive_wrapper, task, right); } /* Rewrite a port right in a message with an appropriate wrapper port. */ static char * rewrite_right (mach_port_t *right, mach_msg_type_name_t *type, struct req_info *req) { error_t err; struct receive_info *receive_wrapper; struct send_info *send_wrapper; task_t dest = unknown_task; task_t source = unknown_task; /* We can never do anything special with a null or dead port right. */ if (!MACH_PORT_VALID (*right)) return 0; if (req) { if (req->valid) /* it's a RPC request */ { source = req->from; dest = req->to; } else { source = req->to; dest = req->from; } } switch (*type) { case MACH_MSG_TYPE_PORT_SEND: assert (req); /* See if this is already one of our own wrapper ports. */ send_wrapper = ports_lookup_port (traced_bucket, *right, 0); if (send_wrapper) { /* This is a send right to one of our own wrapper ports. */ mach_port_deallocate (mach_task_self (), *right); /* eat msg ref */ /* If the send right is moved to the task with the receive right, * copy the send right in 'forward' of receive info to the destination. * Otherwise, copy the send right to the send wrapper. */ assert (send_wrapper->receive_right); if (dest == send_wrapper->receive_right->task) { *right = send_wrapper->receive_right->forward; err = mach_port_mod_refs (mach_task_self (), *right, MACH_PORT_RIGHT_SEND, +1); assert_perror (err); ports_port_deref (send_wrapper); } else { struct send_info *send_wrapper2 = get_send_wrapper (send_wrapper->receive_right, dest, right); ports_port_deref (send_wrapper); *type = MACH_MSG_TYPE_MAKE_SEND; send_wrapper = send_wrapper2; } return TRACED_INFO (send_wrapper)->name; } receive_wrapper = discover_receive_right (*right); if (receive_wrapper == NULL) { /* It's unusual to see an unknown send right from a traced task. * We ignore it. */ if (source != unknown_task) { error (0, 0, "get an unknown send right from process %d", task2pid (source)); return dummy_wrapper.name; } /* the receive right is owned by an unknown task. */ receive_wrapper = new_receive_wrapper (*right, unknown_task); mach_port_mod_refs (mach_task_self (), *right, MACH_PORT_RIGHT_SEND, 1); } /* if the send right is moved to the task with the receive right, * don't do anything. * Otherwise, we translate it into the one to the send wrapper. */ if (dest == receive_wrapper->task) return receive_wrapper->name; else { assert (*right == receive_wrapper->forward); mach_port_deallocate (mach_task_self (), *right); send_wrapper = get_send_wrapper (receive_wrapper, dest, right); *type = MACH_MSG_TYPE_MAKE_SEND; return TRACED_INFO (send_wrapper)->name; } case MACH_MSG_TYPE_PORT_SEND_ONCE: /* There is no way to know if this send-once right is to the same receive right as any other send-once or send right we have seen. Fortunately, it doesn't matter, since the recipient of the send-once right we pass along can't tell either. We always just make a new send-once wrapper object, that will trace the one message it receives, and then die. */ *type = MACH_MSG_TYPE_MAKE_SEND_ONCE; return TRACED_INFO (new_send_once_wrapper (*right, right))->name; case MACH_MSG_TYPE_PORT_RECEIVE: /* We have got a receive right, call it A. We will pass along a different receive right of our own, call it B. We ourselves will receive messages on A, trace them, and forward them on to B. If A is the receive right to a send right that we have wrapped, then B must be that wrapper receive right, moved from us to the intended receiver of A--that way it matches previous send rights to A that were sent through and replaced with our wrapper (B). If not, we create a new receive right. */ { assert (req); receive_wrapper = hurd_ihash_find (&traced_names, *right); if (receive_wrapper) { struct send_info *send_wrapper2; char *name; mach_port_t rr; /* The port has at least one send right - the one * in receive_wrapper->forward. * If the source task doesn't have the send right, the port * will be destroyed after we deallocate the only send right. */ /* We have to deallocate the send right in * receive_wrapper->forward before we import the port to * port_info. So the reference count in the port info will be 1, * if it doesn't have any other send rights. */ mach_port_deallocate (mach_task_self (), receive_wrapper->forward); err = ports_import_port (traced_class, traced_bucket, *right, sizeof *send_wrapper, &send_wrapper); assert_perror (err); TRACED_INFO (send_wrapper)->type = MACH_MSG_TYPE_MOVE_SEND; send_wrapper->task = source; TRACED_INFO (send_wrapper)->name = receive_wrapper->name; /* Initialize them in case that the source task doesn't * have the send right to the port, and the port will * be destroyed immediately. */ send_wrapper->receive_right = NULL; send_wrapper->next = NULL; ports_port_ref_weak (send_wrapper); ports_port_deref (send_wrapper); hurd_ihash_locp_remove (&traced_names, receive_wrapper->locp); send_wrapper2 = get_send_wrapper (receive_wrapper, dest, &rr); assert (TRACED_INFO (send_wrapper2)->pi.refcnt == 1); name = TRACED_INFO (send_wrapper2)->name; TRACED_INFO (send_wrapper2)->name = NULL; /* The port has been removed from the send wrapper, * so we cannot put it in the free list. */ ports_port_deref_weak (send_wrapper2); rr = ports_claim_right (send_wrapper2); /* Get us a send right that we will forward on. */ err = mach_port_insert_right (mach_task_self (), rr, rr, MACH_MSG_TYPE_MAKE_SEND); assert_perror (err); receive_wrapper->forward = rr; receive_wrapper->task = dest; if (dest != unknown_task) { receive_wrapper->receive_right = receive_right_list; receive_right_list = receive_wrapper; } /* the port name will be discovered * when we search for this receive right. */ receive_wrapper->portname = UNKNOWN_NAME; receive_wrapper->name = name; send_wrapper->receive_right = receive_wrapper; send_wrapper->next = receive_wrapper->next; receive_wrapper->next = send_wrapper; err = hurd_ihash_add (&traced_names, receive_wrapper->forward, receive_wrapper); assert_perror (err); *right = rr; } else { /* weird? no send right for the port. */ err = mach_port_insert_right (mach_task_self (), *right, *right, MACH_MSG_TYPE_MAKE_SEND); assert_perror (err); receive_wrapper = new_receive_wrapper (*right, dest); } return receive_wrapper->name; } default: assert (!"??? bogus port type from kernel!"); } return 0; } static void print_contents (mach_msg_header_t *inp, void *msg_buf_ptr, struct req_info *req) { error_t err; int first = 1; /* Process the message data, wrapping ports and printing data. */ while (msg_buf_ptr < (void *) inp + inp->msgh_size) { mach_msg_type_t *const type = msg_buf_ptr; mach_msg_type_long_t *const lt = (void *) type; void *data; mach_msg_type_number_t nelt; /* Number of data items. */ mach_msg_type_size_t eltsize; /* Bytes per item. */ mach_msg_type_name_t name; /* MACH_MSG_TYPE_* code */ if (!type->msgt_longform) { name = type->msgt_name; nelt = type->msgt_number; eltsize = type->msgt_size / 8; data = msg_buf_ptr = type + 1; } else { name = lt->msgtl_name; nelt = lt->msgtl_number; eltsize = lt->msgtl_size / 8; data = msg_buf_ptr = lt + 1; } if (!type->msgt_inline) { /* This datum is out-of-line, meaning the message actually contains a pointer to a vm_allocate'd region of data. */ data = *(void **) data; msg_buf_ptr += sizeof (void *); } else msg_buf_ptr += ((nelt * eltsize + sizeof(natural_t) - 1) & ~(sizeof(natural_t) - 1)); if (first) first = 0; else putc (' ', ostream); /* Note that MACH_MSG_TYPE_PORT_NAME does not indicate a port right. It indicates a port name, i.e. just an integer--and we don't know what task that port name is meaningful in. If it's meaningful in a traced task, then it refers to our intercepting port rather than the original port anyway. */ if (MACH_MSG_TYPE_PORT_ANY_RIGHT (name)) { /* These are port rights. Translate them into wrappers. */ mach_port_t *const portnames = data; mach_msg_type_number_t i; mach_msg_type_name_t newtypes[nelt]; int poly; assert (inp->msgh_bits & MACH_MSGH_BITS_COMPLEX); assert (eltsize == sizeof (mach_port_t)); poly = 0; for (i = 0; i < nelt; ++i) { char *str; newtypes[i] = name; str = rewrite_right (&portnames[i], &newtypes[i], req); putc ((i == 0 && nelt > 1) ? '{' : ' ', ostream); if (portnames[i] == MACH_PORT_NULL) fprintf (ostream, "(null)"); else if (portnames[i] == MACH_PORT_DEAD) fprintf (ostream, "(dead)"); else { if (str != 0) fprintf (ostream, "%s", str); else fprintf (ostream, "%3u", (unsigned int) portnames[i]); } if (i > 0 && newtypes[i] != newtypes[0]) poly = 1; } if (nelt > 1) putc ('}', ostream); if (poly) { if (name == MACH_MSG_TYPE_MOVE_SEND_ONCE) { /* Some of the new rights are MAKE_SEND_ONCE. Turn them all into MOVE_SEND_ONCE. */ for (i = 0; i < nelt; ++i) if (newtypes[i] == MACH_MSG_TYPE_MAKE_SEND_ONCE) { err = mach_port_insert_right (mach_task_self (), portnames[i], portnames[i], newtypes[i]); assert_perror (err); } else assert (newtypes[i] == MACH_MSG_TYPE_MOVE_SEND_ONCE); } else { for (i = 0; i < nelt; ++i) switch (newtypes[i]) { case MACH_MSG_TYPE_COPY_SEND: err = mach_port_mod_refs (mach_task_self (), portnames[i], MACH_PORT_RIGHT_SEND, +1); assert_perror (err); break; case MACH_MSG_TYPE_MAKE_SEND: err = mach_port_insert_right (mach_task_self (), portnames[i], portnames[i], newtypes[i]); assert_perror (err); break; default: assert (newtypes[i] == MACH_MSG_TYPE_MOVE_SEND); break; } name = MACH_MSG_TYPE_MOVE_SEND; } if (type->msgt_longform) lt->msgtl_name = name; else type->msgt_name = name; } else if (nelt > 0 && newtypes[0] != name) if (type->msgt_longform) lt->msgtl_name = newtypes[0]; else type->msgt_name = newtypes[0]; } else print_data (name, data, nelt, eltsize); } } static void wrap_all_threads (struct send_info *info) { struct send_info *thread_send_wrapper; struct receive_info *thread_receive_wrapper; thread_t *threads = NULL; size_t nthreads = 0; error_t err; err = task_threads (get_task(0), &threads, &nthreads); assert_perror (err); for (int i = 0; i < nthreads; i++) { thread_receive_wrapper = hurd_ihash_find (&traced_names, threads[i]); /* We haven't seen the port. */ if (thread_receive_wrapper == NULL) { mach_port_t new_thread_port; thread_receive_wrapper = new_receive_wrapper (threads[i], unknown_task); thread_send_wrapper = new_send_wrapper (thread_receive_wrapper, info->task, &new_thread_port); free (TRACED_INFO (thread_send_wrapper)->name); asprintf (&TRACED_INFO (thread_send_wrapper)->name, "thread%d(pid%d)", threads[i], task2pid (info->task)); err = mach_port_insert_right (mach_task_self (), new_thread_port, new_thread_port, MACH_MSG_TYPE_MAKE_SEND); assert_perror (err); err = thread_set_kernel_port (threads[i], new_thread_port); assert_perror (err); mach_port_deallocate (mach_task_self (), new_thread_port); } } munmap (threads, nthreads * sizeof (thread_t)); } static void wrap_new_thread (mach_msg_header_t *inp, struct req_info *req) { error_t err; mach_port_t thread_port; struct { mach_msg_header_t head; mach_msg_type_t retcode_type; kern_return_t retcode; mach_msg_type_t child_thread_type; mach_port_t child_thread; } *reply = (void *) inp; struct send_info *send_wrapper = ports_lookup_port (traced_bucket, reply->child_thread, 0); assert (send_wrapper); assert (send_wrapper->receive_right); err = mach_port_insert_right (mach_task_self (), reply->child_thread, reply->child_thread, MACH_MSG_TYPE_MAKE_SEND); assert_perror (err); thread_port = send_wrapper->receive_right->forward; err = thread_set_kernel_port (thread_port, reply->child_thread); assert_perror (err); free (TRACED_INFO (send_wrapper)->name); asprintf (&TRACED_INFO (send_wrapper)->name, "thread%d(pid%d)", thread_port, task2pid (req->from)); mach_port_deallocate (mach_task_self (), reply->child_thread); ports_port_deref (send_wrapper); } static void wrap_new_task (mach_msg_header_t *inp, struct req_info *req) { error_t err; pid_t pid; task_t pseudo_task_port; task_t task_port; struct { mach_msg_header_t head; mach_msg_type_t retcode_type; kern_return_t retcode; mach_msg_type_t child_task_type; mach_port_t child_task; } *reply = (void *) inp; /* The send wrapper of the new task for the father task */ struct send_info *task_wrapper1 = ports_lookup_port (traced_bucket, reply->child_task, 0); /* The send wrapper for the new task itself. */ struct send_info *task_wrapper2; assert (task_wrapper1); assert (task_wrapper1->receive_right); task_port = task_wrapper1->receive_right->forward; add_task (task_port); task_wrapper2 = new_send_wrapper (task_wrapper1->receive_right, task_port, &pseudo_task_port); err = mach_port_insert_right (mach_task_self (), pseudo_task_port, pseudo_task_port, MACH_MSG_TYPE_MAKE_SEND); assert_perror (err); err = task_set_kernel_port (task_port, pseudo_task_port); assert_perror (err); mach_port_deallocate (mach_task_self (), pseudo_task_port); pid = task2pid (task_port); free (TRACED_INFO (task_wrapper1)->name); asprintf (&TRACED_INFO (task_wrapper1)->name, "task%d(pid%d)", task_port, task2pid (req->from)); free (TRACED_INFO (task_wrapper2)->name); asprintf (&TRACED_INFO (task_wrapper2)->name, "task%d(pid%d)", task_port, pid); } int trace_and_forward (mach_msg_header_t *inp, mach_msg_header_t *outp) { mach_port_t reply_port; const mach_msg_type_t RetCodeType = { MACH_MSG_TYPE_INTEGER_32, /* msgt_name = */ 32, /* msgt_size = */ 1, /* msgt_number = */ TRUE, /* msgt_inline = */ FALSE, /* msgt_longform = */ FALSE, /* msgt_deallocate = */ 0 /* msgt_unused = */ }; error_t err; const struct msgid_info *msgid; struct traced_info *info; mach_msg_bits_t complex; /* Look up our record for the receiving port. There is no need to check the class, because our port bucket only ever contains one class of ports (traced_class). */ info = ports_lookup_port (traced_bucket, inp->msgh_local_port, 0); assert (info); /* A notification message from the kernel appears to have been sent with a send-once right, even if there have never really been any. */ if (MACH_MSGH_BITS_LOCAL (inp->msgh_bits) == MACH_MSG_TYPE_MOVE_SEND_ONCE) { if (inp->msgh_id == MACH_NOTIFY_DEAD_NAME && info == (void *) notify_pi) { struct receive_info *receive_wrapper; const mach_dead_name_notification_t *const n = (void *) inp; /* Deallocate extra ref allocated by the notification. */ mach_port_deallocate (mach_task_self (), n->not_port); receive_wrapper = hurd_ihash_find (&traced_names, n->not_port); /* The receive wrapper has been destroyed. */ if (receive_wrapper) { assert (n->not_port == receive_wrapper->forward); destroy_receive_wrapper (receive_wrapper); } ((mig_reply_header_t *) outp)->RetCode = MIG_NO_REPLY; ports_port_deref (info); /* It might be a task port. Remove the dead task from the list. */ remove_task (n->not_port); return 1; } else if (inp->msgh_id == MACH_NOTIFY_NO_SENDERS && !INFO_SEND_ONCE (info)) { /* No more senders for a send right we are tracing. Now INFO will die, and we will release the tracee send right so it too can see a no-senders notification. */ mach_no_senders_notification_t *n = (void *) inp; ports_no_senders (info, n->not_count); ports_port_deref (info); ((mig_reply_header_t *) outp)->RetCode = MIG_NO_REPLY; return 1; } /* Get some unexpected notification for rpctrace itself, * TODO ignore them for now. */ else if (info == (void *) notify_pi) { ports_port_deref (info); ((mig_reply_header_t *) outp)->RetCode = MIG_NO_REPLY; return 1; } } assert (info != (void *) notify_pi); assert (MACH_MSGH_BITS_LOCAL (inp->msgh_bits) == info->type); complex = inp->msgh_bits & MACH_MSGH_BITS_COMPLEX; msgid = msgid_info (inp->msgh_id); /* Swap the header data like a crossover cable. */ { mach_msg_type_name_t this_type = MACH_MSGH_BITS_LOCAL (inp->msgh_bits); mach_msg_type_name_t reply_type = MACH_MSGH_BITS_REMOTE (inp->msgh_bits); /* Save the original reply port in the RPC request. */ reply_port = inp->msgh_remote_port; inp->msgh_local_port = inp->msgh_remote_port; if (reply_type && msgid_trace_replies (msgid) /* The reply port might be dead, e.g., the traced task has died. */ && MACH_PORT_VALID (inp->msgh_local_port)) { struct send_once_info *info; // TODO is the reply port always a send once right? assert (reply_type == MACH_MSG_TYPE_PORT_SEND_ONCE); info = new_send_once_wrapper (inp->msgh_local_port, &inp->msgh_local_port); reply_type = MACH_MSG_TYPE_MAKE_SEND_ONCE; assert (inp->msgh_local_port); if (TRACED_INFO (info)->name == 0) { if (msgid == 0) asprintf (&TRACED_INFO (info)->name, "reply(%u:%u)", (unsigned int) TRACED_INFO (info)->pi.port_right, (unsigned int) inp->msgh_id); else asprintf (&TRACED_INFO (info)->name, "reply(%u:%s)", (unsigned int) TRACED_INFO (info)->pi.port_right, msgid->name); } } if (info->type == MACH_MSG_TYPE_MOVE_SEND_ONCE) inp->msgh_remote_port = SEND_ONCE_INFO (info)->forward; else { assert (SEND_INFO (info)->receive_right); inp->msgh_remote_port = SEND_INFO (info)->receive_right->forward; } if (this_type == MACH_MSG_TYPE_MOVE_SEND_ONCE) { /* We have a message to forward for a send-once wrapper object. Since each wrapper object only lives for a single message, this one can be reclaimed now. We continue to hold a hard ref to the ports object, but we know that nothing else refers to it now, and we are consuming its `forward' right in the message we send. */ free (info->name); info->name = 0; SEND_ONCE_INFO (info)->forward = 0; SEND_ONCE_INFO (info)->nextfree = freelist; freelist = SEND_ONCE_INFO (info); } else this_type = MACH_MSG_TYPE_COPY_SEND; inp->msgh_bits = complex | MACH_MSGH_BITS (this_type, reply_type); } /* The message now appears as it would if we were the sender. It is ready to be resent. */ if (msgid_display (msgid)) { if (inp->msgh_local_port == MACH_PORT_NULL && info->type == MACH_MSG_TYPE_MOVE_SEND_ONCE && inp->msgh_size >= sizeof (mig_reply_header_t) && (*(int *) &((mig_reply_header_t *) inp)->RetCodeType == *(int *)&RetCodeType) /* The notification message is considered as a request. */ && (inp->msgh_id > 72 || inp->msgh_id < 64)) { struct req_info *req = remove_request (inp->msgh_id - 100, inp->msgh_remote_port); assert (req); req->valid = FALSE; /* This sure looks like an RPC reply message. */ mig_reply_header_t *rh = (void *) inp; print_reply_header ((struct send_once_info *) info, rh, req); putc (' ', ostream); fflush (ostream); print_contents (&rh->Head, rh + 1, req); putc ('\n', ostream); if (inp->msgh_id == 2161)/* the reply message for thread_create */ wrap_new_thread (inp, req); else if (inp->msgh_id == 2107) /* for task_create */ wrap_new_task (inp, req); free (req); } else { task_t to = 0; struct req_info *req = NULL; /* Print something about the message header. */ print_request_header ((struct send_info *) info, inp); fflush (ostream); /* It's a nofication message. */ if (inp->msgh_id <= 72 && inp->msgh_id >= 64) { assert (info->type == MACH_MSG_TYPE_MOVE_SEND_ONCE); /* mach_notify_port_destroyed message has a port, * TODO how do I handle it? */ assert (inp->msgh_id != 69); } /* If it's mach_port RPC, * the port rights in the message will be moved to the target task. */ else if (inp->msgh_id >= 3200 && inp->msgh_id <= 3218) to = SEND_INFO (info)->receive_right->forward; else to = SEND_INFO (info)->receive_right->task; if (info->type == MACH_MSG_TYPE_MOVE_SEND) req = add_request (inp->msgh_id, reply_port, SEND_INFO (info)->task, to); /* If it's the notification message, req is NULL. * TODO again, it's difficult to handle mach_notify_port_destroyed */ print_contents (inp, inp + 1, req); if (inp->msgh_local_port == MACH_PORT_NULL) /* simpleroutine */ { /* If it's a simpleroutine, * we don't need the request information any more. */ req = remove_request (inp->msgh_id, reply_port); free (req); fprintf (ostream, ");\n"); } else /* Leave a partial line that will be finished later. */ fprintf (ostream, ")"); /* If it's the request of exec_startup_get_info, * it means that the traced process starts to run */ if (inp->msgh_id == 30500) wrap_all_threads ((struct send_info *) info); } } /* Resend the message to the tracee. */ err = mach_msg (inp, MACH_SEND_MSG, inp->msgh_size, 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); if (err == MACH_SEND_INVALID_DEST) { /* The tracee port died. No doubt we are about to receive the dead-name notification. */ /* XXX MAKE_SEND, MAKE_SEND_ONCE rights in msg not handled */ mach_msg_destroy (inp); } else assert_perror (err); ports_port_deref (info); /* We already sent the message, so the server loop shouldn't do it again. */ ((mig_reply_header_t *) outp)->RetCode = MIG_NO_REPLY; return 1; } /* This function runs in the tracing thread and drives all the tracing. */ static any_t trace_thread_function (void *arg) { struct port_bucket *const bucket = arg; ports_manage_port_operations_one_thread (bucket, trace_and_forward, 0); return 0; } /*** Output formatting ***/ #if 0 struct msg_type { const char *name; const char *letter; }; static const char *const msg_types[] = { [MACH_MSG_TYPE_BIT] = {"bool", "b"}, [MACH_MSG_TYPE_INTEGER_16] = {"int16", "h"}, [MACH_MSG_TYPE_INTEGER_32] = {"int32", "i"}, [MACH_MSG_TYPE_CHAR] = {"char", "c"}, [MACH_MSG_TYPE_INTEGER_8] = {"int8", "B"}, [MACH_MSG_TYPE_REAL] = {"float", "f"}, [MACH_MSG_TYPE_INTEGER_64] = {"int64", "q"}, [MACH_MSG_TYPE_STRING] = {"string", "s"}, [MACH_MSG_TYPE_MOVE_RECEIVE] = {"move-receive", "R"}, [MACH_MSG_TYPE_MOVE_SEND] = {"move-send", "S"}, [MACH_MSG_TYPE_MOVE_SEND_ONCE]= {"move-send-once", "O"}, [MACH_MSG_TYPE_COPY_SEND] = {"copy-send", "s"}, [MACH_MSG_TYPE_MAKE_SEND] = {"make-send", ""}, [MACH_MSG_TYPE_MAKE_SEND_ONCE]= {"make-send-once", ""}, [MACH_MSG_TYPE_PORT_NAME] = {"port-name", "n"}, }; #endif static void print_request_header (struct send_info *receiver, mach_msg_header_t *msg) { const char *msgname = msgid_name (msg->msgh_id); if (TRACED_INFO (receiver)->name != 0) fprintf (ostream, "%4s->", TRACED_INFO (receiver)->name); else fprintf (ostream, "%4u->", (unsigned int) TRACED_INFO (receiver)->pi.port_right); if (msgname != 0) fprintf (ostream, "%5s (", msgname); else fprintf (ostream, "%5u (", (unsigned int) msg->msgh_id); } static void print_reply_header (struct send_once_info *info, mig_reply_header_t *reply, struct req_info *req) { /* We have printed a partial line for the request message, and now we have the corresponding reply. */ if (reply->Head.msgh_id == req->req_id + 100) fprintf (ostream, " = "); /* normal case */ else /* This is not the proper reply message ID. */ fprintf (ostream, " =(%u != %u) ", reply->Head.msgh_id, req->req_id + 100); if (reply->RetCode == 0) fprintf (ostream, "0"); else { const char *str = strerror (reply->RetCode); if (str == 0) fprintf (ostream, "%#x", reply->RetCode); else fprintf (ostream, "%#x (%s)", reply->RetCode, str); } } static void print_data (mach_msg_type_name_t type, const void *data, mach_msg_type_number_t nelt, mach_msg_type_number_t eltsize) { switch (type) { case MACH_MSG_TYPE_PORT_NAME: assert (eltsize == sizeof (mach_port_t)); { mach_msg_type_number_t i; fprintf (ostream, "pn{"); for (i = 0; i < nelt; ++i) { fprintf (ostream, "%*u", (i > 0) ? 4 : 3, (unsigned int) ((mach_port_t *) data)[i]); } fprintf (ostream, "}"); return; } case MACH_MSG_TYPE_STRING: case MACH_MSG_TYPE_CHAR: fprintf (ostream, "\"%.*s\"", (int) (nelt * eltsize), (const char *) data); return; #if 0 case MACH_MSG_TYPE_CHAR: if (eltsize == 1) FMT ("'%c'", unsigned char); break; #endif #define FMT(fmt, ctype) do { \ mach_msg_type_number_t i; \ for (i = 0; i < nelt; ++i) \ { \ fprintf (ostream, "%s" fmt, \ (i == 0 && nelt > 1) ? "{" : i > 0 ? " " : "", \ *(const ctype *) data); \ data += eltsize; \ } \ if (nelt > 1) \ putc ('}', ostream); \ return; \ } while (0) case MACH_MSG_TYPE_BIT: case MACH_MSG_TYPE_INTEGER_8: case MACH_MSG_TYPE_INTEGER_16: case MACH_MSG_TYPE_INTEGER_32: case MACH_MSG_TYPE_INTEGER_64: switch (eltsize) { case 1: FMT ("%"PRId8, int8_t); case 2: FMT ("%"PRId16, int16_t); case 4: FMT ("%"PRId32, int32_t); case 8: FMT ("%"PRId64, int64_t); } break; case MACH_MSG_TYPE_REAL: if (eltsize == sizeof (float)) FMT ("%g", float); else if (eltsize == sizeof (double)) FMT ("%g", double); else if (eltsize == sizeof (long double)) FMT ("%Lg", long double); else abort (); break; } /* XXX */ fprintf (ostream, "\t%#x (type %d, %d*%d)\n", *(const int *)data, type, nelt, eltsize); } /*** Main program and child startup ***/ /* Run a child and have it do more or else `execvpe (argv, envp);'. */ pid_t traced_spawn (char **argv, char **envp) { error_t err; pid_t pid; mach_port_t task_wrapper; task_t traced_task; struct send_info *ti; struct receive_info *receive_ti; file_t file = file_name_path_lookup (argv[0], getenv ("PATH"), O_EXEC, 0, 0); if (file == MACH_PORT_NULL) error (1, errno, "command not found: %s", argv[0]); err = task_create (mach_task_self (), #ifdef KERN_INVALID_LEDGER NULL, 0, /* OSF Mach */ #endif 0, &traced_task); assert_perror (err); add_task (traced_task); /* Declare the new task to be our child. This is what a fork does. */ err = proc_child (getproc (), traced_task); if (err) error (2, err, "proc_child"); pid = task2pid (traced_task); if (pid < 0) error (2, errno, "task2pid"); receive_ti = new_receive_wrapper (traced_task, unknown_task); /* Create a trace wrapper for the task port. */ ti = new_send_wrapper (receive_ti, traced_task, &task_wrapper); ti->task = traced_task; free (TRACED_INFO (ti)->name); asprintf (&TRACED_INFO (ti)->name, "task%d(pid%d)", traced_task, pid); /* Replace the task's kernel port with the wrapper. When this task calls `mach_task_self ()', it will get our wrapper send right instead of its own real task port. */ err = mach_port_insert_right (mach_task_self (), task_wrapper, task_wrapper, MACH_MSG_TYPE_MAKE_SEND); assert_perror (err); err = task_set_special_port (traced_task, TASK_KERNEL_PORT, task_wrapper); assert_perror (err); /* Now actually run the command they told us to trace. We do the exec on the actual task, so the RPCs to map in the program itself do not get traced. Could have an option to use TASK_WRAPPER here instead. */ err = _hurd_exec (traced_task, file, argv, envp); if (err) error (2, err, "cannot exec `%s'", argv[0]); /* We were keeping this send right alive so that the wrapper object cannot die and hence our TRACED_TASK ref cannot have been released. */ mach_port_deallocate (mach_task_self (), task_wrapper); return pid; } static void scan_msgids_dir (char **argz, size_t *argz_len, char *dir, bool append) { struct dirent **eps; int n; int msgids_file_p (const struct dirent *eps) { if (fnmatch ("*.msgids", eps->d_name, 0) != FNM_NOMATCH) return 1; return 0; } n = scandir (dir, &eps, msgids_file_p, NULL); if (n >= 0) { for (int cnt = 0; cnt < n; ++cnt) { char *msgids_file; if (asprintf (&msgids_file, "%s/%s", dir, eps[cnt]->d_name) < 0) error (1, errno, "asprintf"); if (append == TRUE) { if (argz_add (argz, argz_len, msgids_file) != 0) error (1, errno, "argz_add"); } else { if (argz_insert (argz, argz_len, *argz, msgids_file) != 0) error (1, errno, "argz_insert"); } free (msgids_file); } } /* If the directory couldn't be scanned for whatever reason, just ignore it. */ } int main (int argc, char **argv, char **envp) { char *msgids_files_argz = NULL; size_t msgids_files_argz_len = 0; bool nostdinc = FALSE; const char *outfile = 0; char **cmd_argv = 0; error_t err; /* Parse our options... */ error_t parse_opt (int key, char *arg, struct argp_state *state) { switch (key) { case 'o': outfile = arg; break; case OPT_NOSTDINC: nostdinc = TRUE; break; case 'i': if (argz_add (&msgids_files_argz, &msgids_files_argz_len, arg) != 0) error (1, errno, "argz_add"); break; case 'I': scan_msgids_dir (&msgids_files_argz, &msgids_files_argz_len, arg, TRUE); break; case ARGP_KEY_NO_ARGS: argp_usage (state); return EINVAL; case ARGP_KEY_ARG: cmd_argv = &state->argv[state->next - 1]; state->next = state->argc; break; default: return ARGP_ERR_UNKNOWN; } return 0; } const struct argp argp = { options, parse_opt, args_doc, doc }; /* Parse our arguments. */ argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, 0); err = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_DEAD_NAME, &unknown_task); assert_perror (err); /* Insert the files from STD_MSGIDS_DIR at the beginning of the list, so that their content can be overridden by subsequently parsed files. */ if (nostdinc == FALSE) scan_msgids_dir (&msgids_files_argz, &msgids_files_argz_len, STD_MSGIDS_DIR, FALSE); if (msgids_files_argz != NULL) { char *msgids_file = NULL; while ((msgids_file = argz_next (msgids_files_argz, msgids_files_argz_len, msgids_file))) parse_msgid_list (msgids_file); free (msgids_files_argz); } if (outfile) { ostream = fopen (outfile, "w"); if (!ostream) error (1, errno, "%s", outfile); } else ostream = stderr; setlinebuf (ostream); traced_bucket = ports_create_bucket (); traced_class = ports_create_class (&traced_clean, &traced_dropweak); other_class = ports_create_class (0, 0); err = ports_create_port (other_class, traced_bucket, sizeof (*notify_pi), ¬ify_pi); assert_perror (err); hurd_ihash_set_cleanup (&msgid_ihash, msgid_ihash_cleanup, 0); /* Spawn a single thread that will receive intercepted messages, print them, and interpose on the ports they carry. The access to the `traced_info' and ihash data structures is all single-threaded, happening only in this new thread. */ cthread_detach (cthread_fork (trace_thread_function, traced_bucket)); /* Run the program on the command line and wait for it to die. The other thread does all the tracing and interposing. */ { pid_t child, pid; int status; child = traced_spawn (cmd_argv, envp); pid = waitpid (child, &status, 0); sleep (1); /* XXX gives other thread time to print */ if (pid != child) error (1, errno, "waitpid"); if (WIFEXITED (status)) fprintf (ostream, "Child %d exited with %d\n", pid, WEXITSTATUS (status)); else fprintf (ostream, "Child %d %s\n", pid, strsignal (WTERMSIG (status))); } ports_destroy_right (notify_pi); return 0; }