]> git.saurik.com Git - apple/system_cmds.git/blob - gcore.tproj/main.c
system_cmds-735.20.1.tar.gz
[apple/system_cmds.git] / gcore.tproj / main.c
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 }