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 | |