1 #include <ctype.h>
2 #include <dispatch/dispatch.h>
3 #include <errno.h>
4 #include <libproc.h>
5 #include <mach/mach.h>
6 #include <mach/task_info.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/sysctl.h>
11 #include <time.h>
12 
13 // from System.framework/Versions/B/PrivateHeaders/sys/codesign.h
14 #define CS_OPS_STATUS 0       /* return status */
15 #define CS_RESTRICT 0x0000800 /* tell dyld to treat restricted */
16 int csops(pid_t pid, unsigned int ops, void *useraddr, size_t usersize);
17 
18 /* Step through the process table, find a matching process name, return
19    the pid of that matched process.
20    If there are multiple processes with that name, issue a warning on stdout
21    and return the highest numbered process.
22    The proc_pidpath() call is used which gets the full process name including
23    directories to the executable and the full (longer than 16 character)
24    executable name. */
25 
get_pid_for_process_name(const char * procname)26 pid_t get_pid_for_process_name(const char *procname) {
27   int process_count = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0) / sizeof(pid_t);
28   if (process_count < 1) {
29     printf("Only found %d processes running!\n", process_count);
30     exit(1);
31   }
32 
33   // Allocate a few extra slots in case new processes are spawned
34   int all_pids_size = sizeof(pid_t) * (process_count + 3);
35   pid_t *all_pids = (pid_t *)malloc(all_pids_size);
36 
37   // re-set process_count in case the number of processes changed (got smaller;
38   // we won't do bigger)
39   process_count =
40       proc_listpids(PROC_ALL_PIDS, 0, all_pids, all_pids_size) / sizeof(pid_t);
41 
42   int i;
43   pid_t highest_pid = 0;
44   int match_count = 0;
45   for (i = 1; i < process_count; i++) {
46     char pidpath[PATH_MAX];
47     int pidpath_len = proc_pidpath(all_pids[i], pidpath, sizeof(pidpath));
48     if (pidpath_len == 0)
49       continue;
50     char *j = strrchr(pidpath, '/');
51     if ((j == NULL && strcmp(procname, pidpath) == 0) ||
52         (j != NULL && strcmp(j + 1, procname) == 0)) {
53       match_count++;
54       if (all_pids[i] > highest_pid)
55         highest_pid = all_pids[i];
56     }
57   }
58   free(all_pids);
59 
60   if (match_count == 0) {
61     printf("Did not find process '%s'.\n", procname);
62     exit(1);
63   }
64   if (match_count > 1) {
65     printf("Warning:  More than one process '%s'!\n", procname);
66     printf("          defaulting to the highest-pid one, %d\n", highest_pid);
67   }
68   return highest_pid;
69 }
70 
71 /* Given a pid, get the full executable name (including directory
72    paths and the longer-than-16-chars executable name) and return
73    the basename of that (i.e. do not include the directory components).
74    This function mallocs the memory for the string it returns;
75    the caller must free this memory. */
76 
get_process_name_for_pid(pid_t pid)77 const char *get_process_name_for_pid(pid_t pid) {
78   char tmp_name[PATH_MAX];
79   if (proc_pidpath(pid, tmp_name, sizeof(tmp_name)) == 0) {
80     printf("Could not find process with pid of %d\n", (int)pid);
81     exit(1);
82   }
83   if (strrchr(tmp_name, '/'))
84     return strdup(strrchr(tmp_name, '/') + 1);
85   else
86     return strdup(tmp_name);
87 }
88 
89 /* Get a struct kinfo_proc structure for a given pid.
90    Process name is required for error printing.
91    Gives you the current state of the process and whether it is being debugged
92    by anyone.
93    memory is malloc()'ed for the returned struct kinfo_proc
94    and must be freed by the caller.  */
95 
get_kinfo_proc_for_pid(pid_t pid,const char * process_name)96 struct kinfo_proc *get_kinfo_proc_for_pid(pid_t pid, const char *process_name) {
97   struct kinfo_proc *kinfo =
98       (struct kinfo_proc *)malloc(sizeof(struct kinfo_proc));
99   int mib[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
100   size_t len = sizeof(struct kinfo_proc);
101   if (sysctl(mib, sizeof(mib) / sizeof(mib[0]), kinfo, &len, NULL, 0) != 0) {
102     free((void *)kinfo);
103     printf("Could not get kinfo_proc for pid %d\n", (int)pid);
104     exit(1);
105   }
106   return kinfo;
107 }
108 
109 /* Get the basic information (thread_basic_info_t) about a given
110    thread.
111    Gives you the suspend count; thread state; user time; system time; sleep
112    time; etc.
113    The return value is a pointer to malloc'ed memory - it is the caller's
114    responsibility to free it.  */
115 
get_thread_basic_info(thread_t thread)116 thread_basic_info_t get_thread_basic_info(thread_t thread) {
117   kern_return_t kr;
118   integer_t *thinfo = (integer_t *)malloc(sizeof(integer_t) * THREAD_INFO_MAX);
119   mach_msg_type_number_t thread_info_count = THREAD_INFO_MAX;
120   kr = thread_info(thread, THREAD_BASIC_INFO, (thread_info_t)thinfo,
121                    &thread_info_count);
122   if (kr != KERN_SUCCESS) {
123     printf("Error - unable to get basic thread info for a thread\n");
124     exit(1);
125   }
126   return (thread_basic_info_t)thinfo;
127 }
128 
129 /* Get the thread identifier info (thread_identifier_info_data_t)
130    about a given thread.
131    Gives you the system-wide unique thread number; the pthread identifier number
132 */
133 
get_thread_identifier_info(thread_t thread)134 thread_identifier_info_data_t get_thread_identifier_info(thread_t thread) {
135   kern_return_t kr;
136   thread_identifier_info_data_t tident;
137   mach_msg_type_number_t tident_count = THREAD_IDENTIFIER_INFO_COUNT;
138   kr = thread_info(thread, THREAD_IDENTIFIER_INFO, (thread_info_t)&tident,
139                    &tident_count);
140   if (kr != KERN_SUCCESS) {
141     printf("Error - unable to get thread ident for a thread\n");
142     exit(1);
143   }
144   return tident;
145 }
146 
147 /* Given a mach port # (in the examine-threads mach port namespace) for a
148    thread,
149    find the mach port # in the inferior program's port namespace.
150    Sets inferior_port if successful.
151    Returns true if successful, false if unable to find the port number.  */
152 
inferior_namespace_mach_port_num(task_t task,thread_t examine_threads_port,thread_t * inferior_port)153 bool inferior_namespace_mach_port_num(task_t task,
154                                       thread_t examine_threads_port,
155                                       thread_t *inferior_port) {
156   kern_return_t retval;
157   mach_port_name_array_t names;
158   mach_msg_type_number_t nameslen;
159   mach_port_type_array_t types;
160   mach_msg_type_number_t typeslen;
161 
162   if (inferior_port == NULL)
163     return false;
164 
165   retval = mach_port_names(task, &names, &nameslen, &types, &typeslen);
166   if (retval != KERN_SUCCESS) {
167     printf("Error - unable to get mach port names for inferior.\n");
168     return false;
169   }
170   int i = 0;
171   for (i = 0; i < nameslen; i++) {
172     mach_port_t local_name;
173     mach_msg_type_name_t local_type;
174     retval = mach_port_extract_right(task, names[i], MACH_MSG_TYPE_COPY_SEND,
175                                      &local_name, &local_type);
176     if (retval == KERN_SUCCESS) {
177       mach_port_deallocate(mach_task_self(), local_name);
178       if (local_name == examine_threads_port) {
179         *inferior_port = names[i];
180         vm_deallocate(mach_task_self(), (vm_address_t)names,
181                       nameslen * sizeof(mach_port_t));
182         vm_deallocate(mach_task_self(), (vm_address_t)types,
183                       typeslen * sizeof(mach_port_t));
184         return true;
185       }
186     }
187   }
188   vm_deallocate(mach_task_self(), (vm_address_t)names,
189                 nameslen * sizeof(mach_port_t));
190   vm_deallocate(mach_task_self(), (vm_address_t)types,
191                 typeslen * sizeof(mach_port_t));
192   return false;
193 }
194 
195 /* Get the current pc value for a given thread.  */
196 
get_current_pc(thread_t thread,int * wordsize)197 uint64_t get_current_pc(thread_t thread, int *wordsize) {
198   kern_return_t kr;
199 
200 #if defined(__x86_64__) || defined(__i386__)
201   x86_thread_state_t gp_regs;
202   mach_msg_type_number_t gp_count = x86_THREAD_STATE_COUNT;
203   kr = thread_get_state(thread, x86_THREAD_STATE, (thread_state_t)&gp_regs,
204                         &gp_count);
205   if (kr != KERN_SUCCESS) {
206     printf("Error - unable to get registers for a thread\n");
207     exit(1);
208   }
209 
210   if (gp_regs.tsh.flavor == x86_THREAD_STATE64) {
211     *wordsize = 8;
212     return gp_regs.uts.ts64.__rip;
213   } else {
214     *wordsize = 4;
215     return gp_regs.uts.ts32.__eip;
216   }
217 #endif
218 
219 #if defined(__arm__)
220   arm_thread_state_t gp_regs;
221   mach_msg_type_number_t gp_count = ARM_THREAD_STATE_COUNT;
222   kr = thread_get_state(thread, ARM_THREAD_STATE, (thread_state_t)&gp_regs,
223                         &gp_count);
224   if (kr != KERN_SUCCESS) {
225     printf("Error - unable to get registers for a thread\n");
226     exit(1);
227   }
228   *wordsize = 4;
229   return gp_regs.__pc;
230 #endif
231 
232 #if defined(__arm64__)
233   arm_thread_state64_t gp_regs;
234   mach_msg_type_number_t gp_count = ARM_THREAD_STATE64_COUNT;
235   kr = thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&gp_regs,
236                         &gp_count);
237   if (kr != KERN_SUCCESS) {
238     printf("Error - unable to get registers for a thread\n");
239     exit(1);
240   }
241   *wordsize = 8;
242   return gp_regs.__pc;
243 #endif
244 }
245 
246 /* Get the proc_threadinfo for a given thread.
247    Gives you the thread name, if set; current and max priorities.
248    Returns 1 if successful
249    Returns 0 if proc_pidinfo() failed
250 */
251 
get_proc_threadinfo(pid_t pid,uint64_t thread_handle,struct proc_threadinfo * pth)252 int get_proc_threadinfo(pid_t pid, uint64_t thread_handle,
253                         struct proc_threadinfo *pth) {
254   pth->pth_name[0] = '\0';
255   int ret = proc_pidinfo(pid, PROC_PIDTHREADINFO, thread_handle, pth,
256                          sizeof(struct proc_threadinfo));
257   if (ret != 0)
258     return 1;
259   else
260     return 0;
261 }
262 
main(int argc,char ** argv)263 int main(int argc, char **argv) {
264   kern_return_t kr;
265   task_t task;
266   pid_t pid = 0;
267   char *procname = NULL;
268   int arg_is_procname = 0;
269   int do_loop = 0;
270   int verbose = 0;
271   int resume_when_done = 0;
272   mach_port_t mytask = mach_task_self();
273 
274   if (argc != 2 && argc != 3 && argc != 4 && argc != 5) {
275     printf("Usage: tdump [-l] [-v] [-r] pid/procname\n");
276     exit(1);
277   }
278 
279   if (argc == 3 || argc == 4) {
280     int i = 1;
281     while (i < argc - 1) {
282       if (strcmp(argv[i], "-l") == 0)
283         do_loop = 1;
284       if (strcmp(argv[i], "-v") == 0)
285         verbose = 1;
286       if (strcmp(argv[i], "-r") == 0)
287         resume_when_done++;
288       i++;
289     }
290   }
291 
292   char *c = argv[argc - 1];
293   if (*c == '\0') {
294     printf("Usage: tdump [-l] [-v] pid/procname\n");
295     exit(1);
296   }
297   while (*c != '\0') {
298     if (!isdigit(*c)) {
299       arg_is_procname = 1;
300       procname = argv[argc - 1];
301       break;
302     }
303     c++;
304   }
305 
306   if (arg_is_procname && procname) {
307     pid = get_pid_for_process_name(procname);
308   } else {
309     errno = 0;
310     pid = (pid_t)strtol(argv[argc - 1], NULL, 10);
311     if (pid == 0 && errno == EINVAL) {
312       printf("Usage: tdump [-l] [-v] pid/procname\n");
313       exit(1);
314     }
315   }
316 
317   const char *process_name = get_process_name_for_pid(pid);
318 
319   // At this point "pid" is the process id and "process_name" is the process
320   // name
321   // Now we have to get the process list from the kernel (which only has the
322   // truncated
323   // 16 char names)
324 
325   struct kinfo_proc *kinfo = get_kinfo_proc_for_pid(pid, process_name);
326 
327   printf("pid %d (%s) is currently ", pid, process_name);
328   switch (kinfo->kp_proc.p_stat) {
329   case SIDL:
330     printf("being created by fork");
331     break;
332   case SRUN:
333     printf("runnable");
334     break;
335   case SSLEEP:
336     printf("sleeping on an address");
337     break;
338   case SSTOP:
339     printf("suspended");
340     break;
341   case SZOMB:
342     printf("zombie state - awaiting collection by parent");
343     break;
344   default:
345     printf("unknown");
346   }
347   if (kinfo->kp_proc.p_flag & P_TRACED)
348     printf(" and is being debugged.");
349   free((void *)kinfo);
350 
351   printf("\n");
352 
353   int csops_flags = 0;
354   if (csops(pid, CS_OPS_STATUS, &csops_flags, sizeof(csops_flags)) != -1 &&
355       (csops_flags & CS_RESTRICT)) {
356     printf("pid %d (%s) is restricted so nothing can attach to it.\n", pid,
357            process_name);
358   }
359 
360   kr = task_for_pid(mach_task_self(), pid, &task);
361   if (kr != KERN_SUCCESS) {
362     printf("Error - unable to task_for_pid()\n");
363     exit(1);
364   }
365 
366   struct task_basic_info info;
367   unsigned int info_count = TASK_BASIC_INFO_COUNT;
368 
369   kr = task_info(task, TASK_BASIC_INFO, (task_info_t)&info, &info_count);
370   if (kr != KERN_SUCCESS) {
371     printf("Error - unable to call task_info.\n");
372     exit(1);
373   }
374   printf("Task suspend count: %d.\n", info.suspend_count);
375 
376   struct timespec *rqtp = (struct timespec *)malloc(sizeof(struct timespec));
377   rqtp->tv_sec = 0;
378   rqtp->tv_nsec = 150000000;
379 
380   int loop_cnt = 1;
381   do {
382     int i;
383     if (do_loop)
384       printf("Iteration %d:\n", loop_cnt++);
385     thread_array_t thread_list;
386     mach_msg_type_number_t thread_count;
387 
388     kr = task_threads(task, &thread_list, &thread_count);
389     if (kr != KERN_SUCCESS) {
390       printf("Error - unable to get thread list\n");
391       exit(1);
392     }
393     printf("pid %d has %d threads\n", pid, thread_count);
394     if (verbose)
395       printf("\n");
396 
397     for (i = 0; i < thread_count; i++) {
398       thread_basic_info_t basic_info = get_thread_basic_info(thread_list[i]);
399 
400       thread_identifier_info_data_t identifier_info =
401           get_thread_identifier_info(thread_list[i]);
402 
403       int wordsize;
404       uint64_t pc = get_current_pc(thread_list[i], &wordsize);
405 
406       printf("thread #%d, system-wide-unique-tid 0x%llx, suspend count is %d, ",
407              i, identifier_info.thread_id, basic_info->suspend_count);
408       if (wordsize == 8)
409         printf("pc 0x%016llx, ", pc);
410       else
411         printf("pc 0x%08llx, ", pc);
412       printf("run state is ");
413       switch (basic_info->run_state) {
414       case TH_STATE_RUNNING:
415         puts("running");
416         break;
417       case TH_STATE_STOPPED:
418         puts("stopped");
419         break;
420       case TH_STATE_WAITING:
421         puts("waiting");
422         break;
423       case TH_STATE_UNINTERRUPTIBLE:
424         puts("uninterruptible");
425         break;
426       case TH_STATE_HALTED:
427         puts("halted");
428         break;
429       default:
430         puts("");
431       }
432 
433       printf("           pthread handle id 0x%llx (not the same value as "
434              "pthread_self() returns)\n",
435              (uint64_t)identifier_info.thread_handle);
436 
437       struct proc_threadinfo pth;
438       int proc_threadinfo_succeeded =
439           get_proc_threadinfo(pid, identifier_info.thread_handle, &pth);
440 
441       if (proc_threadinfo_succeeded && pth.pth_name[0] != '\0')
442         printf("           thread name '%s'\n", pth.pth_name);
443 
444       printf("           libdispatch qaddr 0x%llx (not the same as the "
445              "dispatch_queue_t token)\n",
446              (uint64_t)identifier_info.dispatch_qaddr);
447 
448       if (verbose) {
449         printf(
450             "           (examine-threads port namespace) mach port # 0x%4.4x\n",
451             (int)thread_list[i]);
452         thread_t mach_port_inferior_namespace;
453         if (inferior_namespace_mach_port_num(task, thread_list[i],
454                                              &mach_port_inferior_namespace))
455           printf("           (inferior port namepsace) mach port # 0x%4.4x\n",
456                  (int)mach_port_inferior_namespace);
457         printf("           user %d.%06ds, system %d.%06ds",
458                basic_info->user_time.seconds,
459                basic_info->user_time.microseconds,
460                basic_info->system_time.seconds,
461                basic_info->system_time.microseconds);
462         if (basic_info->cpu_usage > 0) {
463           float cpu_percentage = basic_info->cpu_usage / 10.0;
464           printf(", using %.1f%% cpu currently", cpu_percentage);
465         }
466         if (basic_info->sleep_time > 0)
467           printf(", this thread has slept for %d seconds",
468                  basic_info->sleep_time);
469 
470         printf("\n           ");
471         printf("scheduling policy %d", basic_info->policy);
472 
473         if (basic_info->flags != 0) {
474           printf(", flags %d", basic_info->flags);
475           if ((basic_info->flags | TH_FLAGS_SWAPPED) == TH_FLAGS_SWAPPED)
476             printf(" (thread is swapped out)");
477           if ((basic_info->flags | TH_FLAGS_IDLE) == TH_FLAGS_IDLE)
478             printf(" (thread is idle)");
479         }
480         if (proc_threadinfo_succeeded)
481           printf(", current pri %d, max pri %d", pth.pth_curpri,
482                  pth.pth_maxpriority);
483 
484         printf("\n\n");
485       }
486 
487       free((void *)basic_info);
488     }
489     if (do_loop)
490       printf("\n");
491     vm_deallocate(mytask, (vm_address_t)thread_list,
492                   thread_count * sizeof(thread_act_t));
493     nanosleep(rqtp, NULL);
494   } while (do_loop);
495 
496   while (resume_when_done > 0) {
497     kern_return_t err = task_resume(task);
498     if (err != KERN_SUCCESS)
499       printf("Error resuming task: %d.", err);
500     resume_when_done--;
501   }
502 
503   vm_deallocate(mytask, (vm_address_t)task, sizeof(task_t));
504   free((void *)process_name);
505 
506   return 0;
507 }
508