From 0abf2329d31cb9779a16933414202ee8ccda108b Mon Sep 17 00:00:00 2001 From: Justus Winter <4winter@informatik.uni-hamburg.de> Date: Fri, 23 May 2014 08:42:45 +0200 Subject: [PATCH hurd 8/9] utils/rpctrace: support attaching to servers * utils/rpctrace.c (options): Add `--pid' and `--reference-port'. (print_contents): Prevent the translation of rights if `req' is NULL. We will use this to print messages in `trace_server'. (parse_task): New function. (trace_server): Mach server function that displays relayed messages. (trace_class_rpcs): New function that attaches to a server and starts tracing. (parse_opt): Handle `--pid' and `--reference-port'. (main): Handle new arguments, call trace_class_rpcs if desired. --- utils/Makefile | 5 +- utils/rpctrace.c | 245 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 247 insertions(+), 3 deletions(-) diff --git a/utils/Makefile b/utils/Makefile index 955789b..352494a 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -34,7 +34,7 @@ SRCS = shd.c ps.c settrans.c syncfs.c showtrans.c addauth.c rmauth.c \ nullauth.c match-options.c msgids.c rpcscan.c OBJS = $(filter-out %.sh,$(SRCS:.c=.o)) -HURDLIBS = ps ihash store fshelp ports ftpconn shouldbeinlibc +HURDLIBS = ps ihash store fshelp ports ftpconn shouldbeinlibc introspection LDLIBS += -lpthread login-LDLIBS = -lutil -lcrypt addauth-LDLIBS = -lcrypt @@ -67,7 +67,8 @@ ps w ids settrans syncfs showtrans fsysopts storeinfo login vmstat portinfo \ $(filter-out $(special-targets), $(targets)): %: %.o -rpctrace: ../libports/libports.a +rpctrace: ../libports/libports.a \ + ../libintrospection/libintrospection.a hurd_portUser.o rpctrace rpcscan: msgids.o \ ../libihash/libihash.a msgids-CPPFLAGS = -DDATADIR=\"${datadir}\" diff --git a/utils/rpctrace.c b/utils/rpctrace.c index afe6b6d..666ff08 100644 --- a/utils/rpctrace.c +++ b/utils/rpctrace.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -40,14 +41,22 @@ #include #include "msgids.h" +#include "hurd_port_U.h" const char *argp_program_version = STANDARD_HURD_VERSION (rpctrace); static unsigned strsize = 80; +static int trace_class; static const struct argp_option options[] = { {"output", 'o', "FILE", 0, "Send trace output to FILE instead of stderr."}, + {"pid", 'p', "PID", 0, "Attach to PID and trace all requests to objects " + "of the same class as the given reference port. This will only work " + "for Hurd servers implementing the introspection protocol."}, + {"port", 'P', "PORT", 0, "Trace all requests PORT. " + "PORT must denote a receive right in PID."}, + {"class", 'c', NULL, 0, "Trace all requests to the same class as PORT."}, {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 " @@ -852,7 +861,7 @@ print_contents (mach_msg_header_t *inp, 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)) + if (MACH_MSG_TYPE_PORT_ANY_RIGHT (name) && req != NULL) { /* These are port rights. Translate them into wrappers. */ mach_port_t *const portnames = data; @@ -1673,10 +1682,217 @@ traced_spawn (char **argv, char **envp) return pid; } + +/* Return the task corresponding to the user argument ARG, exiting with an + appriate error message if we can't. */ +static task_t +parse_task (char *arg) +{ + error_t err; + task_t task; + char *arg_end; + pid_t pid = strtoul (arg, &arg_end, 10); + static process_t proc = MACH_PORT_NULL; + + if (*arg == '\0' || *arg_end != '\0') + error (10, 0, "%s: Invalid process id", arg); + + if (proc == MACH_PORT_NULL) + proc = getproc (); + + err = proc_pid2task (proc, pid, &task); + if (err) + error (11, err, "%s", arg); + else if (task == MACH_PORT_NULL) + error (11, 0, "%s: Process %d is dead and has no task", arg, (int) pid); + + return task; +} + +static mach_port_t trace_notification_port; +static mach_port_t reference_port; + +boolean_t +trace_server (mach_msg_header_t *inp, + mach_msg_header_t *outp) +{ + error_t err; + static struct hurd_ihash ongoing_requests = + HURD_IHASH_INITIALIZER (HURD_IHASH_NO_LOCP); + struct msgid_info *info; + mach_port_t trace_id; + int is_reply; + + if (inp->msgh_local_port == trace_notification_port + && inp->msgh_id == MACH_NOTIFY_NO_SENDERS) + { + error (0, 0, "The tracee vanished."); + exit (EXIT_SUCCESS); + } + + err = introspection_extract_message (inp, &trace_id); + if (err) + { + error (0, err, "introspection_extract_message"); + goto out; + } + info = msgid_info (inp->msgh_id); + + /* XXX This hardcodes an assumption about reply message ids. */ + is_reply = (inp->msgh_id / 100) % 2 == 1; + if (is_reply) + { + /* This looks like a traced reply or a pseudo-reply. A + pseudo-reply is a message containing the result of a simple + procedure that is only sent to us. */ + mig_reply_header_t *reply = (mig_reply_header_t *) inp; + + mach_port_t request_port; + request_port = hurd_ihash_find (&ongoing_requests, trace_id); + if (! MACH_PORT_VALID (request_port)) + { + fprintf (stderr, "unsolicited reply packet with id: %d\n", + trace_id); + goto out; + } + hurd_ihash_remove (&ongoing_requests, trace_id); + + if (! (trace_class || request_port == reference_port)) + goto out; + + if (last_reply_port != trace_id) + { + print_ellipsis (); + fprintf (ostream, "%u...", (unsigned int) trace_id); + } + last_reply_port = MACH_PORT_NULL; + + fprintf (ostream, " = "); + + 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); + } + + if (inp->msgh_size > sizeof *reply) + { + fprintf (ostream, " "); + print_contents (inp, (void *) inp + sizeof *reply, NULL); + } + fprintf (ostream, "\n"); + } + else + { + /* Remember the request port. */ + hurd_ihash_add (&ongoing_requests, trace_id, inp->msgh_local_port); + + if (! (trace_class || inp->msgh_local_port == reference_port)) + goto out; + + /* This looks like a traced request. */ + print_ellipsis (); + last_reply_port = trace_id; + + if (info) + fprintf (ostream, "% 4d->%s (", inp->msgh_local_port, info->name); + else + fprintf (ostream, "% 4d->%d (", inp->msgh_local_port, inp->msgh_id); + + print_contents (inp, (void *) inp + sizeof *inp, NULL); + fprintf (ostream, ")"); + } + + out: + /* vm_deallocate any out-of-band memory. */ + mach_msg_destroy (inp); + + /* Prevent mach_msg_server from sending messages. */ + ((mig_reply_header_t *) outp)->RetCode = MIG_NO_REPLY; + return TRUE; +} + +int +trace_class_rpcs (mach_port_t task, + mach_port_t name) +{ + error_t err; + mach_port_t trace_port; + mach_port_t introspection_port; + mach_port_t previous; + mach_port_t port_set; + + err = introspection_get_port (task, &introspection_port); + if (err) + error (13, err, "Failed to get introspection port"); + + if (! MACH_PORT_VALID (introspection_port)) + error (13, 0, "The server does not implement the introspection protocol"); + + err = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, + &trace_port); + if (err) + error (13, err, "mach_port_allocate"); + + err = hurd_port_trace_class_rpcs (introspection_port, name, + trace_port, MACH_MSG_TYPE_MAKE_SEND); + if (err) + { + if (err == EINVAL) + error (13, 0, + "%d does not denote a receive right managed by libports", name); + else + error (13, err, "hurd_port_trace_class_rpcs"); + } + + err = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_RECEIVE, + &trace_notification_port); + if (err) + error (13, err, "mach_port_allocate"); + + err = mach_port_request_notification (mach_task_self (), + trace_port, + MACH_NOTIFY_NO_SENDERS, + 0, + trace_notification_port, + MACH_MSG_TYPE_MAKE_SEND_ONCE, + &previous); + if (err) + error (13, err, "mach_port_request_notification"); + assert (! MACH_PORT_VALID (previous)); + + + err = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_PORT_SET, + &port_set); + if (err) + error (13, err, "mach_port_allocate"); + + err = mach_port_move_member (mach_task_self (), trace_port, port_set); + if (err) + error (13, err, "mach_port_move_member"); + err = mach_port_move_member (mach_task_self (), trace_notification_port, + port_set); + if (err) + error (13, err, "mach_port_move_member"); + + error (0, 0, "entering service loop"); + while (1) + mach_msg_server (trace_server, 0, port_set); + + /* Not reached. */ + return 0; +} + int main (int argc, char **argv, char **envp) { + mach_port_t target_task = MACH_PORT_NULL; const char *outfile = 0; char **cmd_argv = 0; pthread_t thread; @@ -1688,12 +1904,27 @@ main (int argc, char **argv, char **envp) /* Parse our options... */ error_t parse_opt (int key, char *arg, struct argp_state *state) { + char *arg_end; switch (key) { case 'o': outfile = arg; break; + case 'p': + target_task = parse_task (arg); + break; + + case 'P': + reference_port = strtoul (arg, &arg_end, 10); + if (*arg == '\0' || *arg_end != '\0') + argp_error (state, "Invalid port name: %s", arg); + break; + + case 'c': + trace_class = 1; + break; + case 's': strsize = atoi (arg); break; @@ -1727,10 +1958,16 @@ main (int argc, char **argv, char **envp) break; case ARGP_KEY_NO_ARGS: + if (MACH_PORT_VALID (target_task)) + break; + argp_usage (state); return EINVAL; case ARGP_KEY_ARG: + if (MACH_PORT_VALID (target_task)) + argp_error (state, "Superfluous argument: %s", arg); + cmd_argv = &state->argv[state->next - 1]; state->next = state->argc; break; @@ -1750,6 +1987,9 @@ main (int argc, char **argv, char **envp) /* Parse our arguments. */ argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, 0); + if (MACH_PORT_VALID (target_task) != MACH_PORT_VALID (reference_port)) + error (10, 0, "Please specify either both -p and -P, or neither."); + err = mach_port_allocate (mach_task_self (), MACH_PORT_RIGHT_DEAD_NAME, &unknown_task); assert_perror (err); @@ -1764,6 +2004,9 @@ main (int argc, char **argv, char **envp) ostream = stderr; setlinebuf (ostream); + if (MACH_PORT_VALID (target_task)) + return trace_class_rpcs (target_task, reference_port); + traced_bucket = ports_create_bucket (); traced_class = ports_create_class (&traced_clean, NULL); other_class = ports_create_class (0, 0); -- 2.1.4