| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <mach/mach.h> |
| #include <mach/task_info.h> |
| #include <time.h> |
| #include <sys/sysctl.h> |
| #include <ctype.h> |
| #include <libproc.h> |
| #include <errno.h> |
| |
| /* Step through the process table, find a matching process name, return |
| the pid of that matched process. |
| If there are multiple processes with that name, issue a warning on stdout |
| and return the highest numbered process. |
| The proc_pidpath() call is used which gets the full process name including |
| directories to the executable and the full (longer than 16 character) |
| executable name. */ |
| |
| pid_t |
| get_pid_for_process_name (const char *procname) |
| { |
| int process_count = proc_listpids (PROC_ALL_PIDS, 0, NULL, 0) / sizeof (pid_t); |
| if (process_count < 1) |
| { |
| printf ("Only found %d processes running!\n", process_count); |
| exit (1); |
| } |
| |
| // Allocate a few extra slots in case new processes are spawned |
| int all_pids_size = sizeof (pid_t) * (process_count + 3); |
| pid_t *all_pids = (pid_t *) malloc (all_pids_size); |
| |
| // re-set process_count in case the number of processes changed (got smaller; we won't do bigger) |
| process_count = proc_listpids (PROC_ALL_PIDS, 0, all_pids, all_pids_size) / sizeof (pid_t); |
| |
| int i; |
| pid_t highest_pid = 0; |
| int match_count = 0; |
| for (i = 1; i < process_count; i++) |
| { |
| char pidpath[PATH_MAX]; |
| int pidpath_len = proc_pidpath (all_pids[i], pidpath, sizeof (pidpath)); |
| if (pidpath_len == 0) |
| continue; |
| char *j = strrchr (pidpath, '/'); |
| if ((j == NULL && strcmp (procname, pidpath) == 0) |
| || (j != NULL && strcmp (j + 1, procname) == 0)) |
| { |
| match_count++; |
| if (all_pids[i] > highest_pid) |
| highest_pid = all_pids[i]; |
| } |
| } |
| free (all_pids); |
| |
| if (match_count == 0) |
| { |
| printf ("Did not find process '%s'.\n", procname); |
| exit (1); |
| } |
| if (match_count > 1) |
| { |
| printf ("Warning: More than one process '%s'!\n", procname); |
| printf (" defaulting to the highest-pid one, %d\n", highest_pid); |
| } |
| return highest_pid; |
| } |
| |
| /* Given a pid, get the full executable name (including directory |
| paths and the longer-than-16-chars executable name) and return |
| the basename of that (i.e. do not include the directory components). |
| This function mallocs the memory for the string it returns; |
| the caller must free this memory. */ |
| |
| const char * |
| get_process_name_for_pid (pid_t pid) |
| { |
| char tmp_name[PATH_MAX]; |
| if (proc_pidpath (pid, tmp_name, sizeof (tmp_name)) == 0) |
| { |
| printf ("Could not find process with pid of %d\n", (int) pid); |
| exit (1); |
| } |
| if (strrchr (tmp_name, '/')) |
| return strdup (strrchr (tmp_name, '/') + 1); |
| else |
| return strdup (tmp_name); |
| } |
| |
| /* Get a struct kinfo_proc structure for a given pid. |
| Process name is required for error printing. |
| Gives you the current state of the process and whether it is being debugged by anyone. |
| memory is malloc()'ed for the returned struct kinfo_proc |
| and must be freed by the caller. */ |
| |
| struct kinfo_proc * |
| get_kinfo_proc_for_pid (pid_t pid, const char *process_name) |
| { |
| struct kinfo_proc *kinfo = (struct kinfo_proc *) malloc (sizeof (struct kinfo_proc)); |
| int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; |
| size_t len = sizeof (struct kinfo_proc); |
| if (sysctl (mib, sizeof (mib) / sizeof (mib[0]), kinfo, &len, NULL, 0) != 0) |
| { |
| free ((void *) kinfo); |
| printf ("Could not get kinfo_proc for pid %d\n", (int) pid); |
| exit (1); |
| } |
| return kinfo; |
| } |
| |
| /* Get the basic information (thread_basic_info_t) about a given |
| thread. |
| Gives you the suspend count; thread state; user time; system time; sleep time; etc. |
| The return value is a pointer to malloc'ed memory - it is the caller's |
| responsibility to free it. */ |
| |
| thread_basic_info_t |
| get_thread_basic_info (thread_t thread) |
| { |
| kern_return_t kr; |
| integer_t *thinfo = (integer_t *) malloc (sizeof (integer_t) * THREAD_INFO_MAX); |
| mach_msg_type_number_t thread_info_count = THREAD_INFO_MAX; |
| kr = thread_info (thread, THREAD_BASIC_INFO, |
| (thread_info_t) thinfo, &thread_info_count); |
| if (kr != KERN_SUCCESS) |
| { |
| printf ("Error - unable to get basic thread info for a thread\n"); |
| exit (1); |
| } |
| return (thread_basic_info_t) thinfo; |
| } |
| |
| /* Get the thread identifier info (thread_identifier_info_data_t) |
| about a given thread. |
| Gives you the system-wide unique thread number; the pthread identifier number |
| */ |
| |
| thread_identifier_info_data_t |
| get_thread_identifier_info (thread_t thread) |
| { |
| kern_return_t kr; |
| thread_identifier_info_data_t tident; |
| mach_msg_type_number_t tident_count = THREAD_IDENTIFIER_INFO_COUNT; |
| kr = thread_info (thread, THREAD_IDENTIFIER_INFO, |
| (thread_info_t) &tident, &tident_count); |
| if (kr != KERN_SUCCESS) |
| { |
| printf ("Error - unable to get thread ident for a thread\n"); |
| exit (1); |
| } |
| return tident; |
| } |
| |
| |
| /* Given a mach port # (in the examine-threads mach port namespace) for a thread, |
| find the mach port # in the inferior program's port namespace. |
| Sets inferior_port if successful. |
| Returns true if successful, false if unable to find the port number. */ |
| |
| bool |
| inferior_namespace_mach_port_num (task_t task, thread_t examine_threads_port, thread_t *inferior_port) |
| { |
| kern_return_t retval; |
| mach_port_name_array_t names; |
| mach_msg_type_number_t nameslen; |
| mach_port_type_array_t types; |
| mach_msg_type_number_t typeslen; |
| |
| if (inferior_port == NULL) |
| return false; |
| |
| retval = mach_port_names (task, &names, &nameslen, &types, &typeslen); |
| if (retval != KERN_SUCCESS) |
| { |
| printf ("Error - unable to get mach port names for inferior.\n"); |
| return false; |
| } |
| int i = 0; |
| for (i = 0; i < nameslen; i++) |
| { |
| mach_port_t local_name; |
| mach_msg_type_name_t local_type; |
| retval = mach_port_extract_right (task, names[i], MACH_MSG_TYPE_COPY_SEND, &local_name, &local_type); |
| if (retval == KERN_SUCCESS) |
| { |
| mach_port_deallocate (mach_task_self(), local_name); |
| if (local_name == examine_threads_port) |
| { |
| *inferior_port = names[i]; |
| vm_deallocate (mach_task_self (), (vm_address_t) names, nameslen * sizeof (mach_port_t)); |
| vm_deallocate (mach_task_self (), (vm_address_t) types, typeslen * sizeof (mach_port_t)); |
| return true; |
| } |
| } |
| } |
| vm_deallocate (mach_task_self (), (vm_address_t) names, nameslen * sizeof (mach_port_t)); |
| vm_deallocate (mach_task_self (), (vm_address_t) types, typeslen * sizeof (mach_port_t)); |
| return false; |
| } |
| |
| /* Get the current pc value for a given thread. */ |
| |
| uint64_t |
| get_current_pc (thread_t thread, int *wordsize) |
| { |
| kern_return_t kr; |
| |
| #if defined (__x86_64__) || defined (__i386__) |
| x86_thread_state_t gp_regs; |
| mach_msg_type_number_t gp_count = x86_THREAD_STATE_COUNT; |
| kr = thread_get_state (thread, x86_THREAD_STATE, |
| (thread_state_t) &gp_regs, &gp_count); |
| if (kr != KERN_SUCCESS) |
| { |
| printf ("Error - unable to get registers for a thread\n"); |
| exit (1); |
| } |
| |
| if (gp_regs.tsh.flavor == x86_THREAD_STATE64) |
| { |
| *wordsize = 8; |
| return gp_regs.uts.ts64.__rip; |
| } |
| else |
| { |
| *wordsize = 4; |
| return gp_regs.uts.ts32.__eip; |
| } |
| #endif |
| |
| #if defined (__arm__) |
| arm_thread_state_t gp_regs; |
| mach_msg_type_number_t gp_count = ARM_THREAD_STATE_COUNT; |
| kr = thread_get_state (thread, ARM_THREAD_STATE, |
| (thread_state_t) &gp_regs, &gp_count); |
| if (kr != KERN_SUCCESS) |
| { |
| printf ("Error - unable to get registers for a thread\n"); |
| exit (1); |
| } |
| return gp_regs.__pc; |
| *wordsize = 4; |
| #endif |
| |
| } |
| |
| /* Get the proc_threadinfo for a given thread. |
| Gives you the thread name, if set; current and max priorities. |
| Returns 1 if successful |
| Returns 0 if proc_pidinfo() failed |
| */ |
| |
| int |
| get_proc_threadinfo (pid_t pid, uint64_t thread_handle, struct proc_threadinfo *pth) |
| { |
| pth->pth_name[0] = '\0'; |
| int ret = proc_pidinfo (pid, PROC_PIDTHREADINFO, thread_handle, |
| pth, sizeof (struct proc_threadinfo)); |
| if (ret != 0) |
| return 1; |
| else |
| return 0; |
| } |
| |
| int |
| main (int argc, char **argv) |
| { |
| kern_return_t kr; |
| task_t task; |
| thread_t thread; |
| pid_t pid = 0; |
| char *procname = NULL; |
| int arg_is_procname = 0; |
| int do_loop = 0; |
| int verbose = 0; |
| int resume_when_done = 0; |
| mach_port_t mytask = mach_task_self (); |
| |
| if (argc != 2 && argc != 3 && argc != 4 && argc != 5) |
| { |
| printf ("Usage: tdump [-l] [-v] [-r] pid/procname\n"); |
| exit (1); |
| } |
| |
| if (argc == 3 || argc == 4) |
| { |
| int i = 1; |
| while (i < argc - 1) |
| { |
| if (strcmp (argv[i], "-l") == 0) |
| do_loop = 1; |
| if (strcmp (argv[i], "-v") == 0) |
| verbose = 1; |
| if (strcmp (argv[i], "-r") == 0) |
| resume_when_done = 1; |
| i++; |
| } |
| } |
| |
| char *c = argv[argc - 1]; |
| if (*c == '\0') |
| { |
| printf ("Usage: tdump [-l] [-v] pid/procname\n"); |
| exit (1); |
| } |
| while (*c != '\0') |
| { |
| if (!isdigit (*c)) |
| { |
| arg_is_procname = 1; |
| procname = argv[argc - 1]; |
| break; |
| } |
| c++; |
| } |
| |
| if (arg_is_procname && procname) |
| { |
| pid = get_pid_for_process_name (procname); |
| } |
| else |
| { |
| errno = 0; |
| pid = (pid_t) strtol (argv[argc - 1], NULL, 10); |
| if (pid == 0 && errno == EINVAL) |
| { |
| printf ("Usage: tdump [-l] [-v] pid/procname\n"); |
| exit (1); |
| } |
| } |
| |
| const char *process_name = get_process_name_for_pid (pid); |
| |
| // At this point "pid" is the process id and "process_name" is the process name |
| // Now we have to get the process list from the kernel (which only has the truncated |
| // 16 char names) |
| |
| struct kinfo_proc *kinfo = get_kinfo_proc_for_pid (pid, process_name); |
| |
| printf ("pid %d (%s) is currently ", pid, process_name); |
| switch (kinfo->kp_proc.p_stat) { |
| case SIDL: printf ("being created by fork"); break; |
| case SRUN: printf ("runnable"); break; |
| case SSLEEP: printf ("sleeping on an address"); break; |
| case SSTOP: printf ("suspended"); break; |
| case SZOMB: printf ("zombie state - awaiting collection by parent"); break; |
| default: printf ("unknown"); |
| } |
| if (kinfo->kp_proc.p_flag & P_TRACED) |
| printf (" and is being debugged."); |
| free ((void *) kinfo); |
| |
| printf ("\n"); |
| |
| kr = task_for_pid (mach_task_self (), pid, &task); |
| if (kr != KERN_SUCCESS) |
| { |
| printf ("Error - unable to task_for_pid()\n"); |
| exit (1); |
| } |
| |
| struct task_basic_info info; |
| unsigned int info_count = TASK_BASIC_INFO_COUNT; |
| |
| kr = task_info (task, TASK_BASIC_INFO, (task_info_t) &info, &info_count); |
| if (kr != KERN_SUCCESS) |
| { |
| printf ("Error - unable to call task_info.\n"); |
| exit (1); |
| } |
| printf ("Task suspend count: %d.\n", info.suspend_count); |
| |
| struct timespec *rqtp = (struct timespec *) malloc (sizeof (struct timespec)); |
| rqtp->tv_sec = 0; |
| rqtp->tv_nsec = 150000000; |
| |
| int loop_cnt = 1; |
| do |
| { |
| int i; |
| if (do_loop) |
| printf ("Iteration %d:\n", loop_cnt++); |
| thread_array_t thread_list; |
| mach_msg_type_number_t thread_count; |
| |
| kr = task_threads (task, &thread_list, &thread_count); |
| if (kr != KERN_SUCCESS) |
| { |
| printf ("Error - unable to get thread list\n"); |
| exit (1); |
| } |
| printf ("pid %d has %d threads\n", pid, thread_count); |
| if (verbose) |
| printf ("\n"); |
| |
| for (i = 0; i < thread_count; i++) |
| { |
| thread_basic_info_t basic_info = get_thread_basic_info (thread_list[i]); |
| |
| thread_identifier_info_data_t identifier_info = get_thread_identifier_info (thread_list[i]); |
| |
| int wordsize; |
| uint64_t pc = get_current_pc (thread_list[i], &wordsize); |
| |
| printf ("thread #%d, system-wide-unique-tid %lld, suspend count is %d, ", i, |
| identifier_info.thread_id, |
| basic_info->suspend_count); |
| if (wordsize == 8) |
| printf ("pc 0x%016llx, ", pc); |
| else |
| printf ("pc 0x%08llx, ", pc); |
| printf ("run state is "); |
| switch (basic_info->run_state) { |
| case TH_STATE_RUNNING: puts ("running"); break; |
| case TH_STATE_STOPPED: puts ("stopped"); break; |
| case TH_STATE_WAITING: puts ("waiting"); break; |
| case TH_STATE_UNINTERRUPTIBLE: puts ("uninterruptible"); break; |
| case TH_STATE_HALTED: puts ("halted"); break; |
| default: puts (""); |
| } |
| if (verbose) |
| { |
| printf (" (examine-threads port namespace) mach port # 0x%4.4x\n", (int) thread_list[i]); |
| thread_t mach_port_inferior_namespace; |
| if (inferior_namespace_mach_port_num (task, thread_list[i], &mach_port_inferior_namespace)) |
| printf (" (inferior port namepsace) mach port # 0x%4.4x\n", (int) mach_port_inferior_namespace); |
| printf (" pthread handle id 0x%llx\n", (uint64_t) identifier_info.thread_handle); |
| |
| struct proc_threadinfo pth; |
| int proc_threadinfo_succeeded = get_proc_threadinfo (pid, identifier_info.thread_handle, &pth); |
| |
| if (proc_threadinfo_succeeded && pth.pth_name[0] != '\0') |
| printf (" thread name '%s' ", pth.pth_name); |
| |
| printf (" user %d.%06ds, system %d.%06ds", |
| basic_info->user_time.seconds, basic_info->user_time.microseconds, |
| basic_info->system_time.seconds, basic_info->system_time.microseconds); |
| if (basic_info->cpu_usage > 0) |
| { |
| float cpu_percentage = basic_info->cpu_usage / 10.0; |
| printf (", using %.1f%% cpu currently", cpu_percentage); |
| } |
| if (basic_info->sleep_time > 0) |
| printf (", this thread has slept for %d seconds", basic_info->sleep_time); |
| |
| printf ("\n "); |
| printf ("scheduling policy %d", basic_info->policy); |
| |
| if (basic_info->flags != 0) |
| { |
| printf (", flags %d", basic_info->flags); |
| if ((basic_info->flags | TH_FLAGS_SWAPPED) == TH_FLAGS_SWAPPED) |
| printf (" (thread is swapped out)"); |
| if ((basic_info->flags | TH_FLAGS_IDLE) == TH_FLAGS_IDLE) |
| printf (" (thread is idle)"); |
| } |
| if (proc_threadinfo_succeeded) |
| printf (", current pri %d, max pri %d", pth.pth_curpri, pth.pth_maxpriority); |
| |
| printf ("\n\n"); |
| } |
| |
| free ((void *) basic_info); |
| } |
| if (do_loop) |
| printf ("\n"); |
| vm_deallocate (mytask, (vm_address_t) thread_list, |
| thread_count * sizeof (thread_act_t)); |
| nanosleep (rqtp, NULL); |
| } while (do_loop); |
| |
| kern_return_t err = task_resume (task); |
| if (err != KERN_SUCCESS) |
| printf ("Error resuming task: %d.", err); |
| |
| vm_deallocate (mytask, (vm_address_t) task, sizeof (task_t)); |
| free ((void *) process_name); |
| |
| return 0; |
| } |