diff options
Diffstat (limited to 'utils/rpctrace.c')
-rw-r--r-- | utils/rpctrace.c | 1062 |
1 files changed, 787 insertions, 275 deletions
diff --git a/utils/rpctrace.c b/utils/rpctrace.c index 24e20c4e..9e6825d0 100644 --- a/utils/rpctrace.c +++ b/utils/rpctrace.c @@ -62,6 +62,8 @@ static const struct argp_option options[] = {0} }; +#define UNKNOWN_NAME MACH_PORT_NULL + static const char args_doc[] = "COMMAND [ARG...]"; static const char doc[] = "Trace Mach Remote Procedure Calls."; @@ -82,10 +84,43 @@ msgid_ihash_cleanup (void *element, void *arg) 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); -task_t traced_task; +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 @@ -191,45 +226,121 @@ msgid_trace_replies (const struct msgid_info *info) { return 1; } + -/* We keep one of these structures for each port right we are tracing. */ +/* A common structure between sender_info and send_once_info */ 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 */ +}; + +/* 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 */ +}; - union - { - struct traced_info *nextfree; /* Link when on free list. */ +struct sender_info +{ + struct traced_info pi; + task_t task; /* The task who has the right. */ - struct /* For a send right wrapper. */ - { - hurd_ihash_locp_t locp; /* position in the traced_names hash table */ - } send; + /* It is used to form the list of send rights for different tasks. + * The head is the receive right. */ + struct sender_info *next; - 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; + 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; +} -static struct traced_info *freelist; +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 traced_info, u.send.locp)); + = 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; @@ -239,12 +350,13 @@ FILE *ostream; /* 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, +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 traced_info *info, - mig_reply_header_t *header); +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. */ @@ -252,42 +364,106 @@ 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) +/* 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 traced_info *info; + struct receiver_info *info; + mach_port_t foo; - /* Use a free send-once wrapper port if we have one. */ - if (freelist) + 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 = freelist; - freelist = info->u.nextfree; + info->receive_right = receive_right_list; + receive_right_list = info; } - else + 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) { - /* 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; + 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; } - info->forward = right; - info->type = MACH_MSG_TYPE_MOVE_SEND; + hurd_ihash_locp_remove (&traced_names, info->locp); + free (info); +} - /* 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); +/* 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); - ports_port_ref_weak (info); - assert (info->u.send.locp != 0); + + 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); @@ -296,17 +472,17 @@ new_send_wrapper (mach_port_t right, mach_port_t *wrapper_right) } /* Create a new wrapper port and do `ports_get_right' on it. */ -static struct traced_info * +static struct send_once_info * new_send_once_wrapper (mach_port_t right, mach_port_t *wrapper_right) { error_t err; - struct traced_info *info; + struct send_once_info *info; /* Use a free send-once wrapper port if we have one. */ if (freelist) { info = freelist; - freelist = info->u.nextfree; + freelist = info->nextfree; } else { @@ -314,11 +490,12 @@ new_send_once_wrapper (mach_port_t right, mach_port_t *wrapper_right) err = ports_create_port (traced_class, traced_bucket, sizeof *info, &info); assert_perror (err); - info->name = 0; + TRACED_INFO (info)->name = 0; } info->forward = right; - info->type = MACH_MSG_TYPE_MOVE_SEND_ONCE; + 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 @@ -331,87 +508,286 @@ new_send_once_wrapper (mach_port_t right, mach_port_t *wrapper_right) 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); + *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; + } +} -/* 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. */ +/* 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_dropweak (void *pi) +traced_clean (void *pi) { - struct traced_info *const info = pi; + struct sender_info *info = pi; - assert (info->type == MACH_MSG_TYPE_MOVE_SEND); - assert (info->u.send.locp); + assert (TRACED_INFO (info)->type == MACH_MSG_TYPE_MOVE_SEND); + free (TRACED_INFO (info)->name); - /* Remove INFO from the hash table. */ - hurd_ihash_locp_remove (&traced_names, info->u.send.locp); - ports_port_deref_weak (info); + if (info->receive_right) + { + unlink_sender_info (pi); - /* Deallocate the forward port, so the real port also sees no-senders. */ - mach_port_deallocate (mach_task_self (), info->forward); + /* 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; + } +} - /* 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); +/* 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; +} - free (info->name); - info->name = 0; +/* 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; + } - info->u.nextfree = freelist; - freelist = info; + /* 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 struct traced_info * -rewrite_right (mach_port_t *right, mach_msg_type_name_t *type) +static char * +rewrite_right (mach_port_t *right, mach_msg_type_name_t *type, + struct req_info *req) { error_t err; - struct traced_info *info; + 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; - switch (*type) + if (req) { - case MACH_MSG_TYPE_PORT_SEND: - /* See if we are already tracing this port. */ - info = hurd_ihash_find (&traced_names, *right); - if (info) + if (req->is_req) /* It's a RPC request. */ { - /* 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; + 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. + */ + assert (req); /* See if this is already one of our own wrapper ports. */ - info = ports_lookup_port (traced_bucket, *right, 0); - if (info) + send_wrapper = ports_lookup_port (traced_bucket, *right, 0); + if (send_wrapper) { - /* This is a send right to one of our own wrapper ports. - Instead, send along the original send right. */ + /* This is a send right to one of our own wrapper ports. */ 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; + + /* 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; } - /* 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); + if (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) + { + 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 @@ -421,62 +797,99 @@ rewrite_right (mach_port_t *right, mach_msg_type_name_t *type) 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); + return TRACED_INFO (new_send_once_wrapper (*right, right))->name; case MACH_MSG_TYPE_PORT_RECEIVE: - /* We have got a receive right, call it A. We will pass along a - different receive right of our own, call it B. We ourselves will - receive messages on A, trace them, and forward them on to B. - - If A is the receive right to a send right that we have wrapped, - then B must be that wrapper receive right, moved from us to the - intended receiver of A--that way it matches previous send rights - to A that were sent through and replaced with our wrapper (B). - If not, we create a new receive right. */ + /* 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. */ { - mach_port_t rr; /* B */ - char *name; - - info = hurd_ihash_find (&traced_names, *right); - if (info) + assert (req); + receiver_info = hurd_ihash_find (&traced_names, *right); + if (receiver_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. */ + 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 { - /* This is a port we know nothing about. */ - rr = mach_reply_port (); - name = 0; + /* 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); } - /* 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; + return receiver_info->name; } default: @@ -487,7 +900,7 @@ rewrite_right (mach_port_t *right, mach_msg_type_name_t *type) static void print_contents (mach_msg_header_t *inp, - void *msg_buf_ptr) + void *msg_buf_ptr, struct req_info *req) { error_t err; @@ -546,7 +959,6 @@ print_contents (mach_msg_header_t *inp, 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)); @@ -554,19 +966,11 @@ print_contents (mach_msg_header_t *inp, poly = 0; for (i = 0; i < nelt; ++i) { - newtypes[i] = name; + char *str; - 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; - } + newtypes[i] = name; - ti = rewrite_right (&portnames[i], &newtypes[i]); + str = rewrite_right (&portnames[i], &newtypes[i], req); putc ((i == 0 && nelt > 1) ? '{' : ' ', ostream); @@ -576,9 +980,8 @@ print_contents (mach_msg_header_t *inp, fprintf (ostream, "(dead)"); else { - assert (ti); - if (ti->name != 0) - fprintf (ostream, "%s", ti->name); + if (str != 0) + fprintf (ostream, "%s", str); else fprintf (ostream, "%3u", (unsigned int) portnames[i]); } @@ -649,11 +1052,12 @@ print_contents (mach_msg_header_t *inp, } } -/* Wrap all thread port in the task */ +/* Wrap all thread ports in the task */ static void wrap_all_threads (task_t task) { - struct traced_info *thread_send_wrapper; + struct sender_info *thread_send_wrapper; + struct receiver_info *thread_receiver_info; thread_t *threads; size_t nthreads; error_t err; @@ -664,14 +1068,18 @@ wrap_all_threads (task_t task) for (int i = 0; i < nthreads; ++i) { - thread_send_wrapper = hurd_ihash_find (&traced_names, threads[i]); + thread_receiver_info = hurd_ihash_find (&traced_names, threads[i]); /* We haven't seen the port. */ - if (thread_send_wrapper == NULL) + if (thread_receiver_info == NULL) { mach_port_t new_thread_port; - thread_send_wrapper = new_send_wrapper (threads[i], &new_thread_port); - free (thread_send_wrapper->name); - asprintf (&thread_send_wrapper->name, "thread%d", threads[i]); + + 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, @@ -691,7 +1099,7 @@ wrap_all_threads (task_t task) /* Wrap the new thread port that is in the message. */ static void -wrap_new_thread (mach_msg_header_t *inp) +wrap_new_thread (mach_msg_header_t *inp, struct req_info *req) { error_t err; mach_port_t thread_port; @@ -705,11 +1113,12 @@ wrap_new_thread (mach_msg_header_t *inp) } *reply = (void *) inp; /* This function is called after rewrite_right, * so the wrapper for the thread port has been created. */ - struct traced_info *send_wrapper = ports_lookup_port (traced_bucket, + struct sender_info *send_wrapper = ports_lookup_port (traced_bucket, reply->child_thread, 0); assert (send_wrapper); - thread_port = send_wrapper->forward; + 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); @@ -720,14 +1129,67 @@ wrap_new_thread (mach_msg_header_t *inp) error (2, err, "thread_set_kernel_port"); mach_port_deallocate (mach_task_self (), reply->child_thread); - free (send_wrapper->name); - asprintf (&send_wrapper->name, "thread%d", thread_port); + 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 = */ @@ -754,19 +1216,28 @@ trace_and_forward (mach_msg_header_t *inp, mach_msg_header_t *outp) 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 (inp->msgh_id == MACH_NOTIFY_DEAD_NAME && info == (void *) notify_pi) { - /* If INFO is a send-once wrapper, this could be a forged - notification; oh well. XXX */ - + struct receiver_info *receiver_info; 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); + 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 @@ -781,8 +1252,17 @@ trace_and_forward (mach_msg_header_t *inp, mach_msg_header_t *outp) ((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; @@ -793,31 +1273,43 @@ trace_and_forward (mach_msg_header_t *inp, mach_msg_header_t *outp) { 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)) + if (reply_type && msgid_trace_replies (msgid) + /* The reply port might be dead, e.g., the traced task has died. */ + && MACH_PORT_VALID (inp->msgh_local_port)) { - struct traced_info *info; - info = rewrite_right (&inp->msgh_local_port, &reply_type); - assert (info); - if (info->name == 0) + struct send_once_info *info; + // TODO is the reply port always a send once right? + assert (reply_type == MACH_MSG_TYPE_PORT_SEND_ONCE); + info = new_send_once_wrapper (inp->msgh_local_port, + &inp->msgh_local_port); + reply_type = MACH_MSG_TYPE_MAKE_SEND_ONCE; + assert (inp->msgh_local_port); + + if (TRACED_INFO (info)->name == 0) { if (msgid == 0) - asprintf (&info->name, "reply(%u:%u)", - (unsigned int) info->pi.port_right, + asprintf (&TRACED_INFO (info)->name, "reply(%u:%u)", + (unsigned int) TRACED_INFO (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; + asprintf (&TRACED_INFO (info)->name, "reply(%u:%s)", + (unsigned int) TRACED_INFO (info)->pi.port_right, + msgid->name); } } - inp->msgh_remote_port = info->forward; + 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. @@ -827,8 +1319,9 @@ trace_and_forward (mach_msg_header_t *inp, mach_msg_header_t *outp) we are consuming its `forward' right in the message we send. */ free (info->name); info->name = 0; - info->u.nextfree = freelist; - freelist = info; + 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; @@ -844,34 +1337,81 @@ trace_and_forward (mach_msg_header_t *inp, mach_msg_header_t *outp) 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 (info, rh); + print_reply_header ((struct send_once_info *) info, rh, req); putc (' ', ostream); - print_contents (&rh->Head, rh + 1); + 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); + 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 (info, inp); - print_contents (inp, inp + 1); + 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 */ - fprintf (ostream, ");\n"); + { + /* 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 request of exec_startup_get_info, - * it means that the traced process starts to run */ - if (inp->msgh_id == 30500) - wrap_all_threads (traced_task); + /* 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; + } } } @@ -934,19 +1474,16 @@ static const char *const msg_types[] = }; #endif -static mach_port_t expected_reply_port; - static void -print_request_header (struct traced_info *receiver, mach_msg_header_t *msg) +print_request_header (struct sender_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); + if (TRACED_INFO (receiver)->name != 0) + fprintf (ostream, "%4s->", TRACED_INFO (receiver)->name); else - fprintf (ostream, "%4u->", (unsigned int) receiver->pi.port_right); + fprintf (ostream, "%4u->", + (unsigned int) TRACED_INFO (receiver)->pi.port_right); if (msgname != 0) fprintf (ostream, "%5s (", msgname); @@ -955,57 +1492,17 @@ print_request_header (struct traced_info *receiver, mach_msg_header_t *msg) } static void -unfinished_line (void) +print_reply_header (struct send_once_info *info, mig_reply_header_t *reply, + struct req_info *req) { - /* 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); - } + /* 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 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); - } - } + /* 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"); @@ -1017,8 +1514,6 @@ print_reply_header (struct traced_info *info, mig_reply_header_t *reply) else fprintf (ostream, "%#x (%s)", reply->RetCode, str); } - - expected_reply_port = MACH_PORT_NULL; } @@ -1115,7 +1610,9 @@ traced_spawn (char **argv, char **envp) error_t err; pid_t pid; mach_port_t task_wrapper; - struct traced_info *ti; + 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); @@ -1129,6 +1626,7 @@ traced_spawn (char **argv, char **envp) 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) @@ -1137,9 +1635,12 @@ traced_spawn (char **argv, char **envp) 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 (traced_task, &task_wrapper);/* consumes ref */ - asprintf (&ti->name, "task%d", (int) pid); + 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 @@ -1215,6 +1716,7 @@ main (int argc, char **argv, char **envp) bool nostdinc = FALSE; const char *outfile = 0; char **cmd_argv = 0; + error_t err; /* Parse our options... */ error_t parse_opt (int key, char *arg, struct argp_state *state) @@ -1263,6 +1765,10 @@ main (int argc, char **argv, char **envp) /* 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) @@ -1291,7 +1797,11 @@ main (int argc, char **argv, char **envp) setlinebuf (ostream); traced_bucket = ports_create_bucket (); - traced_class = ports_create_class (0, &traced_dropweak); + 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), ¬ify_pi); + assert_perror (err); hurd_ihash_set_cleanup (&msgid_ihash, msgid_ihash_cleanup, 0); @@ -1317,6 +1827,8 @@ main (int argc, char **argv, char **envp) else fprintf (ostream, "Child %d %s\n", pid, strsignal (WTERMSIG (status))); } + + ports_destroy_right (notify_pi); return 0; } |