]>
Commit | Line | Data |
---|---|---|
cf37c299 A |
1 | /* |
2 | * Copyright (c) 2016 Apple Inc. All rights reserved. | |
3 | */ | |
4 | ||
5 | #include "options.h" | |
6 | #include "utils.h" | |
7 | #include "corefile.h" | |
8 | #include "vanilla.h" | |
9 | #include "sparse.h" | |
10 | ||
11 | #include <sys/types.h> | |
12 | #include <sys/sysctl.h> | |
13 | #include <sys/stat.h> | |
14 | #include <sys/mman.h> | |
15 | #include <libproc.h> | |
16 | ||
17 | #include <sys/kauth.h> | |
18 | ||
19 | #include <stdio.h> | |
20 | #include <string.h> | |
21 | #include <strings.h> | |
22 | #include <stddef.h> | |
23 | #include <stdlib.h> | |
24 | #include <stdarg.h> | |
25 | #include <signal.h> | |
26 | #include <unistd.h> | |
27 | #include <errno.h> | |
28 | #include <ctype.h> | |
29 | #include <fcntl.h> | |
30 | #include <assert.h> | |
31 | #include <libutil.h> | |
32 | ||
33 | #include <mach/mach.h> | |
34 | ||
35 | static char * | |
36 | kern_corefile(void) | |
37 | { | |
38 | char *(^sysc_string)(const char *name) = ^(const char *name) { | |
39 | char *p = NULL; | |
40 | size_t len = 0; | |
41 | ||
42 | if (-1 == sysctlbyname(name, NULL, &len, NULL, 0)) { | |
43 | warnc(errno, "sysctl: %s", name); | |
44 | } else if (0 != len) { | |
45 | p = malloc(len); | |
46 | if (-1 == sysctlbyname(name, p, &len, NULL, 0)) { | |
47 | warnc(errno, "sysctl: %s", name); | |
48 | free(p); | |
49 | p = NULL; | |
50 | } | |
51 | } | |
52 | return p; | |
53 | }; | |
54 | ||
55 | char *s = sysc_string("kern.corefile"); | |
56 | if (NULL == s) | |
57 | s = strdup("/cores/core.%P"); | |
58 | return s; | |
59 | } | |
60 | ||
61 | static const struct proc_bsdinfo * | |
62 | get_bsdinfo(pid_t pid) | |
63 | { | |
64 | if (0 == pid) | |
65 | return NULL; | |
66 | struct proc_bsdinfo *pbi = calloc(1, sizeof (*pbi)); | |
67 | if (0 != proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, pbi, sizeof (*pbi))) | |
68 | return pbi; | |
69 | free(pbi); | |
70 | return NULL; | |
71 | } | |
72 | ||
73 | static char * | |
74 | format_gcore_name(const char *fmt, const struct proc_bsdinfo *pbi) | |
75 | { | |
76 | __block size_t resid = MAXPATHLEN; | |
77 | __block char *p = calloc(1, resid); | |
78 | char *out = p; | |
79 | ||
80 | int (^outchar)(int c) = ^(int c) { | |
81 | if (resid > 1) { | |
82 | *p++ = (char)c; | |
83 | resid--; | |
84 | return 1; | |
85 | } else | |
86 | return 0; | |
87 | }; | |
88 | ||
89 | ptrdiff_t (^outstr)(const char *str) = ^(const char *str) { | |
90 | const char *s = str; | |
91 | while (*s && 0 != outchar(*s++)) | |
92 | ; | |
93 | return s - str; | |
94 | }; | |
95 | ||
96 | ptrdiff_t (^outint)(int value)= ^(int value) { | |
97 | char id[11]; | |
98 | snprintf(id, sizeof (id), "%u", value); | |
99 | return outstr(id); | |
100 | }; | |
101 | ||
102 | ptrdiff_t (^outtstamp)(void) = ^(void) { | |
103 | time_t now; | |
104 | time(&now); | |
105 | struct tm tm; | |
106 | gmtime_r(&now, &tm); | |
107 | char tstamp[50]; | |
108 | strftime(tstamp, sizeof (tstamp), "%Y%m%dT%H%M%SZ", &tm); | |
109 | return outstr(tstamp); | |
110 | }; | |
111 | ||
112 | int c; | |
113 | ||
114 | for (int i = 0; resid > 0; i++) | |
115 | switch (c = fmt[i]) { | |
116 | default: | |
117 | outchar(c); | |
118 | break; | |
119 | case '%': | |
120 | i++; | |
121 | switch (c = fmt[i]) { | |
122 | case '%': | |
123 | outchar(c); | |
124 | break; | |
125 | case 'P': | |
126 | outint(pbi->pbi_pid); | |
127 | break; | |
128 | case 'U': | |
129 | outint(pbi->pbi_uid); | |
130 | break; | |
131 | case 'N': | |
132 | outstr(pbi->pbi_name[0] ? | |
133 | pbi->pbi_name : pbi->pbi_comm); | |
134 | break; | |
135 | case 'T': | |
136 | outtstamp(); // ISO 8601 format | |
137 | break; | |
138 | default: | |
139 | if (isprint(c)) | |
140 | err(EX_DATAERR, "unknown format char: %%%c", c); | |
141 | else if (c != 0) | |
142 | err(EX_DATAERR, "bad format char %%\\%03o", c); | |
143 | else | |
144 | err(EX_DATAERR, "bad format specifier"); | |
145 | } | |
146 | break; | |
147 | case 0: | |
148 | outchar(c); | |
149 | goto done; | |
150 | } | |
151 | done: | |
152 | return out; | |
153 | } | |
154 | ||
155 | const char *pgm; | |
156 | const struct options *opt; | |
157 | ||
158 | int | |
159 | main(int argc, char *const *argv) | |
160 | { | |
161 | if (NULL == (pgm = strrchr(*argv, '/'))) | |
162 | pgm = *argv; | |
163 | else | |
164 | pgm++; | |
165 | ||
166 | #define ZOPT_ALG (0) | |
167 | #define ZOPT_CHSIZE (ZOPT_ALG + 1) | |
168 | ||
169 | static char *const zoptkeys[] = { | |
170 | [ZOPT_ALG] = "algorithm", | |
171 | [ZOPT_CHSIZE] = "chunksize", | |
172 | NULL | |
173 | }; | |
174 | ||
175 | err_set_exit_b(^(int eval) { | |
176 | if (EX_USAGE == eval) { | |
177 | fprintf(stderr, | |
178 | "usage:\n\t%s [-s] [-v] [[-o file] | [-c pathfmt ]] [-b size] " | |
179 | #if DEBUG | |
180 | "[-d] [-n] [-i] [-p] [-S] [-z] [-C] " | |
181 | "[-Z compression-options] " | |
182 | #ifdef CONFIG_REFSC | |
183 | "[-R] " | |
184 | #endif | |
185 | #endif | |
186 | "pid\n", pgm); | |
187 | #if DEBUG | |
188 | fprintf(stderr, "where compression-options:\n"); | |
189 | const char zvalfmt[] = "\t%s=%s\t\t%s\n"; | |
190 | fprintf(stderr, zvalfmt, zoptkeys[ZOPT_ALG], "alg", | |
191 | "set compression algorithm"); | |
192 | fprintf(stderr, zvalfmt, zoptkeys[ZOPT_CHSIZE], "size", | |
193 | "set compression chunksize, Mib"); | |
194 | #endif | |
195 | } | |
196 | }); | |
197 | ||
198 | char *corefmt = NULL; | |
199 | char *corefname = NULL; | |
200 | const size_t oneM = 1024 * 1024; | |
201 | ||
202 | #define LARGEST_CHUNKSIZE INT32_MAX | |
203 | #define DEFAULT_COMPRESSION_CHUNKSIZE (16 * oneM) | |
204 | ||
205 | struct options options = { | |
206 | .corpse = 0, | |
207 | .suspend = 0, | |
208 | .preserve = 0, | |
209 | .verbose = 0, | |
210 | .debug = 0, | |
211 | .dryrun = 0, | |
212 | .sparse = 0, | |
213 | .sizebound = 0, | |
214 | .coreinfo = 0, | |
215 | #ifdef OPTIONS_REFSC | |
216 | .scfileref = 0, | |
217 | #endif | |
218 | .compress = 0, | |
219 | .chunksize = LARGEST_CHUNKSIZE, | |
220 | .calgorithm = COMPRESSION_LZFSE, | |
221 | }; | |
222 | ||
223 | int c; | |
224 | char *sopts, *value; | |
225 | ||
226 | while ((c = getopt(argc, argv, "inmvdszpCSRZ:o:c:b:")) != -1) { | |
227 | switch (c) { | |
228 | ||
229 | /* | |
230 | * documented options | |
231 | */ | |
232 | case 's': /* FreeBSD compat: stop while gathering */ | |
233 | options.suspend = 1; | |
234 | break; | |
235 | case 'o': /* Linux (& SunOS) compat: basic name */ | |
236 | corefname = strdup(optarg); | |
237 | break; | |
238 | case 'c': /* FreeBSD compat: basic name */ | |
239 | /* (also allows pattern-based naming) */ | |
240 | corefmt = strdup(optarg); | |
241 | break; | |
242 | ||
243 | case 'b': /* bound the size of the core file */ | |
244 | if (NULL != optarg) { | |
245 | off_t bsize = atoi(optarg) * oneM; | |
246 | if (bsize > 0) | |
247 | options.sizebound = bsize; | |
248 | else | |
249 | errx(EX_USAGE, "invalid bound"); | |
250 | } else | |
251 | errx(EX_USAGE, "no bound specified"); | |
252 | break; | |
253 | case 'v': /* verbose output */ | |
254 | options.verbose++; | |
255 | break; | |
256 | ||
257 | /* | |
258 | * dev and debugging help | |
259 | */ | |
260 | case 'n': /* write the core file to /dev/null */ | |
261 | options.dryrun++; | |
262 | break; | |
263 | case 'd': /* debugging */ | |
264 | options.debug++; | |
265 | options.verbose++; | |
266 | options.preserve++; | |
267 | break; | |
268 | case 'p': /* preserve partial core file (even if errors) */ | |
269 | options.preserve++; | |
270 | break; | |
271 | ||
272 | /* | |
273 | * Remaining options are experimental and/or | |
274 | * affect the content of the core file | |
275 | */ | |
276 | case 'i': /* include LC_COREINFO data */ | |
277 | options.coreinfo++; | |
278 | break; | |
279 | case 'C': /* corpsify rather than suspend */ | |
280 | options.corpse++; | |
281 | break; | |
282 | #ifdef CONFIG_REFSC | |
283 | case 'R': /* include the shared cache by reference */ | |
284 | options.scfileref++; | |
285 | options.coreinfo++; | |
286 | break; | |
287 | #endif | |
288 | case 'S': /* use dyld info to control the content */ | |
289 | options.sparse++; | |
290 | options.coreinfo++; | |
291 | break; | |
292 | case 'z': /* create compressed LC_SEGMENT* segments */ | |
293 | if (0 == options.compress) { | |
294 | options.compress++; | |
295 | options.chunksize = DEFAULT_COMPRESSION_CHUNKSIZE; | |
296 | } | |
297 | options.coreinfo++; | |
298 | break; | |
299 | case 'Z': /* control compression options */ | |
300 | /* | |
301 | * Only LZFSE and LZ4 seem practical. | |
302 | * (Default to LZ4 compression when the | |
303 | * process is suspended, LZFSE when corpsed?) | |
304 | */ | |
305 | if (0 == options.compress) { | |
306 | options.compress++; | |
307 | options.chunksize = DEFAULT_COMPRESSION_CHUNKSIZE; | |
308 | } | |
309 | sopts = optarg; | |
310 | while (*sopts) { | |
311 | size_t chsize; | |
312 | ||
313 | switch (getsubopt(&sopts, zoptkeys, &value)) { | |
314 | case ZOPT_ALG: /* change the algorithm */ | |
315 | if (NULL == value) | |
316 | errx(EX_USAGE, "missing algorithm for " | |
317 | "%s suboption", | |
318 | zoptkeys[ZOPT_ALG]); | |
319 | if (strcmp(value, "lz4") == 0) | |
320 | options.calgorithm = | |
321 | COMPRESSION_LZ4; | |
322 | else if (strcmp(value, "zlib") == 0) | |
323 | options.calgorithm = | |
324 | COMPRESSION_ZLIB; | |
325 | else if (strcmp(value, "lzma") == 0) | |
326 | options.calgorithm = | |
327 | COMPRESSION_LZMA; | |
328 | else if (strcmp(value, "lzfse") == 0) | |
329 | options.calgorithm = | |
330 | COMPRESSION_LZFSE; | |
331 | else | |
332 | errx(EX_USAGE, "unknown algorithm '%s'" | |
333 | " for %s suboption", | |
334 | value, | |
335 | zoptkeys[ZOPT_ALG]); | |
336 | break; | |
337 | case ZOPT_CHSIZE: /* set the chunksize */ | |
338 | if (NULL == value) | |
339 | errx(EX_USAGE, "no value specified for " | |
340 | "%s suboption", | |
341 | zoptkeys[ZOPT_CHSIZE]); | |
342 | if ((chsize = atoi(value)) < 1) | |
343 | errx(EX_USAGE, "chunksize %lu too small", chsize); | |
344 | if (chsize > (LARGEST_CHUNKSIZE / oneM)) | |
345 | errx(EX_USAGE, "chunksize %lu too large", chsize); | |
346 | options.chunksize = chsize * oneM; | |
347 | break; | |
348 | default: | |
349 | if (suboptarg) | |
350 | errx(EX_USAGE, "illegal suboption '%s'", | |
351 | suboptarg); | |
352 | else | |
353 | errx(EX_USAGE, "missing suboption"); | |
354 | } | |
355 | } | |
356 | break; | |
357 | default: | |
358 | errx(EX_USAGE, "unknown flag"); | |
359 | } | |
360 | } | |
361 | if (optind == argc) | |
362 | errx(EX_USAGE, "no pid specified"); | |
363 | ||
364 | opt = &options; | |
365 | ||
366 | if ((opt->dryrun ? 1 : 0) + | |
367 | (NULL != corefname ? 1 : 0) + | |
368 | (NULL != corefmt ? 1 : 0) > 1) | |
369 | errx(EX_USAGE, "specify only one of -n, -o and -c"); | |
370 | ||
371 | setpageshift(); | |
372 | ||
373 | const pid_t pid = atoi(argv[optind]); | |
374 | if (pid < 1 || getpid() == pid) | |
375 | errx(EX_DATAERR, "invalid pid: %d", pid); | |
376 | if (-1 == kill(pid, 0)) { | |
377 | switch (errno) { | |
378 | case ESRCH: | |
379 | errc(EX_DATAERR, errno, "no process with pid %d", pid); | |
380 | default: | |
381 | errc(EX_DATAERR, errno, "pid %d", pid); | |
382 | } | |
383 | } | |
384 | ||
385 | const struct proc_bsdinfo *pbi = get_bsdinfo(pid); | |
386 | if (NULL == pbi) | |
387 | errx(EX_OSERR, "cannot get bsdinfo about %d", pid); | |
388 | ||
389 | /* | |
390 | * make our data model match the data model of the target | |
391 | */ | |
392 | if (-1 == reexec_to_match_lp64ness(pbi->pbi_flags & PROC_FLAG_LP64)) | |
393 | errc(1, errno, "cannot match data model of %d", pid); | |
394 | ||
395 | #if defined(__LP64__) | |
396 | if ((pbi->pbi_flags & PROC_FLAG_LP64) == 0) | |
397 | #else | |
398 | if ((pbi->pbi_flags & PROC_FLAG_LP64) != 0) | |
399 | #endif | |
400 | errx(EX_OSERR, "cannot match data model of %d", pid); | |
401 | ||
402 | /* | |
403 | * These are experimental options for the moment. | |
404 | * These will likely change. | |
405 | * Some may become defaults, some may be removed altogether. | |
406 | */ | |
407 | if (opt->sparse || | |
408 | #ifdef CONFIG_REFSC | |
409 | opt->scfileref || | |
410 | #endif | |
411 | opt->compress || | |
412 | opt->corpse || | |
413 | opt->coreinfo) | |
414 | warnx("experimental option(s) used, " | |
415 | "resulting corefile may be unusable."); | |
416 | ||
417 | if (pbi->pbi_ruid != pbi->pbi_svuid || | |
418 | pbi->pbi_rgid != pbi->pbi_svgid) | |
419 | errx(EX_NOPERM, "pid %d - not dumping a set-id process", pid); | |
420 | ||
421 | if (NULL == corefname) { | |
422 | if (NULL == corefmt) { | |
423 | const char defcore[] = "%N-%P-%T"; | |
424 | if (NULL == (corefmt = kern_corefile())) | |
425 | corefmt = strdup(defcore); | |
426 | else { | |
427 | // use the same directory as kern.corefile | |
428 | char *p = strrchr(corefmt, '/'); | |
429 | if (NULL != p) { | |
430 | *p = '\0'; | |
431 | size_t len = strlen(corefmt) + | |
432 | strlen(defcore) + 2; | |
433 | char *buf = malloc(len); | |
434 | snprintf(buf, len, "%s/%s", corefmt, defcore); | |
435 | free(corefmt); | |
436 | corefmt = buf; | |
437 | } | |
438 | if (opt->debug) | |
439 | printf("corefmt '%s'\n", corefmt); | |
440 | } | |
441 | } | |
442 | corefname = format_gcore_name(corefmt, pbi); | |
443 | free(corefmt); | |
444 | } | |
445 | ||
446 | task_t task; | |
447 | kern_return_t ret = task_for_pid(mach_task_self(), pid, &task); | |
448 | if (KERN_SUCCESS != ret) { | |
449 | if (KERN_FAILURE == ret) | |
450 | errx(EX_NOPERM, "insufficient privilege"); | |
451 | else | |
452 | errx(EX_NOPERM, "task_for_pid: %s", mach_error_string(ret)); | |
453 | } | |
454 | ||
455 | /* | |
456 | * Now that we have the task port, we adopt the credentials of | |
457 | * the target process, *before* opening the core file, and | |
458 | * analyzing the address space. | |
459 | * | |
460 | * If we are unable to match the target credentials, bail out. | |
461 | */ | |
462 | if (getgid() != pbi->pbi_gid && | |
463 | setgid(pbi->pbi_gid) == -1) | |
464 | errc(EX_NOPERM, errno, "insufficient privilege"); | |
465 | ||
466 | if (getuid() != pbi->pbi_uid && | |
467 | setuid(pbi->pbi_uid) == -1) | |
468 | errc(EX_NOPERM, errno, "insufficient privilege"); | |
469 | ||
470 | int fd; | |
471 | ||
472 | if (opt->dryrun) { | |
473 | free(corefname); | |
474 | corefname = strdup("/dev/null"); | |
475 | fd = open(corefname, O_RDWR); | |
476 | } else { | |
477 | fd = open(corefname, O_RDWR | O_CREAT | O_EXCL, 0400); | |
478 | ||
479 | struct stat st; | |
480 | ||
481 | if (-1 == fd || -1 == fstat(fd, &st)) | |
482 | errc(EX_CANTCREAT, errno, "%s", corefname); | |
483 | if ((st.st_mode & S_IFMT) != S_IFREG || 1 != st.st_nlink) { | |
484 | close(fd); | |
485 | errx(EX_CANTCREAT, "%s: invalid file", corefname); | |
486 | } | |
487 | } | |
488 | ||
489 | if (opt->verbose) { | |
490 | printf("Dumping core "); | |
491 | if (opt->debug) { | |
492 | printf("(%s%s%s", | |
493 | opt->sparse ? "sparse" : "normal", | |
494 | opt->compress ? ", compressed" : "", | |
495 | #ifdef CONFIG_REFSC | |
496 | opt->scfileref ? ", scfilerefs" : | |
497 | #endif | |
498 | ""); | |
499 | if (0 != opt->sizebound) { | |
500 | hsize_str_t hstr; | |
501 | printf(", %s", str_hsize(hstr, opt->sizebound)); | |
502 | } | |
503 | printf(") "); | |
504 | } | |
505 | printf("for pid %d to %s\n", pid, corefname); | |
506 | } | |
507 | int ecode; | |
508 | ||
509 | /* | |
510 | * The traditional way to capture a consistent core dump is to | |
511 | * suspend the process while processing it and writing it out. | |
512 | * Yet suspending a large process for a long time can have | |
513 | * unpleasant side-effects. Alternatively dumping from the live | |
514 | * process can lead to an inconsistent state in the core file. | |
515 | * | |
516 | * Instead we can ask xnu to create a 'corpse' - the process is transiently | |
517 | * suspended while a COW snapshot of the address space is constructed | |
518 | * in the kernel and dump from that. This vastly reduces the suspend | |
519 | * time, but it is more resource hungry and thus may fail. | |
520 | * | |
521 | * The -s flag (opt->suspend) causes a task_suspend/task_resume | |
522 | * The -C flag (opt->corpse) causes a corpse be taken, exiting if that fails. | |
523 | * Both flags can be specified, in which case corpse errors are ignored. | |
524 | * | |
525 | * With no flags, we imitate traditional behavior though more | |
526 | * efficiently: we try to take a corpse-based dump, in the event that | |
527 | * fails, dump the live process. | |
528 | */ | |
529 | ||
530 | int trycorpse = 1; /* default: use corpses */ | |
531 | int badcorpse_is_fatal = 1; /* default: failure to create a corpse is an error */ | |
532 | ||
533 | if (!opt->suspend && !opt->corpse) { | |
534 | /* try a corpse dump, else dump the live process */ | |
535 | badcorpse_is_fatal = 0; | |
536 | } else if (opt->suspend) { | |
537 | trycorpse = opt->corpse; | |
538 | /* if suspended anyway, ignore corpse-creation errors */ | |
539 | badcorpse_is_fatal = 0; | |
540 | } | |
541 | ||
542 | if (opt->suspend) | |
543 | task_suspend(task); | |
544 | ||
545 | if (trycorpse) { | |
546 | /* | |
547 | * Create a corpse from the image before dumping it | |
548 | */ | |
549 | mach_port_t corpse = MACH_PORT_NULL; | |
550 | ret = task_generate_corpse(task, &corpse); | |
551 | switch (ret) { | |
552 | case KERN_SUCCESS: | |
553 | if (opt->debug) | |
554 | printf("corpse generated on port %x, task %x\n", | |
555 | corpse, task); | |
556 | ecode = coredump(corpse, fd); | |
557 | mach_port_deallocate(mach_task_self(), corpse); | |
558 | break; | |
559 | default: | |
560 | if (badcorpse_is_fatal || opt->debug) { | |
561 | warnx("failed to snapshot pid %d: %s\n", | |
562 | pid, mach_error_string(ret)); | |
563 | if (badcorpse_is_fatal) { | |
564 | ecode = KERN_RESOURCE_SHORTAGE == ret ? EX_TEMPFAIL : EX_OSERR; | |
565 | goto out; | |
566 | } | |
567 | } | |
568 | ecode = coredump(task, fd); | |
569 | break; | |
570 | } | |
571 | } else { | |
572 | /* | |
573 | * Examine the task directly | |
574 | */ | |
575 | ecode = coredump(task, fd); | |
576 | } | |
577 | ||
578 | out: | |
579 | if (opt->suspend) | |
580 | task_resume(task); | |
581 | ||
582 | if (0 != ecode && !opt->preserve && !opt->dryrun) { | |
583 | /* | |
584 | * try not to leave a half-written mess occupying | |
585 | * blocks on the filesystem | |
586 | */ | |
587 | ftruncate(fd, 0); | |
588 | unlink(corefname); | |
589 | } | |
590 | if (-1 == close(fd)) | |
591 | ecode = EX_OSERR; | |
592 | if (ecode) | |
593 | errx(ecode, "failed to dump core for pid %d", pid); | |
594 | free(corefname); | |
595 | ||
596 | return 0; | |
597 | } |