| 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 | |
| 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(format: "Only found %d processes running!\n" , process_count); |
| 30 | exit(status: 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(size: 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(s: pidpath, c: '/'); |
| 51 | if ((j == NULL && strcmp(s1: procname, s2: pidpath) == 0) || |
| 52 | (j != NULL && strcmp(s1: j + 1, s2: procname) == 0)) { |
| 53 | match_count++; |
| 54 | if (all_pids[i] > highest_pid) |
| 55 | highest_pid = all_pids[i]; |
| 56 | } |
| 57 | } |
| 58 | free(ptr: all_pids); |
| 59 | |
| 60 | if (match_count == 0) { |
| 61 | printf(format: "Did not find process '%s'.\n" , procname); |
| 62 | exit(status: 1); |
| 63 | } |
| 64 | if (match_count > 1) { |
| 65 | printf(format: "Warning: More than one process '%s'!\n" , procname); |
| 66 | printf(format: " 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 | |
| 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(format: "Could not find process with pid of %d\n" , (int)pid); |
| 81 | exit(status: 1); |
| 82 | } |
| 83 | if (strrchr(s: tmp_name, c: '/')) |
| 84 | return strdup(s: strrchr(s: tmp_name, c: '/') + 1); |
| 85 | else |
| 86 | return strdup(s: 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 | |
| 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(ptr: (void *)kinfo); |
| 103 | printf(format: "Could not get kinfo_proc for pid %d\n" , (int)pid); |
| 104 | exit(status: 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 | |
| 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(format: "Error - unable to get basic thread info for a thread\n" ); |
| 124 | exit(status: 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 | |
| 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(format: "Error - unable to get thread ident for a thread\n" ); |
| 142 | exit(status: 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 | |
| 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(format: "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 | |
| 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(format: "Error - unable to get registers for a thread\n" ); |
| 207 | exit(status: 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 | |
| 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 | |
| 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(format: "Usage: tdump [-l] [-v] [-r] pid/procname\n" ); |
| 276 | exit(status: 1); |
| 277 | } |
| 278 | |
| 279 | if (argc == 3 || argc == 4) { |
| 280 | int i = 1; |
| 281 | while (i < argc - 1) { |
| 282 | if (strcmp(s1: argv[i], s2: "-l" ) == 0) |
| 283 | do_loop = 1; |
| 284 | if (strcmp(s1: argv[i], s2: "-v" ) == 0) |
| 285 | verbose = 1; |
| 286 | if (strcmp(s1: argv[i], s2: "-r" ) == 0) |
| 287 | resume_when_done++; |
| 288 | i++; |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | char *c = argv[argc - 1]; |
| 293 | if (*c == '\0') { |
| 294 | printf(format: "Usage: tdump [-l] [-v] pid/procname\n" ); |
| 295 | exit(status: 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(nptr: argv[argc - 1], NULL, base: 10); |
| 311 | if (pid == 0 && errno == EINVAL) { |
| 312 | printf(format: "Usage: tdump [-l] [-v] pid/procname\n" ); |
| 313 | exit(status: 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(format: "pid %d (%s) is currently " , pid, process_name); |
| 328 | switch (kinfo->kp_proc.p_stat) { |
| 329 | case SIDL: |
| 330 | printf(format: "being created by fork" ); |
| 331 | break; |
| 332 | case SRUN: |
| 333 | printf(format: "runnable" ); |
| 334 | break; |
| 335 | case SSLEEP: |
| 336 | printf(format: "sleeping on an address" ); |
| 337 | break; |
| 338 | case SSTOP: |
| 339 | printf(format: "suspended" ); |
| 340 | break; |
| 341 | case SZOMB: |
| 342 | printf(format: "zombie state - awaiting collection by parent" ); |
| 343 | break; |
| 344 | default: |
| 345 | printf(format: "unknown" ); |
| 346 | } |
| 347 | if (kinfo->kp_proc.p_flag & P_TRACED) |
| 348 | printf(format: " and is being debugged." ); |
| 349 | free(ptr: (void *)kinfo); |
| 350 | |
| 351 | printf(format: "\n" ); |
| 352 | |
| 353 | int csops_flags = 0; |
| 354 | if (csops(pid, CS_OPS_STATUS, useraddr: &csops_flags, usersize: sizeof(csops_flags)) != -1 && |
| 355 | (csops_flags & CS_RESTRICT)) { |
| 356 | printf(format: "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(format: "Error - unable to task_for_pid()\n" ); |
| 363 | exit(status: 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(format: "Error - unable to call task_info.\n" ); |
| 372 | exit(status: 1); |
| 373 | } |
| 374 | printf(format: "Task suspend count: %d.\n" , info.suspend_count); |
| 375 | |
| 376 | struct timespec *rqtp = (struct timespec *)malloc(size: 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(format: "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(format: "Error - unable to get thread list\n" ); |
| 391 | exit(status: 1); |
| 392 | } |
| 393 | printf("pid %d has %d threads\n" , pid, thread_count); |
| 394 | if (verbose) |
| 395 | printf(format: "\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(format: "\n" ); |
| 491 | vm_deallocate(mytask, (vm_address_t)thread_list, |
| 492 | thread_count * sizeof(thread_act_t)); |
| 493 | nanosleep(requested_time: 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(ptr: (void *)process_name); |
| 505 | |
| 506 | return 0; |
| 507 | } |
| 508 | |