/* * Copyright (c) 2002-2016 Apple Inc. All rights reserved. * * @APPLE_LICENSE_HEADER_START@ * * The contents of this file constitute Original Code as defined in and * are subject to the Apple Public Source License Version 1.1 (the * "License"). You may not use this file except in compliance with the * License. Please obtain a copy of the License at * http://www.apple.com/publicsource and read it before using this file. * * This Original Code and all software distributed under the License are * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the * License for the specific language governing rights and limitations * under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #include #include #include "common.h" #pragma mark kobject to name hash table implementation #if (K2N_TABLE_SIZE & (K2N_TABLE_SIZE - 1) != 0) #error K2N_TABLE_SIZE must be a power of two #endif #define K2N_TABLE_MASK (K2N_TABLE_SIZE - 1) static uint32_t k2n_hash(natural_t kobject) { return (uint64_t)kobject * 2654435761 >> 32; } static struct k2n_table_node *k2n_table_lookup_next_internal(struct k2n_table_node *node, natural_t kobject) { while (node) { if (kobject == node->kobject) return node; node = node->next; } return NULL; } struct k2n_table_node *k2n_table_lookup_next(struct k2n_table_node *node, natural_t kobject) { if (!node) { return NULL; } return k2n_table_lookup_next_internal(node->next, kobject); } struct k2n_table_node *k2n_table_lookup(struct k2n_table_node **table, natural_t kobject) { uint32_t hv; struct k2n_table_node *node; hv = k2n_hash(kobject); node = table[hv & K2N_TABLE_MASK]; return k2n_table_lookup_next_internal(node, kobject); } static void k2n_table_enter(struct k2n_table_node **table, natural_t kobject, ipc_info_name_t *info_name) { uint32_t hv; struct k2n_table_node *node; hv = k2n_hash(kobject); node = malloc(sizeof (struct k2n_table_node)); assert(node); node->kobject = kobject; node->info_name = info_name; assert(kobject == info_name->iin_object); node->next = table[hv & K2N_TABLE_MASK]; table[hv & K2N_TABLE_MASK] = node; } #pragma mark - static my_per_task_info_t NOT_FOUND_TASK_INFO = { .task = NULL, .task_kobject = NULL, .pid = -1, .info = {0,0,0,0,0,0}, .table = NULL, .tableCount = 0, .tree = NULL, .treeCount = 0, .valid = FALSE, .k2ntable = {0}, .processName = "Unknown", .exceptionInfo = {0}, .threadInfos = NULL, .threadCount = 0, .threadExceptionInfos = NULL }; char * get_task_name_by_pid(pid_t pid); static void proc_pid_to_name(int pid, char *pname){ if (0 == proc_name(pid, pname, PROC_NAME_LEN)) { strcpy(pname, "Unknown Process"); } } static my_per_task_info_t *global_taskinfo = NULL; static uint32_t global_taskcount = 0; my_per_task_info_t * allocate_taskinfo_memory(uint32_t taskCount) { my_per_task_info_t * retval = malloc(taskCount * sizeof(my_per_task_info_t)); if (retval) { bzero((void *)retval, taskCount * sizeof(my_per_task_info_t)); } global_taskcount = taskCount; global_taskinfo = retval; return retval; } void deallocate_taskinfo_memory(my_per_task_info_t *data){ if (data) { free(data); global_taskinfo = NULL; global_taskcount = 0; } } kern_return_t collect_per_task_info(my_per_task_info_t *taskinfo, task_read_t target_task) { int i; kern_return_t ret = KERN_SUCCESS; unsigned int kotype = 0; vm_offset_t kobject = (vm_offset_t)0; taskinfo->task = target_task; pid_for_task(target_task, &taskinfo->pid); ret = task_get_exception_ports_info(taskinfo->task, EXC_MASK_ALL, taskinfo->exceptionInfo.masks, &taskinfo->exceptionInfo.count, taskinfo->exceptionInfo.ports_info, taskinfo->exceptionInfo.behaviors, taskinfo->exceptionInfo.flavors); if (ret != KERN_SUCCESS) { fprintf(stderr, "task_get_exception_ports_info() failed: pid:%d error: %s\n",taskinfo->pid, mach_error_string(ret)); taskinfo->pid = 0; } /* collect threads port as well */ taskinfo->threadCount = 0; thread_act_array_t threadPorts; ret = task_threads(taskinfo->task, &threadPorts, &taskinfo->threadCount); if (ret != KERN_SUCCESS) { fprintf(stderr, "task_threads() failed: pid:%d error: %s\n",taskinfo->pid, mach_error_string(ret)); taskinfo->threadCount = 0; } else { /* collect the thread information */ taskinfo->threadInfos = (struct my_per_thread_info *)malloc(sizeof(struct my_per_thread_info) * taskinfo->threadCount); bzero(taskinfo->threadInfos, sizeof(struct my_per_thread_info) * taskinfo->threadCount); /* now collect exception ports for each of those threads as well */ taskinfo->threadExceptionInfos = (struct exc_port_info *) malloc(sizeof(struct exc_port_info) * taskinfo->threadCount); boolean_t found_exception = false; for (int i = 0; i < taskinfo->threadCount; i++) { unsigned th_kobject = 0; unsigned th_kotype = 0; ipc_voucher_t th_voucher = IPC_VOUCHER_NULL; thread_identifier_info_data_t th_info; mach_msg_type_number_t th_info_count = THREAD_IDENTIFIER_INFO_COUNT; struct exc_port_info *excinfo = &(taskinfo->threadExceptionInfos[i]); ret = thread_get_exception_ports_info(threadPorts[i], EXC_MASK_ALL, excinfo->masks, &excinfo->count, excinfo->ports_info, excinfo->behaviors, excinfo->flavors); if (ret != KERN_SUCCESS){ fprintf(stderr, "thread_get_exception_ports_info() failed: pid: %d thread: %d error %s\n", taskinfo->pid, threadPorts[i], mach_error_string(ret)); } if (excinfo->count != 0) { found_exception = true; } taskinfo->threadInfos[i].thread = threadPorts[i]; if (KERN_SUCCESS == mach_port_kernel_object(mach_task_self(), threadPorts[i], &th_kotype, &th_kobject)) { taskinfo->threadInfos[i].th_kobject = th_kobject; if (KERN_SUCCESS == thread_info(threadPorts[i], THREAD_IDENTIFIER_INFO, (thread_info_t)&th_info, &th_info_count)) { taskinfo->threadInfos[i].th_id = th_info.thread_id; } } if (KERN_SUCCESS == thread_get_mach_voucher(threadPorts[i], 0, &th_voucher) && th_voucher != IPC_VOUCHER_NULL) { char *detail = copy_voucher_detail(mach_task_self(), th_voucher, NULL); taskinfo->threadInfos[i].voucher_detail = strndup(detail, VOUCHER_DETAIL_MAXLEN); free(detail); mach_port_deallocate(mach_task_self(), th_voucher); } mach_port_deallocate(mach_task_self(), threadPorts[i]); threadPorts[i] = MACH_PORT_NULL; } if (found_exception == false) { free(taskinfo->threadExceptionInfos); taskinfo->threadExceptionInfos = NULL; } } vm_deallocate(mach_task_self(), threadPorts, taskinfo->threadCount * sizeof(thread_act_t)); threadPorts = NULL; ret = mach_port_space_info(target_task, &taskinfo->info, &taskinfo->table, &taskinfo->tableCount, &taskinfo->tree, &taskinfo->treeCount); if (ret != KERN_SUCCESS) { fprintf(stderr, "mach_port_space_info() failed: pid:%d error: %s\n",taskinfo->pid, mach_error_string(ret)); taskinfo->pid = 0; return ret; } bzero(taskinfo->k2ntable, K2N_TABLE_SIZE * sizeof (struct k2n_table_node *)); for (i = 0; i < taskinfo->tableCount; i++) { k2n_table_enter(taskinfo->k2ntable, taskinfo->table[i].iin_object, &taskinfo->table[i]); } proc_pid_to_name(taskinfo->pid, taskinfo->processName); ret = mach_port_kernel_object(mach_task_self(), taskinfo->task, &kotype, (unsigned *)&kobject); /* Now that we are using read ports, kotype should be checked against IKOT_TASK_READ */ if (ret == KERN_SUCCESS && kotype == IKOT_TASK_READ) { taskinfo->task_kobject = kobject; taskinfo->valid = TRUE; } return ret; } void get_exc_behavior_string(exception_behavior_t b, char *out_string, size_t len) { out_string[0]='\0'; if (b & MACH_EXCEPTION_CODES) strlcat(out_string, "MACH +", len); switch (b & ~MACH_EXCEPTION_CODES) { case EXCEPTION_DEFAULT: strlcat(out_string, " DEFAULT", len); break; case EXCEPTION_STATE: strlcat(out_string, " STATE", len); break; case EXCEPTION_STATE_IDENTITY: strlcat(out_string, " IDENTITY", len); break; default: strlcat(out_string, " UNKNOWN", len); } } void get_exc_mask_string(exception_mask_t m, char *out_string, size_t len) { out_string[0]='\0'; if (m & (1<exceptionInfo.count; i++) { if (taskinfo->exceptionInfo.ports_info[i].iip_port_object != 0) { my_per_task_info_t * _found_task; if (header_required) { printf(" exc_port_object receiver_task flavor mask \n"); header_required = FALSE; } get_exc_behavior_string(taskinfo->exceptionInfo.behaviors[i], behavior_string, sizeof(behavior_string)); get_exc_mask_string(taskinfo->exceptionInfo.masks[i], mask_string, sizeof(mask_string)); JSON_OBJECT_BEGIN(json); JSON_OBJECT_SET(json, port_object, "0x%08x", taskinfo->exceptionInfo.ports_info[i].iip_port_object); JSON_OBJECT_SET(json, receiver_object, "0x%08x", taskinfo->exceptionInfo.ports_info[i].iip_receiver_object); JSON_OBJECT_SET(json, flavor, "0x%03x", taskinfo->exceptionInfo.flavors[i]); JSON_OBJECT_SET(json, behavior, "%s", behavior_string); JSON_OBJECT_SET(json, mask, "%s", mask_string); JSON_OBJECT_END(json); // exception port _found_task = get_taskinfo_by_kobject((natural_t)taskinfo->exceptionInfo.ports_info[i].iip_receiver_object); printf(" 0x%08x (%d) %s 0x%03x <%s> %s \n", taskinfo->exceptionInfo.ports_info[i].iip_port_object, _found_task->pid, _found_task->processName, taskinfo->exceptionInfo.flavors[i], behavior_string, mask_string); } } JSON_ARRAY_END(json); // exception ports return KERN_SUCCESS; } kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo, JSON_t json) { kern_return_t kret = KERN_SUCCESS; mach_msg_type_number_t threadcount = taskinfo->threadCount; boolean_t header_required = TRUE; boolean_t newline_required = TRUE; struct my_per_thread_info * info = NULL; JSON_KEY(json, threads); JSON_ARRAY_BEGIN(json); for (int i = 0; i < threadcount; i++) { JSON_OBJECT_BEGIN(json); info = &taskinfo->threadInfos[i]; if (header_required) { printf("Thread_KObject Thread-ID Port Description."); header_required = FALSE; } if (newline_required) { printf("\n"); } newline_required = TRUE; if (info->th_kobject != 0) { /* TODO: Should print tid and stuff */ printf("0x%08x ", info->th_kobject); printf("0x%llx ", info->th_id); JSON_OBJECT_SET(json, kobject, "0x%08x", info->th_kobject); JSON_OBJECT_SET(json, tid, "0x%llx", info->th_id); } if (info->voucher_detail != NULL) { /* TODO: include voucher detail in JSON */ printf("%s\n", info->voucher_detail); } JSON_KEY(json, exception_ports); JSON_ARRAY_BEGIN(json); /* print the thread exception ports also */ if (taskinfo->threadExceptionInfos != NULL) { struct exc_port_info *excinfo = &taskinfo->threadExceptionInfos[i]; char behavior_string[30]; char mask_string[200]; if (excinfo->count > 0) { boolean_t header_required = TRUE; for (int i = 0; i < excinfo->count; i++) { JSON_OBJECT_BEGIN(json); if (excinfo->ports_info[i].iip_port_object != 0) { if (header_required) { printf("\n exc_port_object exc_port_receiver flavor mask -> name owner\n"); header_required = FALSE; } get_exc_behavior_string(excinfo->behaviors[i], behavior_string, sizeof(behavior_string)); get_exc_mask_string(excinfo->masks[i], mask_string, sizeof(mask_string)); JSON_OBJECT_SET(json, port_object, "0x%08x", excinfo->ports_info[i].iip_port_object); JSON_OBJECT_SET(json, receiver_object, "0x%08x", excinfo->ports_info[i].iip_receiver_object); JSON_OBJECT_SET(json, flavor, "0x%03x", excinfo->flavors[i]); JSON_OBJECT_SET(json, behavior, "%s", behavior_string); JSON_OBJECT_SET(json, mask, "%s", mask_string); printf(" 0x%08x 0x%08x 0x%03x <%s> %s " , excinfo->ports_info[i].iip_port_object, excinfo->ports_info[i].iip_receiver_object, excinfo->flavors[i], behavior_string, mask_string); my_per_task_info_t *recv_holder_taskinfo; mach_port_name_t recv_name = MACH_PORT_NULL; if (KERN_SUCCESS == get_taskinfo_of_receiver_by_send_right_info(excinfo->ports_info[i], &recv_holder_taskinfo, &recv_name)) { JSON_OBJECT_SET(json, name, "0x%08x", recv_name); JSON_OBJECT_SET(json, ipc-object, "0x%08x", excinfo->ports_info[i].iip_port_object); JSON_OBJECT_SET(json, pid, %d, recv_holder_taskinfo->pid); JSON_OBJECT_SET(json, process, "%s", recv_holder_taskinfo->processName); printf(" -> 0x%08x 0x%08x (%d) %s\n", recv_name, excinfo->ports_info[i].iip_port_object, recv_holder_taskinfo->pid, recv_holder_taskinfo->processName); } else { fprintf(stderr, "failed to find"); } printf("\n"); } JSON_OBJECT_END(json); // exception port } } } JSON_ARRAY_END(json); // exception ports JSON_OBJECT_END(json); // thread } JSON_ARRAY_END(json); // threads printf("\n"); return kret; } char * get_task_name_by_pid(pid_t pid) { char * retval = "Unknown"; for (int i = 0; i < global_taskcount; i++) { if (pid == global_taskinfo[i].pid) { return global_taskinfo[i].processName; } } return retval; } my_per_task_info_t * get_taskinfo_by_kobject(natural_t kobj) { my_per_task_info_t *retval = &NOT_FOUND_TASK_INFO; for (int j = 0; j < global_taskcount; j++) { if (global_taskinfo[j].task_kobject == kobj) { retval = &global_taskinfo[j]; break; } } return retval; } static kern_return_t _get_taskinfo_of_receiver_by_send_right(natural_t kobject, my_per_task_info_t **out_taskinfo, mach_port_name_t *out_recv_info) { *out_taskinfo = &NOT_FOUND_TASK_INFO; struct k2n_table_node *k2nnode; for (unsigned int j = 0; j < global_taskcount; j++) { if ((k2nnode = k2n_table_lookup(global_taskinfo[j].k2ntable, kobject))) { assert(k2nnode->info_name->iin_object == kobject); if (k2nnode->info_name->iin_type & MACH_PORT_TYPE_RECEIVE) { *out_taskinfo = &global_taskinfo[j]; *out_recv_info = k2nnode->info_name->iin_name; return KERN_SUCCESS; } } } return KERN_FAILURE; } kern_return_t get_taskinfo_of_receiver_by_send_right(ipc_info_name_t sendright, my_per_task_info_t **out_taskinfo, mach_port_name_t *out_recv_info) { return _get_taskinfo_of_receiver_by_send_right(sendright.iin_object, out_taskinfo, out_recv_info); } kern_return_t get_taskinfo_of_receiver_by_send_right_info(ipc_info_port_t sendright_info, my_per_task_info_t **out_taskinfo, mach_port_name_t *out_recv_info) { return _get_taskinfo_of_receiver_by_send_right(sendright_info.iip_port_object, out_taskinfo, out_recv_info); }