aboutsummaryrefslogtreecommitdiff
path: root/utils/rpctrace.c
diff options
context:
space:
mode:
Diffstat (limited to 'utils/rpctrace.c')
-rw-r--r--utils/rpctrace.c1238
1 files changed, 1238 insertions, 0 deletions
diff --git a/utils/rpctrace.c b/utils/rpctrace.c
new file mode 100644
index 00000000..996d4bae
--- /dev/null
+++ b/utils/rpctrace.c
@@ -0,0 +1,1238 @@
+/* Trace RPCs sent to selected ports
+
+ Copyright (C) 1998, 1999, 2001, 2002, 2003, 2005, 2006, 2009, 2011
+ 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>
+
+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}
+};
+
+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);
+
+/* 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;
+}
+
+/* We keep one of these structures for each port right we are tracing. */
+struct traced_info
+{
+ struct port_info pi;
+
+ mach_port_t forward; /* real port */
+ mach_msg_type_name_t type;
+
+ char *name; /* null or a string describing this */
+
+ union
+ {
+ struct traced_info *nextfree; /* Link when on free list. */
+
+ struct /* For a send right wrapper. */
+ {
+ hurd_ihash_locp_t locp; /* position in the traced_names hash table */
+ } send;
+
+ struct /* For a send-once right wrapper. */
+ {
+ /* We keep track of the send right to which the message containing
+ this send-once right as its reply port was sent, and the msgid of
+ that request. We don't hold a reference to the send right; it is
+ just a hint to indicate a match with a send right on which we just
+ forwarded a message. */
+ mach_port_t sent_to;
+ mach_msg_id_t sent_msgid;
+ } send_once;
+ } u;
+};
+#define INFO_SEND_ONCE(info) ((info)->type == MACH_MSG_TYPE_MOVE_SEND_ONCE)
+
+static struct traced_info *freelist;
+
+struct hurd_ihash traced_names
+ = HURD_IHASH_INITIALIZER (offsetof (struct traced_info, u.send.locp));
+struct port_class *traced_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 traced_info *info,
+ mach_msg_header_t *header);
+
+/* Called for a message that looks like an RPC reply. */
+static void print_reply_header (struct traced_info *info,
+ mig_reply_header_t *header);
+
+/* 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 port and do `ports_get_right' on it. */
+static struct traced_info *
+new_send_wrapper (mach_port_t right, mach_port_t *wrapper_right)
+{
+ error_t err;
+ struct traced_info *info;
+
+ /* Use a free send-once wrapper port if we have one. */
+ if (freelist)
+ {
+ info = freelist;
+ freelist = info->u.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);
+ info->name = 0;
+ }
+
+ info->forward = right;
+ info->type = MACH_MSG_TYPE_MOVE_SEND;
+
+ /* Store it in the reverse-lookup hash table, so we can
+ look up this same right again to find the wrapper port.
+ The entry in the hash table holds a weak ref on INFO. */
+ err = hurd_ihash_add (&traced_names, info->forward, info);
+ assert_perror (err);
+ ports_port_ref_weak (info);
+ assert (info->u.send.locp != 0);
+
+ *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 traced_info *
+new_send_once_wrapper (mach_port_t right, mach_port_t *wrapper_right)
+{
+ error_t err;
+ struct traced_info *info;
+
+ /* Use a free send-once wrapper port if we have one. */
+ if (freelist)
+ {
+ info = freelist;
+ freelist = info->u.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);
+ info->name = 0;
+ }
+
+ info->forward = right;
+ info->type = MACH_MSG_TYPE_MOVE_SEND_ONCE;
+
+ /* 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 = info->pi.port_right;
+ memset (&info->u.send_once, 0, sizeof info->u.send_once);
+
+ return info;
+}
+
+
+/* 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 traced_info *const info = pi;
+
+ assert (info->type == MACH_MSG_TYPE_MOVE_SEND);
+ assert (info->u.send.locp);
+
+ /* Remove INFO from the hash table. */
+ hurd_ihash_locp_remove (&traced_names, info->u.send.locp);
+ ports_port_deref_weak (info);
+
+ /* Deallocate the forward port, so the real port also sees no-senders. */
+ mach_port_deallocate (mach_task_self (), info->forward);
+
+ /* There are no rights to this port, so we can reuse it.
+ Add a hard ref and put INFO on the free list. */
+ ports_port_ref (info);
+
+ free (info->name);
+ info->name = 0;
+
+ info->u.nextfree = freelist;
+ freelist = info;
+}
+
+
+/* Rewrite a port right in a message with an appropriate wrapper port. */
+static struct traced_info *
+rewrite_right (mach_port_t *right, mach_msg_type_name_t *type)
+{
+ error_t err;
+ struct traced_info *info;
+
+ /* We can never do anything special with a null or dead port right. */
+ if (!MACH_PORT_VALID (*right))
+ return 0;
+
+ switch (*type)
+ {
+ case MACH_MSG_TYPE_PORT_SEND:
+ /* See if we are already tracing this port. */
+ info = hurd_ihash_find (&traced_names, *right);
+ if (info)
+ {
+ /* We are already tracing this port. We will pass on a right
+ to our existing wrapper port. */
+ *right = ports_get_right (info);
+ *type = MACH_MSG_TYPE_MAKE_SEND;
+ return info;
+ }
+
+ /* See if this is already one of our own wrapper ports. */
+ info = ports_lookup_port (traced_bucket, *right, 0);
+ if (info)
+ {
+ /* This is a send right to one of our own wrapper ports.
+ Instead, send along the original send right. */
+ mach_port_deallocate (mach_task_self (), *right); /* eat msg ref */
+ *right = info->forward;
+ err = mach_port_mod_refs (mach_task_self (), *right,
+ MACH_PORT_RIGHT_SEND, +1);
+ assert_perror (err);
+ ports_port_deref (info);
+ return info;
+ }
+
+ /* We have never seen this port before. Create a new wrapper port
+ and replace the right in the message with a right to it. */
+ *type = MACH_MSG_TYPE_MAKE_SEND;
+ return new_send_wrapper (*right, right);
+
+ 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 new_send_once_wrapper (*right, right);
+
+ 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. */
+ {
+ mach_port_t rr; /* B */
+ char *name;
+
+ info = hurd_ihash_find (&traced_names, *right);
+ if (info)
+ {
+ /* This is a receive right that we have been tracing sends to. */
+ name = info->name;
+ rr = ports_claim_right (info);
+ /* That released the refs on INFO, so it's been freed now. */
+ }
+ else
+ {
+ /* This is a port we know nothing about. */
+ rr = mach_reply_port ();
+ name = 0;
+ }
+
+ /* Create a new wrapper object that receives on this port. */
+ err = ports_import_port (traced_class, traced_bucket,
+ *right, sizeof *info, &info);
+ assert_perror (err);
+ info->name = name;
+ info->type = MACH_MSG_TYPE_MOVE_SEND; /* XXX ? */
+
+ /* 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);
+ info->forward = rr;
+
+ err = hurd_ihash_add (&traced_names, info->forward, info);
+ assert_perror (err);
+ ports_port_ref_weak (info);
+
+ /* If there are no extant send rights to this port, then INFO will
+ die right here and release its send right to RR.
+ XXX what to do?
+ */
+ ports_port_deref (info);
+
+ *right = rr;
+ return info;
+ }
+
+ default:
+ assert (!"??? bogus port type from kernel!");
+ }
+ return 0;
+}
+
+static void
+print_contents (mach_msg_header_t *inp,
+ void *msg_buf_ptr)
+{
+ 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;
+ struct traced_info *ti;
+
+ assert (inp->msgh_bits & MACH_MSGH_BITS_COMPLEX);
+ assert (eltsize == sizeof (mach_port_t));
+
+ poly = 0;
+ for (i = 0; i < nelt; ++i)
+ {
+ newtypes[i] = name;
+
+ if (inp->msgh_id == 3215) /* mach_port_insert_right */
+ {
+ /* XXX
+ */
+ fprintf (ostream,
+ "\t\t[%d] = pass through port %d, type %d\n",
+ i, portnames[i], name);
+ continue;
+ }
+
+ ti = rewrite_right (&portnames[i], &newtypes[i]);
+
+ 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
+ {
+ assert (ti);
+ if (ti->name != 0)
+ fprintf (ostream, "%s", ti->name);
+ 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);
+ }
+}
+
+int
+trace_and_forward (mach_msg_header_t *inp, mach_msg_header_t *outp)
+{
+ 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)
+ {
+ /* If INFO is a send-once wrapper, this could be a forged
+ notification; oh well. XXX */
+
+ const mach_dead_name_notification_t *const n = (void *) inp;
+
+ assert (n->not_port == info->forward);
+ /* Deallocate extra ref allocated by the notification. */
+ mach_port_deallocate (mach_task_self (), n->not_port);
+ ports_destroy_right (info);
+ ports_port_deref (info);
+ ((mig_reply_header_t *) outp)->RetCode = MIG_NO_REPLY;
+ 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;
+ }
+ }
+
+ 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);
+
+ inp->msgh_local_port = inp->msgh_remote_port;
+ if (reply_type && msgid_trace_replies (msgid))
+ {
+ struct traced_info *info;
+ info = rewrite_right (&inp->msgh_local_port, &reply_type);
+ assert (info);
+ if (info->name == 0)
+ {
+ if (msgid == 0)
+ asprintf (&info->name, "reply(%u:%u)",
+ (unsigned int) info->pi.port_right,
+ (unsigned int) inp->msgh_id);
+ else
+ asprintf (&info->name, "reply(%u:%s)",
+ (unsigned int) info->pi.port_right, msgid->name);
+ }
+ if (info->type == MACH_MSG_TYPE_MOVE_SEND_ONCE)
+ {
+ info->u.send_once.sent_to = info->pi.port_right;
+ info->u.send_once.sent_msgid = inp->msgh_id;
+ }
+ }
+
+ inp->msgh_remote_port = info->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;
+ info->u.nextfree = freelist;
+ freelist = 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))
+ {
+ /* This sure looks like an RPC reply message. */
+ mig_reply_header_t *rh = (void *) inp;
+ print_reply_header (info, rh);
+ putc (' ', ostream);
+ print_contents (&rh->Head, rh + 1);
+ putc ('\n', ostream);
+ }
+ else
+ {
+ /* Print something about the message header. */
+ print_request_header (info, inp);
+ print_contents (inp, inp + 1);
+ if (inp->msgh_local_port == MACH_PORT_NULL) /* simpleroutine */
+ fprintf (ostream, ");\n");
+ else
+ /* Leave a partial line that will be finished later. */
+ fprintf (ostream, ")");
+ }
+ }
+
+ /* 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 mach_port_t expected_reply_port;
+
+static void
+print_request_header (struct traced_info *receiver, mach_msg_header_t *msg)
+{
+ const char *msgname = msgid_name (msg->msgh_id);
+
+ expected_reply_port = msg->msgh_local_port;
+
+ if (receiver->name != 0)
+ fprintf (ostream, "%4s->", receiver->name);
+ else
+ fprintf (ostream, "%4u->", (unsigned int) receiver->pi.port_right);
+
+ if (msgname != 0)
+ fprintf (ostream, "%5s (", msgname);
+ else
+ fprintf (ostream, "%5u (", (unsigned int) msg->msgh_id);
+}
+
+static void
+unfinished_line (void)
+{
+ /* A partial line was printed by print_request_header, but
+ cannot be finished before we print something else.
+ Finish this line with the name of the reply port that
+ will appear in the disconnected reply later on. */
+ fprintf (ostream, " > %4u ...\n", expected_reply_port);
+}
+
+static void
+print_reply_header (struct traced_info *info, mig_reply_header_t *reply)
+{
+ if (info->pi.port_right == expected_reply_port)
+ {
+ /* We have printed a partial line for the request message,
+ and now we have the corresponding reply. */
+ if (reply->Head.msgh_id == info->u.send_once.sent_msgid + 100)
+ fprintf (ostream, " = "); /* normal case */
+ else
+ /* This is not the proper reply message ID. */
+ fprintf (ostream, " =(%u != %u) ",
+ reply->Head.msgh_id,
+ info->u.send_once.sent_msgid + 100);
+ }
+ else
+ {
+ /* This does not match up with the last thing printed. */
+ if (expected_reply_port != MACH_PORT_NULL)
+ /* We don't print anything if the last call was a simpleroutine. */
+ unfinished_line ();
+ if (info->name == 0)
+ /* This was not a reply port in previous message sent
+ through our wrappers. */
+ fprintf (ostream, "reply?%4u",
+ (unsigned int) info->pi.port_right);
+ else
+ fprintf (ostream, "%s%4u",
+ info->name, (unsigned int) info->pi.port_right);
+ if (reply->Head.msgh_id == info->u.send_once.sent_msgid + 100)
+ /* This is a normal reply to a previous request. */
+ fprintf (ostream, " > ");
+ else
+ {
+ /* Weirdo. */
+ const char *msgname = msgid_name (reply->Head.msgh_id);
+ if (msgname == 0)
+ fprintf (ostream, " >(%u) ", reply->Head.msgh_id);
+ else
+ fprintf (ostream, " >(%s) ", msgname);
+ }
+ }
+
+ 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);
+ }
+
+ expected_reply_port = MACH_PORT_NULL;
+}
+
+
+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, "\"%.*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 ***/
+
+task_t traced_task;
+
+
+/* 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;
+ struct traced_info *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);
+
+ /* 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");
+
+ /* Create a trace wrapper for the task port. */
+ ti = new_send_wrapper (traced_task, &task_wrapper);/* consumes ref */
+ asprintf (&ti->name, "task%d", (int) 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;
+
+ /* 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 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);
+
+ /* 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 (0, &traced_dropweak);
+
+ 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)));
+ }
+
+ return 0;
+}