diff options
Diffstat (limited to 'utils/rpctrace.c')
-rw-r--r-- | utils/rpctrace.c | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/utils/rpctrace.c b/utils/rpctrace.c new file mode 100644 index 00000000..8fad04bc --- /dev/null +++ b/utils/rpctrace.c @@ -0,0 +1,713 @@ +/* Trace RPCs sent to selected ports + + Copyright (C) 1998, 1999 Free Software Foundation, Inc. + + Written by Jose M. Moya <josem@gnu.org> + + 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 <unistd.h> +#include <argp.h> +#include <error.h> +#include <string.h> +#include <version.h> +#include <sys/wait.h> + +const char *argp_program_version = STANDARD_HURD_VERSION (rpctrace); + +static const struct argp_option options[] = { + {"output", 'o', "FILE", 0, "Send trace output to FILE instead of stderr."}, + {0} +}; + +static const char *args_doc = "COMMAND [ARG...]"; +static const char *doc = +"Trace Mach Remote Procedure Calls." +"\v."; + +pid_t traced_spawn (char **argv, char **envp); + + +struct traced_info +{ + struct port_info pi; + union + { + struct traced_info *nextfree; /* Link when on free list. */ + + /* For a send right wrapper, the position in the traced_names hash table. + For a send-once right wrapper, this is null. */ + void **locp; +#define INFO_SEND_ONCE(info) ((info)->u.locp == 0) + } u; + mach_port_t forward; +}; + +static struct traced_info *freelist; + +task_t traced_task; +ihash_t traced_names; +struct port_class *traced_class; +struct port_bucket *traced_bucket; +FILE *ostream; + + +/* Create a new wrapper port and do `ports_get_right' on it. */ +static mach_port_t +new_send_wrapper (mach_port_t right) +{ + error_t err; + struct traced_info *info; + mach_port_t wrapper; + + + /* 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->forward = right; + + /* 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 = ihash_add (traced_names, info->forward, info, &info->u.locp); + assert_perror (err); + ports_port_ref_weak (info); + assert (info->u.locp != 0); + + wrapper = ports_get_right (info); + ports_port_deref (info); + + return wrapper; +} + +/* Create a new wrapper port and do `ports_get_right' on it. */ +static mach_port_t +new_send_once_wrapper (mach_port_t right) +{ + error_t err; + struct traced_info *info; + mach_port_t wrapper; + + /* 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->forward = right; + + /* Send-once 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. */ + + info->u.locp = 0; /* Used to mark this as send-once. */ + wrapper = info->pi.port_right; + + return wrapper; +} + + +/* 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->u.locp); + + /* Remove INFO from the hash table. */ + ihash_locp_remove (traced_names, info->u.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); + info->u.nextfree = freelist; + freelist = info; +} + + + +/* Rewrite a port right in a message with an appropriate wrapper port. */ +static error_t +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 = 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 0; + } + + /* 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 0; + } + + /* We have never seen this port before. Create a new wrapper port + and replace the right in the message with a right to it. */ + *right = new_send_wrapper (*right); + *type = MACH_MSG_TYPE_MAKE_SEND; + return 0; + + 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; + *right = new_send_once_wrapper (*right); + return 0; + + 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 */ + + info = ihash_find (traced_names, *right); + if (info) + { + /* This is a receive right that we have been tracing sends to. */ + 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 (); + + /* 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); + + /* 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 = ihash_add (traced_names, info->forward, info, &info->u.locp); + 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 0; + } + + default: + assert (!"??? bogus port type from kernel!"); + return EGRATUITOUS; + } +} + + +static void +print_header (mach_msg_header_t *msg) +{ + fprintf (ostream, + "msgid %d, %d bytes in msg\n" + "\treply port %d (type %d)\n", + msg->msgh_id, + msg->msgh_size, + msg->msgh_remote_port, MACH_MSGH_BITS_REMOTE(msg->msgh_bits)); +} + +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_STRING: + fprintf (ostream, "\t\"%.*s\"\n", + (int) (nelt * eltsize), (const char *) data); + break; + + 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: + case MACH_MSG_TYPE_CHAR: + case MACH_MSG_TYPE_REAL: + default: + /* XXX */ + fprintf (ostream, "\t%#x (type %d, %d*%d)\n", *(const int *)data, type, + nelt, eltsize); + break; + } +} + + +int +trace_and_forward (mach_msg_header_t *inp, mach_msg_header_t *outp) +{ + error_t err; + struct traced_info *info; + void *msg_buf_ptr = inp + 1; + 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); + + 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; + } + } + + complex = inp->msgh_bits & MACH_MSGH_BITS_COMPLEX; + + /* Print something about the message header. */ + fprintf (ostream, "port %d(=>%d) receives (type %d) ", + inp->msgh_local_port, info->forward, + MACH_MSGH_BITS_LOCAL (inp->msgh_bits)); + print_header (inp); + + /* 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) + { + err = rewrite_right (&inp->msgh_local_port, &reply_type); + assert_perror (err); + } + + 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. */ + 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); + } + + /* 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)); + + /* 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 (complex); + assert (eltsize == sizeof (mach_port_t)); + + fprintf (ostream, "\t%d ports, type %d\n", nelt, name); + + poly = 0; + for (i = 0; i < nelt; ++i) + { + mach_port_t o=portnames[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; + } + + err = rewrite_right (&portnames[i], &newtypes[i]); + assert_perror (err); + + if (portnames[i] == MACH_PORT_NULL) + fprintf (ostream, "\t\t[%d] = null\n", i); + else if (portnames[i] == MACH_PORT_DEAD) + fprintf (ostream, "\t\t[%d] = dead name\n", i); + else + fprintf (ostream, + "\t\t[%d] = port %d, type %d => port %d, type %d\n", + i, o, name, portnames[i], newtypes[i]); + + if (i > 0 && newtypes[i] != newtypes[0]) + poly = 1; + } + + 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; + } + (type->msgt_longform ? lt->msgtl_name : type->msgt_name) = name; + } + else if (newtypes[0] != name) + (type->msgt_longform ? lt->msgtl_name : type->msgt_name) + = newtypes[0]; + } + else + print_data (name, data, nelt, eltsize); + } + + /* 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; +} + + +int +main (int argc, char **argv, char **envp) +{ + 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 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); + + if (outfile) + { + ostream = fopen (outfile, "w"); + if (!ostream) + error (1, errno, "%s", outfile); + } + else + ostream = stderr; + + traced_bucket = ports_create_bucket (); + traced_class = ports_create_class (0, &traced_dropweak); + + err = ihash_create (&traced_names); + assert_perror (err); + + /* 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; +} + + +/* 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; + 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 (), 0, &traced_task); + assert_perror (err); + + /* Create a trace wrapper for the task port. */ + task_wrapper = new_send_wrapper (traced_task);/* consumes ref */ + + /* 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); + + /* 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"); + + /* 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; +} |