508 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			508 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
| #include <ctype.h>
 | |
| #include <dispatch/dispatch.h>
 | |
| #include <errno.h>
 | |
| #include <libproc.h>
 | |
| #include <mach/mach.h>
 | |
| #include <mach/task_info.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <sys/sysctl.h>
 | |
| #include <time.h>
 | |
| 
 | |
| // from System.framework/Versions/B/PrivateHeaders/sys/codesign.h
 | |
| #define CS_OPS_STATUS 0       /* return status */
 | |
| #define CS_RESTRICT 0x0000800 /* tell dyld to treat restricted */
 | |
| int csops(pid_t pid, unsigned int ops, void *useraddr, size_t usersize);
 | |
| 
 | |
| /* 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);
 | |
|   }
 | |
|   *wordsize = 4;
 | |
|   return gp_regs.__pc;
 | |
| #endif
 | |
| 
 | |
| #if defined(__arm64__)
 | |
|   arm_thread_state64_t gp_regs;
 | |
|   mach_msg_type_number_t gp_count = ARM_THREAD_STATE64_COUNT;
 | |
|   kr = thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&gp_regs,
 | |
|                         &gp_count);
 | |
|   if (kr != KERN_SUCCESS) {
 | |
|     printf("Error - unable to get registers for a thread\n");
 | |
|     exit(1);
 | |
|   }
 | |
|   *wordsize = 8;
 | |
|   return gp_regs.__pc;
 | |
| #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;
 | |
|   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++;
 | |
|       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");
 | |
| 
 | |
|   int csops_flags = 0;
 | |
|   if (csops(pid, CS_OPS_STATUS, &csops_flags, sizeof(csops_flags)) != -1 &&
 | |
|       (csops_flags & CS_RESTRICT)) {
 | |
|     printf("pid %d (%s) is restricted so nothing can attach to it.\n", pid,
 | |
|            process_name);
 | |
|   }
 | |
| 
 | |
|   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 0x%llx, 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("");
 | |
|       }
 | |
| 
 | |
|       printf("           pthread handle id 0x%llx (not the same value as "
 | |
|              "pthread_self() returns)\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'\n", pth.pth_name);
 | |
| 
 | |
|       printf("           libdispatch qaddr 0x%llx (not the same as the "
 | |
|              "dispatch_queue_t token)\n",
 | |
|              (uint64_t)identifier_info.dispatch_qaddr);
 | |
| 
 | |
|       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("           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);
 | |
| 
 | |
|   while (resume_when_done > 0) {
 | |
|     kern_return_t err = task_resume(task);
 | |
|     if (err != KERN_SUCCESS)
 | |
|       printf("Error resuming task: %d.", err);
 | |
|     resume_when_done--;
 | |
|   }
 | |
| 
 | |
|   vm_deallocate(mytask, (vm_address_t)task, sizeof(task_t));
 | |
|   free((void *)process_name);
 | |
| 
 | |
|   return 0;
 | |
| }
 |