forked from OSchip/llvm-project
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;
|
|
}
|