forked from OSchip/llvm-project
482 lines
15 KiB
C
482 lines
15 KiB
C
#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;
|
|
}
|