aboutsummaryrefslogtreecommitdiff
path: root/utils/rpctrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/rpctrace.c')
-rw-r--r--utils/rpctrace.c1968
1 files changed, 1968 insertions, 0 deletions
diff --git a/utils/rpctrace.c b/utils/rpctrace.c
new file mode 100644
index 00000000..fc913e30
--- /dev/null
+++ b/utils/rpctrace.c
@@ -0,0 +1,1968 @@
+/* Trace RPCs sent to selected ports
+
+ Copyright (C) 1998, 1999, 2001, 2002, 2003, 2005, 2006, 2009, 2011,
+ 2013 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 <hurd.h>
+#include <hurd/ports.h>
+#include <hurd/ihash.h>
+#include <mach/message.h>
+#include <assert.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <argp.h>
+#include <error.h>
+#include <string.h>
+#include <version.h>
+#include <sys/wait.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <argz.h>
+#include <envz.h>
+
+const char *argp_program_version = STANDARD_HURD_VERSION (rpctrace);
+
+#define STD_MSGIDS_DIR DATADIR "/msgids/"
+
+static unsigned strsize = 80;
+
+#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, 's', "SIZE", 0, "Specify the maximum string size to print (the default is 80)."},
+ {0, 'E', "var[=value]", 0,
+ "Set/change (var=value) or remove (var) an environment variable among the "
+ "ones inherited by the executed process."},
+ {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);
+}
+
+/* This structure stores the information of the traced task. */
+struct task_info
+{
+ task_t task;
+ boolean_t threads_wrapped; /* All threads of the task has been wrapped? */
+};
+
+static struct hurd_ihash msgid_ihash
+ = HURD_IHASH_INITIALIZER (HURD_IHASH_NO_LOCP);
+
+static struct hurd_ihash task_ihash
+ = HURD_IHASH_INITIALIZER (HURD_IHASH_NO_LOCP);
+
+task_t unknown_task;
+
+void
+add_task (task_t task)
+{
+ error_t err;
+ struct task_info *info = malloc (sizeof *info);
+
+ if (info == NULL)
+ error (1, 0, "Fail to allocate memory.");
+
+ info->task = task;
+ info->threads_wrapped = FALSE;
+
+ err = hurd_ihash_add (&task_ihash, task, info);
+ if (err)
+ error (1, err, "hurd_ihash_add");
+}
+
+void
+remove_task (task_t task)
+{
+ hurd_ihash_remove (&task_ihash, task);
+}
+
+/* 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, "%ms %*u %ms %*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;
+}
+
+
+/* A common structure between sender_info and send_once_info */
+struct traced_info
+{
+ struct port_info pi;
+ mach_msg_type_name_t type;
+ char *name; /* null or a string describing this */
+};
+
+/* Each traced port has one receiver info and multiple send wrappers.
+ * The receiver info records the information of the receive right to
+ * the traced port, while send wrappers are created for each task
+ * who has the send right to the traced port.
+ */
+
+struct receiver_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 receiver_info *receive_right; /* Link with other receive rights. */
+ struct sender_info *next; /* The head of the send right list */
+};
+
+struct sender_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 sender_info *next;
+
+ struct receiver_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 sender_info *) info)
+#define SEND_ONCE_INFO(info) ((struct send_once_info *) info)
+
+/* This structure stores the information of the RPC requests. */
+struct req_info
+{
+ boolean_t is_req;
+ 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->is_req = 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 receiver infos, but only the ones for the traced tasks. */
+struct receiver_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 receiver_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 sender_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 info for the receive right.
+ * It lives until the traced receive right dies. */
+static struct receiver_info *
+new_receiver_info (mach_port_t right, mach_port_t owner)
+{
+ error_t err;
+ struct receiver_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);
+ if (err)
+ error (2, err, "mach_port_request_notification");
+ mach_port_deallocate (mach_task_self (), foo);
+
+ err = hurd_ihash_add (&traced_names, info->forward, info);
+ if (err)
+ error (2, err, "hurd_ihash_add");
+ return info;
+}
+
+static void
+destroy_receiver_info (struct receiver_info *info)
+{
+ struct sender_info *send_wrapper;
+ struct receiver_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 receiver info in the list. */
+ if (*prev)
+ *prev = info->receive_right;
+
+ send_wrapper = info->next;
+ while (send_wrapper)
+ {
+ struct sender_info *next = send_wrapper->next;
+ assert (TRACED_INFO (send_wrapper)->pi.refcnt == 1);
+ /* Reset the receive_right of the send wrapper in advance to avoid
+ * destroy_receiver_info is called when the port info is destroyed. */
+ send_wrapper->receive_right = NULL;
+ 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 receiver info is destroyed.
+ */
+static struct sender_info *
+new_send_wrapper (struct receiver_info *receive, task_t task,
+ mach_port_t *wrapper_right)
+{
+ error_t err;
+ struct sender_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;
+
+ *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;
+ }
+ 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;
+}
+
+/* Unlink the send wrapper from the list. */
+static void
+unlink_sender_info (void *pi)
+{
+ struct sender_info *info = pi;
+ struct sender_info **prev;
+
+ 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;
+ }
+}
+
+/* The function is called when the port_info is going to be destroyed.
+ * If it's the last send wrapper for the traced port, the receiver info
+ * will also be destroyed. */
+static void
+traced_clean (void *pi)
+{
+ struct sender_info *info = pi;
+
+ assert (TRACED_INFO (info)->type == MACH_MSG_TYPE_MOVE_SEND);
+ free (TRACED_INFO (info)->name);
+
+ if (info->receive_right)
+ {
+ unlink_sender_info (pi);
+
+ /* If this is the last send wrapper, it means that our traced port won't
+ * have any more send rights. We notify the owner of the receive right
+ * of that by deallocating the forward port. */
+ if (info->receive_right->next == NULL)
+ destroy_receiver_info (info->receive_right);
+
+ info->receive_right = NULL;
+ }
+}
+
+/* Check if the receive right has been seen. */
+boolean_t
+seen_receive_right (task_t task, mach_port_t name)
+{
+ struct receiver_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 receiver_info returned from the function
+ * might not be the receive right in the traced tasks.
+ * */
+struct receiver_info *
+discover_receive_right (mach_port_t send, task_t task)
+{
+ error_t err;
+ struct receiver_info *info = NULL;
+
+ 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;
+
+ {
+ 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 receiver_info *receiver_info = NULL;
+
+ err = mach_port_names (task, &portnames, &nportnames,
+ &porttypes, &nporttypes);
+ if (err == MACH_SEND_INVALID_DEST)
+ {
+ remove_task (task);
+ return 0;
+ }
+ if (err)
+ error (2, err, "mach_port_names");
+
+ for (j = 0; j < nportnames; j++)
+ {
+ mach_port_status_t port_status;
+ 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_get_receive_status (task, portnames[j],
+ &port_status);
+ if (err)
+ error (2, err, "mach_port_get_receive_status");
+ /* If the port doesn't have the send right, skip it. */
+ if (!port_status.mps_srights)
+ continue;
+
+ err = mach_port_extract_right (task, portnames[j],
+ MACH_MSG_TYPE_MAKE_SEND,
+ &send_right, &type);
+ if (err)
+ error (2, err, "mach_port_extract_right");
+
+ if (/* We have seen this send right before. */
+ hurd_ihash_find (&traced_names, send_right)
+ || send_right != send /* It's not the port we want. */)
+ {
+ mach_port_deallocate (mach_task_self (), send_right);
+ continue;
+ }
+
+ /* We have found the receive right we want. */
+ receiver_info = new_receiver_info (send_right, task);
+ receiver_info->portname = portnames[j];
+ break;
+ }
+ 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 (receiver_info)
+ return receiver_info;
+ }
+ return NULL;
+}
+
+/* get_send_wrapper searches for the send wrapper for the target task.
+ If it doesn't exist, create a new one. */
+struct sender_info *
+get_send_wrapper (struct receiver_info *receiver_info,
+ mach_port_t task, mach_port_t *right)
+{
+ struct sender_info *info = receiver_info->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 (receiver_info, 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 receiver_info *receiver_info;
+ struct sender_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->is_req) /* 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:
+ /* The strategy for moving the send right is: if the destination task
+ * has the receive right, we move the send right of the traced port to
+ * the destination; otherwise, we move the one of the send wrapper.
+ */
+ /* 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 receiver 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);
+ if (err)
+ error (2, err, "mach_port_mod_refs");
+ ports_port_deref (send_wrapper);
+ }
+ else
+ {
+ struct sender_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;
+ }
+
+ if (req && req->req_id == 3216) /* mach_port_extract_right */
+ receiver_info = discover_receive_right (*right, dest);
+ else
+ receiver_info = discover_receive_right (*right, source);
+ if (receiver_info == NULL)
+ {
+ /* It's unusual to see an unknown send right from a traced task.
+ * We ignore it. */
+ if (source != unknown_task)
+ {
+ /* TODO: this happens on fork() when the new process does not
+ have the send right yet (it is about to get inserted). */
+ 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. */
+ receiver_info = new_receiver_info (*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 == receiver_info->task)
+ return receiver_info->name;
+ else
+ {
+ assert (*right == receiver_info->forward);
+ mach_port_deallocate (mach_task_self (), *right);
+ send_wrapper = get_send_wrapper (receiver_info, 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 and the send wrapper for
+ * the destination task is denoted as B (if the destination task
+ * doesn't have the send wrapper, we create it before moving receive
+ * right).
+ * We wrap the receive right A in the send wrapper and move the receive
+ * right B to the destination task. */
+ {
+ assert (req);
+ receiver_info = hurd_ihash_find (&traced_names, *right);
+ if (receiver_info)
+ {
+ struct sender_info *send_wrapper2;
+ char *name;
+ mach_port_t rr;
+
+ /* The port A has at least one send right - the one in
+ * receiver_info->forward. If the source task doesn't have
+ * the send right, the port A will be destroyed after we
+ * deallocate the only send right. */
+
+ /* We have to deallocate the send right in
+ * receiver_info->forward before we import the port to port_info.
+ * So the reference count in the imported port info will be 1,
+ * if it doesn't have any other send rights. */
+ mach_port_deallocate (mach_task_self (), receiver_info->forward);
+ err = ports_import_port (traced_class, traced_bucket,
+ *right, sizeof *send_wrapper,
+ &send_wrapper);
+ if (err)
+ error (2, err, "ports_import_port");
+
+ TRACED_INFO (send_wrapper)->type = MACH_MSG_TYPE_MOVE_SEND;
+ send_wrapper->task = source;
+ TRACED_INFO (send_wrapper)->name = receiver_info->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_deref (send_wrapper);
+
+ hurd_ihash_locp_remove (&traced_names, receiver_info->locp);
+
+ send_wrapper2 = get_send_wrapper (receiver_info, dest, &rr);
+ assert (TRACED_INFO (send_wrapper2)->pi.refcnt == 1);
+ name = TRACED_INFO (send_wrapper2)->name;
+ TRACED_INFO (send_wrapper2)->name = NULL;
+ /* send_wrapper2 isn't destroyed normally, so we need to unlink
+ * it from the send wrapper list before calling ports_claim_right */
+ unlink_sender_info (send_wrapper2);
+ send_wrapper2->receive_right = NULL;
+ 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);
+ if (err)
+ error (2, err, "mach_port_insert_right");
+ receiver_info->forward = rr;
+ receiver_info->task = dest;
+ if (dest != unknown_task)
+ {
+ receiver_info->receive_right = receive_right_list;
+ receive_right_list = receiver_info;
+ }
+ /* The port name will be discovered
+ * when we search for this receive right. */
+ receiver_info->portname = UNKNOWN_NAME;
+ receiver_info->name = name;
+
+ send_wrapper->receive_right = receiver_info;
+ send_wrapper->next = receiver_info->next;
+ receiver_info->next = send_wrapper;
+
+ err = hurd_ihash_add (&traced_names, receiver_info->forward,
+ receiver_info);
+ if (err)
+ error (2, err, "hurd_ihash_add");
+ *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);
+ if (err)
+ error (2, err, "mach_port_insert_right");
+ receiver_info = new_receiver_info (*right, dest);
+ }
+
+ return receiver_info->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);
+ }
+}
+
+/* Wrap all thread ports in the task */
+static void
+wrap_all_threads (task_t task)
+{
+ struct sender_info *thread_send_wrapper;
+ struct receiver_info *thread_receiver_info;
+ thread_t *threads;
+ size_t nthreads;
+ error_t err;
+
+ err = task_threads (task, &threads, &nthreads);
+ if (err)
+ error (2, err, "task_threads");
+
+ for (int i = 0; i < nthreads; ++i)
+ {
+ thread_receiver_info = hurd_ihash_find (&traced_names, threads[i]);
+ /* We haven't seen the port. */
+ if (thread_receiver_info == NULL)
+ {
+ mach_port_t new_thread_port;
+
+ thread_receiver_info = new_receiver_info (threads[i], unknown_task);
+ thread_send_wrapper = new_send_wrapper (thread_receiver_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 (task));
+
+ err = mach_port_insert_right (mach_task_self (),
+ new_thread_port, new_thread_port,
+ MACH_MSG_TYPE_MAKE_SEND);
+ if (err)
+ error (2, err, "mach_port_insert_right");
+
+ err = thread_set_kernel_port (threads[i], new_thread_port);
+ if (err)
+ error (2, err, "thread_set_kernel_port");
+
+ mach_port_deallocate (mach_task_self (), new_thread_port);
+ }
+ }
+ vm_deallocate (mach_task_self (), threads, nthreads * sizeof (thread_t));
+}
+
+/* Wrap the new thread port that is in the message. */
+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;
+ /* This function is called after rewrite_right,
+ * so the wrapper for the thread port has been created. */
+ struct sender_info *send_wrapper = ports_lookup_port (traced_bucket,
+ reply->child_thread, 0);
+
+ assert (send_wrapper);
+ assert (send_wrapper->receive_right);
+ thread_port = send_wrapper->receive_right->forward;
+
+ err = mach_port_insert_right (mach_task_self (), reply->child_thread,
+ reply->child_thread, MACH_MSG_TYPE_MAKE_SEND);
+ if (err)
+ error (2, err, "mach_port_insert_right");
+ err = thread_set_kernel_port (thread_port, reply->child_thread);
+ if (err)
+ error (2, err, "thread_set_kernel_port");
+ mach_port_deallocate (mach_task_self (), reply->child_thread);
+
+ free (TRACED_INFO (send_wrapper)->name);
+ asprintf (&TRACED_INFO (send_wrapper)->name, "thread%d(pid%d)",
+ thread_port, task2pid (req->from));
+ ports_port_deref (send_wrapper);
+}
+
+/* Wrap the new task port that is in the message. */
+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 sender_info *task_wrapper1 = ports_lookup_port (traced_bucket,
+ reply->child_task, 0);
+ /* The send wrapper for the new task itself. */
+ struct sender_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);
+ if (err)
+ error (2, err, "mach_port_insert_right");
+ err = task_set_kernel_port (task_port, pseudo_task_port);
+ if (err)
+ error (2, err, "task_set_kernel_port");
+ 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);
+ ports_port_deref (task_wrapper1);
+}
+
+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 receiver_info *receiver_info;
+ 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);
+ receiver_info = hurd_ihash_find (&traced_names, n->not_port);
+ /* The receiver info might have been destroyed.
+ * If not, we destroy it here. */
+ if (receiver_info)
+ {
+ assert (n->not_port == receiver_info->forward);
+ destroy_receiver_info (receiver_info);
+ }
+
+ ((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))
+ {
+ switch (reply_type)
+ {
+ case MACH_MSG_TYPE_PORT_SEND:
+ rewrite_right (&inp->msgh_local_port, &reply_type, NULL);
+ break;
+
+ case MACH_MSG_TYPE_PORT_SEND_ONCE:;
+ struct send_once_info *info;
+ 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);
+ }
+ break;
+
+ default:
+ error (1, 0, "Reply type %i not handled", reply_type);
+ }
+ }
+
+ 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)
+ /* The notification message is considered as a request. */
+ && (inp->msgh_id > 72 || inp->msgh_id < 64)
+ && (*(int *) &((mig_reply_header_t *) inp)->RetCodeType
+ == *(int *)&RetCodeType))
+ {
+ struct req_info *req = remove_request (inp->msgh_id - 100,
+ inp->msgh_remote_port);
+ assert (req);
+ req->is_req = 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
+ {
+ struct task_info *task_info;
+ task_t to = 0;
+ struct req_info *req = NULL;
+
+ /* Print something about the message header. */
+ print_request_header ((struct sender_info *) info, inp);
+ /* 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, ")");
+ fflush (ostream);
+
+ /* If it's the first request from the traced task,
+ * wrap the all threads in the task. */
+ task_info = hurd_ihash_find (&task_ihash, SEND_INFO (info)->task);
+ if (task_info && !task_info->threads_wrapped)
+ {
+ wrap_all_threads (SEND_INFO (info)->task);
+ task_info->threads_wrapped = TRUE;
+ }
+ }
+ }
+
+ /* 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 void *
+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
+
+/* We keep track of the last reply port used in a request we print to
+ ostream. This way we can end incomplete requests with an ellipsis
+ and the name of the reply port. When the reply finally arrives, we
+ start a new line with that port name and an ellipsis, making it
+ easy to match it to the associated request. */
+static mach_port_t last_reply_port;
+
+/* Print an ellipsis if necessary. */
+static void
+print_ellipsis (void)
+{
+ if (MACH_PORT_VALID (last_reply_port))
+ fprintf (ostream, " ...%u\n", (unsigned int) last_reply_port);
+}
+
+static void
+print_request_header (struct sender_info *receiver, mach_msg_header_t *msg)
+{
+ const char *msgname = msgid_name (msg->msgh_id);
+ print_ellipsis ();
+ last_reply_port = msg->msgh_local_port;
+
+ 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)
+{
+ if (last_reply_port != info->pi.pi.port_right)
+ {
+ print_ellipsis ();
+ fprintf (ostream, "%u...", (unsigned int) info->pi.pi.port_right);
+ }
+ last_reply_port = MACH_PORT_NULL;
+
+ /* 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 char escape_sequences[0x100] =
+ {
+ ['\0'] = '0',
+ ['\a'] = 'a',
+ ['\b'] = 'b',
+ ['\f'] = 'f',
+ ['\n'] = 'n',
+ ['\r'] = 'r',
+ ['\t'] = 't',
+ ['\v'] = 'v',
+ ['\\'] = '\\',
+ ['\''] = '\'',
+ ['"'] = '"',
+ };
+
+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:
+ if (nelt > strsize)
+ nelt = strsize;
+ fprintf (ostream, "\"");
+ /* Scan data for non-printable characters. p always points to
+ the first character that has not yet been printed. */
+ const char *p, *q;
+ p = q = (const char *) data;
+ while (*q && q - (const char *) data < (int) (nelt * eltsize))
+ {
+ if (isgraph (*q) || *q == ' ')
+ {
+ q += 1;
+ continue;
+ }
+
+ /* We encountered a non-printable character. Print anything
+ that has not been printed so far. */
+ if (p < q)
+ fprintf (ostream, "%.*s", q - p, p);
+
+ char c = escape_sequences[*((const unsigned char *) q)];
+ if (c)
+ fprintf (ostream, "\\%c", c);
+ else
+ fprintf (ostream, "\\x%02x", *((const unsigned char *) q));
+
+ q += 1;
+ p = q;
+ }
+
+ /* Print anything that has not been printed so far. */
+ if (p < q)
+ fprintf (ostream, "%.*s", q - p, p);
+ fprintf (ostream, "\"");
+ 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 sender_info *ti;
+ struct receiver_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_receiver_info (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;
+ pthread_t thread;
+ error_t err;
+ char **cmd_envp = NULL;
+ char *envz = NULL;
+ size_t envz_len = 0;
+
+ /* 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 's':
+ strsize = atoi (arg);
+ break;
+
+ case 'E':
+ if (envz == NULL)
+ {
+ if (argz_create (envp, &envz, &envz_len))
+ error (1, errno, "argz_create");
+ }
+ if (envz != NULL)
+ {
+ char *equal = strchr (arg, '=');
+ char *name;
+ char *newval;
+ if (equal != NULL)
+ {
+ name = strndupa (arg, equal - arg);
+ if (name == NULL)
+ error (1, errno, "strndupa");
+ newval = equal + 1;
+ }
+ else
+ {
+ name = arg;
+ newval = NULL;
+ }
+ if (envz_add (&envz, &envz_len, name, newval))
+ error (1, errno, "envz_add");
+ }
+ 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, NULL);
+ other_class = ports_create_class (0, 0);
+ err = ports_create_port (other_class, traced_bucket,
+ sizeof (*notify_pi), &notify_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. */
+ err = pthread_create (&thread, NULL, trace_thread_function, traced_bucket);
+ if (!err)
+ pthread_detach (thread);
+ else
+ {
+ errno = err;
+ perror ("pthread_create");
+ }
+
+ if (envz != NULL)
+ {
+ envz_strip (&envz, &envz_len);
+ cmd_envp = alloca ((argz_count (envz, envz_len) + 1) * sizeof (char *));
+ if (cmd_envp == NULL)
+ error (1, errno, "alloca");
+ else
+ argz_extract (envz, envz_len, cmd_envp);
+ }
+ if (cmd_envp == NULL)
+ cmd_envp = envp;
+
+ /* 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, cmd_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);
+ free (envz);
+
+ return 0;
+}