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