]>
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" | |
887d5eed | 10 | #include "convert.h" |
cf37c299 A |
11 | |
12 | #include <sys/types.h> | |
13 | #include <sys/sysctl.h> | |
14 | #include <sys/stat.h> | |
15 | #include <sys/mman.h> | |
16 | #include <libproc.h> | |
17 | ||
18 | #include <sys/kauth.h> | |
19 | ||
20 | #include <stdio.h> | |
21 | #include <string.h> | |
22 | #include <strings.h> | |
23 | #include <stddef.h> | |
24 | #include <stdlib.h> | |
25 | #include <stdarg.h> | |
26 | #include <signal.h> | |
27 | #include <unistd.h> | |
28 | #include <errno.h> | |
29 | #include <ctype.h> | |
30 | #include <fcntl.h> | |
31 | #include <assert.h> | |
32 | #include <libutil.h> | |
33 | ||
34 | #include <mach/mach.h> | |
35 | ||
36 | static char * | |
37 | kern_corefile(void) | |
38 | { | |
39 | char *(^sysc_string)(const char *name) = ^(const char *name) { | |
40 | char *p = NULL; | |
41 | size_t len = 0; | |
42 | ||
43 | if (-1 == sysctlbyname(name, NULL, &len, NULL, 0)) { | |
44 | warnc(errno, "sysctl: %s", name); | |
45 | } else if (0 != len) { | |
46 | p = malloc(len); | |
47 | if (-1 == sysctlbyname(name, p, &len, NULL, 0)) { | |
48 | warnc(errno, "sysctl: %s", name); | |
49 | free(p); | |
50 | p = NULL; | |
51 | } | |
52 | } | |
53 | return p; | |
54 | }; | |
55 | ||
56 | char *s = sysc_string("kern.corefile"); | |
57 | if (NULL == s) | |
58 | s = strdup("/cores/core.%P"); | |
59 | return s; | |
60 | } | |
61 | ||
62 | static const struct proc_bsdinfo * | |
63 | get_bsdinfo(pid_t pid) | |
64 | { | |
65 | if (0 == pid) | |
66 | return NULL; | |
67 | struct proc_bsdinfo *pbi = calloc(1, sizeof (*pbi)); | |
68 | if (0 != proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, pbi, sizeof (*pbi))) | |
69 | return pbi; | |
70 | free(pbi); | |
71 | return NULL; | |
72 | } | |
73 | ||
74 | static char * | |
887d5eed | 75 | format_gcore_name(const char *fmt, pid_t pid, uid_t uid, const char *nm) |
cf37c299 A |
76 | { |
77 | __block size_t resid = MAXPATHLEN; | |
78 | __block char *p = calloc(1, resid); | |
79 | char *out = p; | |
80 | ||
81 | int (^outchar)(int c) = ^(int c) { | |
82 | if (resid > 1) { | |
83 | *p++ = (char)c; | |
84 | resid--; | |
85 | return 1; | |
86 | } else | |
87 | return 0; | |
88 | }; | |
89 | ||
90 | ptrdiff_t (^outstr)(const char *str) = ^(const char *str) { | |
91 | const char *s = str; | |
92 | while (*s && 0 != outchar(*s++)) | |
93 | ; | |
94 | return s - str; | |
95 | }; | |
96 | ||
887d5eed | 97 | ptrdiff_t (^outint)(int value) = ^(int value) { |
cf37c299 A |
98 | char id[11]; |
99 | snprintf(id, sizeof (id), "%u", value); | |
100 | return outstr(id); | |
101 | }; | |
102 | ||
103 | ptrdiff_t (^outtstamp)(void) = ^(void) { | |
104 | time_t now; | |
105 | time(&now); | |
106 | struct tm tm; | |
107 | gmtime_r(&now, &tm); | |
108 | char tstamp[50]; | |
109 | strftime(tstamp, sizeof (tstamp), "%Y%m%dT%H%M%SZ", &tm); | |
110 | return outstr(tstamp); | |
111 | }; | |
112 | ||
113 | int c; | |
114 | ||
115 | for (int i = 0; resid > 0; i++) | |
116 | switch (c = fmt[i]) { | |
117 | default: | |
118 | outchar(c); | |
119 | break; | |
120 | case '%': | |
121 | i++; | |
122 | switch (c = fmt[i]) { | |
123 | case '%': | |
124 | outchar(c); | |
125 | break; | |
126 | case 'P': | |
887d5eed | 127 | outint(pid); |
cf37c299 A |
128 | break; |
129 | case 'U': | |
887d5eed | 130 | outint(uid); |
cf37c299 A |
131 | break; |
132 | case 'N': | |
887d5eed | 133 | outstr(nm); |
cf37c299 A |
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 | ||
887d5eed A |
155 | static char * |
156 | make_gcore_path(char **corefmtp, pid_t pid, uid_t uid, const char *nm) | |
157 | { | |
158 | char *corefmt = *corefmtp; | |
159 | if (NULL == corefmt) { | |
160 | const char defcore[] = "%N-%P-%T"; | |
161 | if (NULL == (corefmt = kern_corefile())) | |
162 | corefmt = strdup(defcore); | |
163 | else { | |
164 | // use the same directory as kern.corefile | |
165 | char *p = strrchr(corefmt, '/'); | |
166 | if (NULL != p) { | |
167 | *p = '\0'; | |
168 | size_t len = strlen(corefmt) + strlen(defcore) + 2; | |
169 | char *buf = malloc(len); | |
170 | snprintf(buf, len, "%s/%s", corefmt, defcore); | |
171 | free(corefmt); | |
172 | corefmt = buf; | |
173 | } | |
174 | if (OPTIONS_DEBUG(opt, 3)) | |
175 | printf("corefmt '%s'\n", corefmt); | |
176 | } | |
177 | } | |
178 | char *path = format_gcore_name(corefmt, pid, uid, nm); | |
179 | free(corefmt); | |
180 | *corefmtp = NULL; | |
181 | return path; | |
182 | } | |
183 | ||
184 | static bool proc_same_data_model(const struct proc_bsdinfo *pbi) { | |
185 | #if defined(__LP64__) | |
186 | return (pbi->pbi_flags & PROC_FLAG_LP64) != 0; | |
187 | #else | |
188 | return (pbi->pbi_flags & PROC_FLAG_LP64) == 0; | |
189 | #endif | |
190 | } | |
191 | ||
192 | static bool task_same_data_model(const task_flags_info_data_t *tfid) { | |
193 | #if defined(__LP64__) | |
194 | return (tfid->flags & TF_LP64) != 0; | |
195 | #else | |
196 | return (tfid->flags & TF_LP64) == 0; | |
197 | #endif | |
198 | } | |
199 | ||
200 | /* | |
201 | * Change credentials for writing out the file | |
202 | */ | |
203 | static void | |
204 | change_credentials(gid_t uid, uid_t gid) | |
205 | { | |
206 | if ((getgid() != gid && -1 == setgid(gid)) || | |
207 | (getuid() != uid && -1 == setuid(uid))) | |
208 | errc(EX_NOPERM, errno, "insufficient privilege"); | |
209 | if (uid != getuid() || gid != getgid()) | |
210 | err(EX_OSERR, "wrong credentials"); | |
211 | } | |
212 | ||
213 | static int | |
214 | openout(const char *corefname, char **coretname, struct stat *st) | |
215 | { | |
216 | const int tfd = open(corefname, O_WRONLY); | |
217 | if (-1 == tfd) { | |
218 | if (ENOENT == errno) { | |
219 | /* | |
220 | * Arrange for a core file to appear "atomically": write the data | |
221 | * to the file + ".tmp" suffix, then fchmod and rename it into | |
222 | * place once the dump completes successfully. | |
223 | */ | |
224 | const size_t nmlen = strlen(corefname) + 4 + 1; | |
225 | char *tnm = malloc(nmlen); | |
226 | snprintf(tnm, nmlen, "%s.tmp", corefname); | |
227 | const int fd = open(tnm, O_WRONLY | O_CREAT | O_TRUNC, 0600); | |
228 | if (-1 == fd || -1 == fstat(fd, st)) | |
229 | errc(EX_CANTCREAT, errno, "%s", tnm); | |
230 | if (!S_ISREG(st->st_mode) || 1 != st->st_nlink) | |
231 | errx(EX_CANTCREAT, "%s: invalid attributes", tnm); | |
232 | *coretname = tnm; | |
233 | return fd; | |
234 | } else | |
235 | errc(EX_CANTCREAT, errno, "%s", corefname); | |
236 | } else if (-1 == fstat(tfd, st)) { | |
237 | close(tfd); | |
238 | errx(EX_CANTCREAT, "%s: fstat", corefname); | |
239 | } else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) { | |
240 | /* | |
241 | * Write dump to a device, no rename! | |
242 | */ | |
243 | *coretname = NULL; | |
244 | return tfd; | |
245 | } else { | |
246 | close(tfd); | |
247 | errc(EX_CANTCREAT, EEXIST, "%s", corefname); | |
248 | } | |
249 | } | |
250 | ||
251 | static int | |
252 | closeout(int fd, int ecode, char *corefname, char *coretname, const struct stat *st) | |
253 | { | |
254 | if (0 != ecode && !opt->preserve && S_ISREG(st->st_mode)) | |
255 | ftruncate(fd, 0); // limit large file clutter | |
256 | if (0 == ecode && S_ISREG(st->st_mode)) | |
257 | fchmod(fd, 0400); // protect core files | |
258 | if (-1 == close(fd)) { | |
259 | warnc(errno, "%s: close", coretname ? coretname : corefname); | |
260 | ecode = EX_OSERR; | |
261 | } | |
262 | if (NULL != coretname) { | |
263 | if (0 == ecode && -1 == rename(coretname, corefname)) { | |
264 | warnc(errno, "cannot rename %s to %s", coretname, corefname); | |
265 | ecode = EX_NOPERM; | |
266 | } | |
267 | free(coretname); | |
268 | } | |
269 | if (corefname) | |
270 | free(corefname); | |
271 | return ecode; | |
272 | } | |
273 | ||
cf37c299 A |
274 | const char *pgm; |
275 | const struct options *opt; | |
276 | ||
887d5eed A |
277 | static const size_t oneK = 1024; |
278 | static const size_t oneM = oneK * oneK; | |
279 | ||
280 | #define LARGEST_CHUNKSIZE INT32_MAX | |
281 | #define DEFAULT_COMPRESSION_CHUNKSIZE (16 * oneM) | |
282 | #define DEFAULT_NC_THRESHOLD (17 * oneK) | |
cf37c299 | 283 | |
887d5eed A |
284 | static struct options options = { |
285 | .corpsify = 0, | |
286 | .suspend = 0, | |
287 | .preserve = 0, | |
288 | .verbose = 0, | |
289 | #ifdef CONFIG_DEBUG | |
290 | .debug = 0, | |
291 | #endif | |
292 | .extended = 0, | |
293 | .sizebound = 0, | |
294 | .chunksize = 0, | |
295 | .calgorithm = COMPRESSION_LZFSE, | |
296 | .ncthresh = DEFAULT_NC_THRESHOLD, | |
d52496fd | 297 | .dsymforuuid = 0, |
887d5eed A |
298 | }; |
299 | ||
300 | static int | |
301 | gcore_main(int argc, char *const *argv) | |
302 | { | |
cf37c299 A |
303 | #define ZOPT_ALG (0) |
304 | #define ZOPT_CHSIZE (ZOPT_ALG + 1) | |
305 | ||
306 | static char *const zoptkeys[] = { | |
307 | [ZOPT_ALG] = "algorithm", | |
308 | [ZOPT_CHSIZE] = "chunksize", | |
309 | NULL | |
310 | }; | |
311 | ||
312 | err_set_exit_b(^(int eval) { | |
313 | if (EX_USAGE == eval) { | |
314 | fprintf(stderr, | |
887d5eed | 315 | "usage:\t%s [-s] [-v] [[-o file] | [-c pathfmt ]] [-b size] " |
cf37c299 | 316 | #if DEBUG |
887d5eed A |
317 | #ifdef CONFIG_DEBUG |
318 | "[-d] " | |
cf37c299 | 319 | #endif |
887d5eed A |
320 | "[-x] [-C] " |
321 | "[-Z compression-options] " | |
322 | "[-t size] " | |
323 | "[-F] " | |
cf37c299 A |
324 | #endif |
325 | "pid\n", pgm); | |
326 | #if DEBUG | |
327 | fprintf(stderr, "where compression-options:\n"); | |
328 | const char zvalfmt[] = "\t%s=%s\t\t%s\n"; | |
329 | fprintf(stderr, zvalfmt, zoptkeys[ZOPT_ALG], "alg", | |
330 | "set compression algorithm"); | |
331 | fprintf(stderr, zvalfmt, zoptkeys[ZOPT_CHSIZE], "size", | |
332 | "set compression chunksize, Mib"); | |
333 | #endif | |
334 | } | |
335 | }); | |
336 | ||
337 | char *corefmt = NULL; | |
338 | char *corefname = NULL; | |
cf37c299 A |
339 | |
340 | int c; | |
341 | char *sopts, *value; | |
342 | ||
887d5eed | 343 | while ((c = getopt(argc, argv, "vdsxCFZ:o:c:b:t:")) != -1) { |
cf37c299 A |
344 | switch (c) { |
345 | ||
346 | /* | |
347 | * documented options | |
348 | */ | |
349 | case 's': /* FreeBSD compat: stop while gathering */ | |
350 | options.suspend = 1; | |
351 | break; | |
352 | case 'o': /* Linux (& SunOS) compat: basic name */ | |
353 | corefname = strdup(optarg); | |
354 | break; | |
355 | case 'c': /* FreeBSD compat: basic name */ | |
356 | /* (also allows pattern-based naming) */ | |
357 | corefmt = strdup(optarg); | |
358 | break; | |
359 | ||
360 | case 'b': /* bound the size of the core file */ | |
361 | if (NULL != optarg) { | |
362 | off_t bsize = atoi(optarg) * oneM; | |
363 | if (bsize > 0) | |
364 | options.sizebound = bsize; | |
365 | else | |
366 | errx(EX_USAGE, "invalid bound"); | |
367 | } else | |
368 | errx(EX_USAGE, "no bound specified"); | |
369 | break; | |
370 | case 'v': /* verbose output */ | |
371 | options.verbose++; | |
372 | break; | |
373 | ||
374 | /* | |
375 | * dev and debugging help | |
376 | */ | |
887d5eed | 377 | #ifdef CONFIG_DEBUG |
cf37c299 A |
378 | case 'd': /* debugging */ |
379 | options.debug++; | |
380 | options.verbose++; | |
381 | options.preserve++; | |
382 | break; | |
887d5eed | 383 | #endif |
cf37c299 A |
384 | /* |
385 | * Remaining options are experimental and/or | |
386 | * affect the content of the core file | |
387 | */ | |
887d5eed A |
388 | case 'x': /* write extended format (small) core files */ |
389 | options.extended++; | |
390 | options.chunksize = DEFAULT_COMPRESSION_CHUNKSIZE; | |
cf37c299 | 391 | break; |
887d5eed A |
392 | case 'C': /* forcibly corpsify rather than suspend */ |
393 | options.corpsify++; | |
cf37c299 A |
394 | break; |
395 | case 'Z': /* control compression options */ | |
396 | /* | |
397 | * Only LZFSE and LZ4 seem practical. | |
398 | * (Default to LZ4 compression when the | |
399 | * process is suspended, LZFSE when corpsed?) | |
400 | */ | |
887d5eed A |
401 | if (0 == options.extended) |
402 | errx(EX_USAGE, "illegal flag combination"); | |
cf37c299 A |
403 | sopts = optarg; |
404 | while (*sopts) { | |
405 | size_t chsize; | |
406 | ||
407 | switch (getsubopt(&sopts, zoptkeys, &value)) { | |
408 | case ZOPT_ALG: /* change the algorithm */ | |
409 | if (NULL == value) | |
410 | errx(EX_USAGE, "missing algorithm for " | |
411 | "%s suboption", | |
412 | zoptkeys[ZOPT_ALG]); | |
413 | if (strcmp(value, "lz4") == 0) | |
887d5eed | 414 | options.calgorithm = COMPRESSION_LZ4; |
cf37c299 | 415 | else if (strcmp(value, "zlib") == 0) |
887d5eed | 416 | options.calgorithm = COMPRESSION_ZLIB; |
cf37c299 | 417 | else if (strcmp(value, "lzma") == 0) |
887d5eed | 418 | options.calgorithm = COMPRESSION_LZMA; |
cf37c299 | 419 | else if (strcmp(value, "lzfse") == 0) |
887d5eed | 420 | options.calgorithm = COMPRESSION_LZFSE; |
cf37c299 A |
421 | else |
422 | errx(EX_USAGE, "unknown algorithm '%s'" | |
423 | " for %s suboption", | |
887d5eed | 424 | value, zoptkeys[ZOPT_ALG]); |
cf37c299 A |
425 | break; |
426 | case ZOPT_CHSIZE: /* set the chunksize */ | |
427 | if (NULL == value) | |
428 | errx(EX_USAGE, "no value specified for " | |
429 | "%s suboption", | |
430 | zoptkeys[ZOPT_CHSIZE]); | |
431 | if ((chsize = atoi(value)) < 1) | |
432 | errx(EX_USAGE, "chunksize %lu too small", chsize); | |
433 | if (chsize > (LARGEST_CHUNKSIZE / oneM)) | |
434 | errx(EX_USAGE, "chunksize %lu too large", chsize); | |
435 | options.chunksize = chsize * oneM; | |
436 | break; | |
437 | default: | |
438 | if (suboptarg) | |
439 | errx(EX_USAGE, "illegal suboption '%s'", | |
440 | suboptarg); | |
441 | else | |
442 | errx(EX_USAGE, "missing suboption"); | |
443 | } | |
444 | } | |
887d5eed A |
445 | break; |
446 | case 't': /* set the F_NOCACHE threshold */ | |
447 | if (NULL != optarg) { | |
448 | size_t tsize = atoi(optarg) * oneK; | |
449 | if (tsize > 0) | |
450 | options.ncthresh = tsize; | |
451 | else | |
452 | errx(EX_USAGE, "invalid nc threshold"); | |
453 | } else | |
454 | errx(EX_USAGE, "no threshold specified"); | |
455 | break; | |
456 | case 'F': /* maximize filerefs */ | |
457 | options.allfilerefs++; | |
cf37c299 A |
458 | break; |
459 | default: | |
460 | errx(EX_USAGE, "unknown flag"); | |
461 | } | |
462 | } | |
cf37c299 | 463 | |
887d5eed A |
464 | if (optind == argc) |
465 | errx(EX_USAGE, "no pid specified"); | |
466 | if (optind < argc-1) | |
467 | errx(EX_USAGE, "too many arguments"); | |
468 | ||
469 | opt = &options; | |
470 | if (NULL != corefname && NULL != corefmt) | |
471 | errx(EX_USAGE, "specify only one of -o and -c"); | |
472 | if (!opt->extended && opt->allfilerefs) | |
473 | errx(EX_USAGE, "unknown flag"); | |
474 | ||
475 | setpageshift(); | |
476 | ||
477 | if (opt->ncthresh < ((vm_offset_t)1 << pageshift_host)) | |
478 | errx(EX_USAGE, "threshold %lu less than host pagesize", opt->ncthresh); | |
479 | ||
480 | const pid_t apid = atoi(argv[optind]); | |
481 | pid_t pid = apid; | |
482 | mach_port_t corpse = MACH_PORT_NULL; | |
483 | kern_return_t ret; | |
484 | ||
485 | if (0 == apid) { | |
486 | /* look for corpse - dead or alive */ | |
487 | mach_port_array_t parray = NULL; | |
488 | mach_msg_type_number_t pcount = 0; | |
489 | ret = mach_ports_lookup(mach_task_self(), &parray, &pcount); | |
490 | if (KERN_SUCCESS == ret && pcount > 0) { | |
491 | task_t tcorpse = parray[0]; | |
492 | mig_deallocate((vm_address_t)parray, pcount * sizeof (*parray)); | |
493 | pid_t tpid = 0; | |
494 | ret = pid_for_task(tcorpse, &tpid); | |
495 | if (KERN_SUCCESS == ret && tpid != getpid()) { | |
496 | corpse = tcorpse; | |
497 | pid = tpid; | |
498 | } | |
499 | } | |
500 | } | |
501 | ||
502 | if (pid < 1 || getpid() == pid) | |
503 | errx(EX_DATAERR, "invalid pid: %d", pid); | |
504 | ||
505 | if (0 == apid && MACH_PORT_NULL == corpse) | |
506 | errx(EX_DATAERR, "missing or bad corpse from parent"); | |
507 | ||
508 | task_t task = TASK_NULL; | |
509 | const struct proc_bsdinfo *pbi = NULL; | |
d52496fd | 510 | const int rc = kill(pid, 0); |
887d5eed | 511 | |
d52496fd A |
512 | if (rc == 0) { |
513 | /* process or corpse that may respond to signals */ | |
887d5eed | 514 | pbi = get_bsdinfo(pid); |
d52496fd A |
515 | } |
516 | ||
517 | if (rc == 0 && pbi != NULL) { | |
518 | /* process or corpse that responds to signals */ | |
887d5eed A |
519 | |
520 | /* make our data model match the data model of the target */ | |
521 | if (-1 == reexec_to_match_lp64ness(pbi->pbi_flags & PROC_FLAG_LP64)) | |
522 | errc(1, errno, "cannot match data model of %d", pid); | |
523 | ||
524 | if (!proc_same_data_model(pbi)) | |
525 | errx(EX_OSERR, "cannot match data model of %d", pid); | |
526 | ||
527 | if (pbi->pbi_ruid != pbi->pbi_svuid || | |
528 | pbi->pbi_rgid != pbi->pbi_svgid) | |
529 | errx(EX_NOPERM, "pid %d - not dumping a set-id process", pid); | |
530 | ||
531 | if (NULL == corefname) | |
532 | corefname = make_gcore_path(&corefmt, pbi->pbi_pid, pbi->pbi_uid, pbi->pbi_name[0] ? pbi->pbi_name : pbi->pbi_comm); | |
533 | ||
534 | if (MACH_PORT_NULL == corpse) { | |
535 | ret = task_for_pid(mach_task_self(), pid, &task); | |
536 | if (KERN_SUCCESS != ret) { | |
537 | if (KERN_FAILURE == ret) | |
538 | errx(EX_NOPERM, "insufficient privilege"); | |
539 | else | |
540 | errx(EX_NOPERM, "task_for_pid: %s", mach_error_string(ret)); | |
541 | } | |
542 | } | |
543 | ||
544 | /* | |
545 | * Have either the corpse port or the task port so adopt the | |
546 | * credentials of the target process, *before* opening the | |
547 | * core file, and analyzing the address space. | |
548 | * | |
549 | * If we are unable to match the target credentials, bail out. | |
550 | */ | |
551 | change_credentials(pbi->pbi_uid, pbi->pbi_gid); | |
552 | } else { | |
553 | if (MACH_PORT_NULL == corpse) { | |
d52496fd A |
554 | if (rc == 0) { |
555 | errx(EX_OSERR, "cannot get process info for %d", pid); | |
556 | } | |
887d5eed A |
557 | switch (errno) { |
558 | case ESRCH: | |
559 | errc(EX_DATAERR, errno, "no process with pid %d", pid); | |
560 | default: | |
561 | errc(EX_DATAERR, errno, "pid %d", pid); | |
562 | } | |
563 | } | |
564 | /* a corpse with no live process backing it */ | |
565 | ||
566 | assert(0 == apid && TASK_NULL == task); | |
567 | ||
568 | task_flags_info_data_t tfid; | |
569 | mach_msg_type_number_t count = TASK_FLAGS_INFO_COUNT; | |
570 | ret = task_info(corpse, TASK_FLAGS_INFO, (task_info_t)&tfid, &count); | |
571 | if (KERN_SUCCESS != ret) | |
572 | err_mach(ret, NULL, "task_info"); | |
573 | if (!task_same_data_model(&tfid)) | |
574 | errx(EX_OSERR, "data model mismatch for target corpse"); | |
575 | ||
576 | if (opt->suspend || opt->corpsify) | |
577 | errx(EX_USAGE, "cannot use -s or -C option with a corpse"); | |
578 | if (NULL != corefmt) | |
579 | errx(EX_USAGE, "cannot use -c with a corpse"); | |
580 | if (NULL == corefname) | |
581 | corefname = make_gcore_path(&corefmt, pid, -2, "corpse"); | |
582 | ||
583 | /* | |
584 | * Only have a corpse, thus no process credentials. | |
585 | * Switch to nobody. | |
586 | */ | |
587 | change_credentials(-2, -2); | |
588 | } | |
589 | ||
590 | struct stat cst; | |
591 | char *coretname = NULL; | |
592 | const int fd = openout(corefname, &coretname, &cst); | |
593 | ||
594 | if (opt->verbose) { | |
595 | printf("Dumping core "); | |
596 | if (OPTIONS_DEBUG(opt, 1)) { | |
597 | printf("(%s", opt->extended ? "extended" : "vanilla"); | |
598 | if (0 != opt->sizebound) { | |
599 | hsize_str_t hstr; | |
600 | printf(", <= %s", str_hsize(hstr, opt->sizebound)); | |
601 | } | |
602 | printf(") "); | |
603 | } | |
604 | printf("for pid %d to %s\n", pid, corefname); | |
605 | } | |
606 | ||
607 | int ecode; | |
cf37c299 | 608 | |
887d5eed A |
609 | if (MACH_PORT_NULL == corpse) { |
610 | assert(TASK_NULL != task); | |
611 | ||
612 | /* | |
613 | * The "traditional" way to capture a consistent core dump is to | |
614 | * suspend the process while examining it and writing it out. | |
615 | * Yet suspending a large process for a long time can have | |
616 | * unpleasant side-effects. Alternatively dumping from the live | |
617 | * process can lead to an inconsistent state in the core file. | |
618 | * | |
619 | * Instead we can ask xnu to create a 'corpse' - the process is transiently | |
620 | * suspended while a COW snapshot of the address space is constructed | |
621 | * in the kernel and dump from that. This vastly reduces the suspend | |
622 | * time, but it is more resource hungry and thus may fail. | |
623 | * | |
624 | * The -s flag (opt->suspend) causes a task_suspend/task_resume | |
625 | * The -C flag (opt->corpse) causes a corpse be taken, exiting if that fails. | |
626 | * Both flags can be specified, in which case corpse errors are ignored. | |
627 | * | |
628 | * With no flags, we imitate traditional behavior though more | |
629 | * efficiently: we try to take a corpse-based dump, in the event that | |
630 | * fails, dump the live process. | |
631 | */ | |
632 | ||
633 | int trycorpse = 1; /* default: use corpses */ | |
634 | int badcorpse_is_fatal = 1; /* default: failure to create is an error */ | |
635 | ||
636 | if (!opt->suspend && !opt->corpsify) { | |
637 | /* try a corpse dump, else dump the live process */ | |
638 | badcorpse_is_fatal = 0; | |
639 | } else if (opt->suspend) { | |
640 | trycorpse = opt->corpsify; | |
641 | /* if suspended anyway, ignore corpse-creation errors */ | |
642 | badcorpse_is_fatal = 0; | |
643 | } | |
644 | ||
645 | if (opt->suspend) | |
646 | task_suspend(task); | |
647 | ||
648 | if (trycorpse) { | |
649 | /* | |
650 | * Create a corpse from the image before dumping it | |
651 | */ | |
652 | ret = task_generate_corpse(task, &corpse); | |
653 | switch (ret) { | |
654 | case KERN_SUCCESS: | |
655 | if (OPTIONS_DEBUG(opt, 1)) | |
656 | printf("Corpse generated on port %x, task %x\n", | |
657 | corpse, task); | |
658 | ecode = coredump(corpse, fd, pbi); | |
659 | mach_port_deallocate(mach_task_self(), corpse); | |
660 | break; | |
661 | default: | |
662 | if (badcorpse_is_fatal || opt->verbose) { | |
663 | warnx("failed to snapshot pid %d: %s\n", | |
664 | pid, mach_error_string(ret)); | |
665 | if (badcorpse_is_fatal) { | |
666 | ecode = KERN_RESOURCE_SHORTAGE == ret ? EX_TEMPFAIL : EX_OSERR; | |
667 | goto out; | |
668 | } | |
669 | } | |
670 | ecode = coredump(task, fd, pbi); | |
671 | break; | |
672 | } | |
673 | } else { | |
674 | /* | |
675 | * Examine the task directly | |
676 | */ | |
677 | ecode = coredump(task, fd, pbi); | |
678 | } | |
679 | ||
680 | out: | |
681 | if (opt->suspend) | |
682 | task_resume(task); | |
683 | } else { | |
684 | /* | |
685 | * Handed a corpse by our parent. | |
686 | */ | |
687 | ecode = coredump(corpse, fd, pbi); | |
688 | mach_port_deallocate(mach_task_self(), corpse); | |
689 | } | |
690 | ||
691 | ecode = closeout(fd, ecode, corefname, coretname, &cst); | |
692 | if (ecode) | |
693 | errx(ecode, "failed to dump core for pid %d", pid); | |
694 | return 0; | |
695 | } | |
cf37c299 | 696 | |
887d5eed | 697 | #if defined(CONFIG_GCORE_FREF) || defined(CONFIG_GCORE_MAP) || defined(GCONFIG_GCORE_CONV) |
cf37c299 | 698 | |
887d5eed A |
699 | static int |
700 | getcorefd(const char *infile) | |
701 | { | |
702 | const int fd = open(infile, O_RDONLY | O_CLOEXEC); | |
703 | if (-1 == fd) | |
704 | errc(EX_DATAERR, errno, "cannot open %s", infile); | |
cf37c299 | 705 | |
887d5eed A |
706 | struct mach_header mh; |
707 | if (-1 == pread(fd, &mh, sizeof (mh), 0)) | |
708 | errc(EX_OSERR, errno, "cannot read mach header from %s", infile); | |
cf37c299 | 709 | |
887d5eed A |
710 | static const char cant_match_data_model[] = "cannot match the data model of %s"; |
711 | ||
712 | if (-1 == reexec_to_match_lp64ness(MH_MAGIC_64 == mh.magic)) | |
713 | errc(1, errno, cant_match_data_model, infile); | |
714 | ||
715 | if (NATIVE_MH_MAGIC != mh.magic) | |
716 | errx(EX_OSERR, cant_match_data_model, infile); | |
717 | if (MH_CORE != mh.filetype) | |
718 | errx(EX_DATAERR, "%s is not a mach core file", infile); | |
719 | return fd; | |
720 | } | |
cf37c299 | 721 | |
cf37c299 | 722 | #endif |
cf37c299 | 723 | |
887d5eed | 724 | #ifdef CONFIG_GCORE_FREF |
cf37c299 | 725 | |
887d5eed A |
726 | static int |
727 | gcore_fref_main(int argc, char *argv[]) | |
728 | { | |
729 | err_set_exit_b(^(int eval) { | |
730 | if (EX_USAGE == eval) { | |
731 | fprintf(stderr, "usage:\t%s %s corefile\n", pgm, argv[1]); | |
732 | } | |
733 | }); | |
734 | if (2 == argc) | |
735 | errx(EX_USAGE, "no input corefile"); | |
736 | if (argc > 3) | |
737 | errx(EX_USAGE, "too many arguments"); | |
738 | opt = &options; | |
739 | return gcore_fref(getcorefd(argv[2])); | |
740 | } | |
cf37c299 | 741 | |
887d5eed | 742 | #endif /* CONFIG_GCORE_FREF */ |
cf37c299 | 743 | |
887d5eed | 744 | #ifdef CONFIG_GCORE_MAP |
cf37c299 | 745 | |
887d5eed A |
746 | static int |
747 | gcore_map_main(int argc, char *argv[]) | |
748 | { | |
749 | err_set_exit_b(^(int eval) { | |
750 | if (EX_USAGE == eval) { | |
751 | fprintf(stderr, "usage:\t%s %s corefile\n", pgm, argv[1]); | |
752 | } | |
753 | }); | |
754 | if (2 == argc) | |
755 | errx(EX_USAGE, "no input corefile"); | |
756 | if (argc > 3) | |
757 | errx(EX_USAGE, "too many arguments"); | |
758 | opt = &options; | |
759 | return gcore_map(getcorefd(argv[2])); | |
760 | } | |
cf37c299 | 761 | |
887d5eed | 762 | #endif |
cf37c299 | 763 | |
887d5eed A |
764 | #ifdef CONFIG_GCORE_CONV |
765 | ||
766 | static int | |
767 | gcore_conv_main(int argc, char *argv[]) | |
768 | { | |
769 | err_set_exit_b(^(int eval) { | |
770 | if (EX_USAGE == eval) | |
771 | fprintf(stderr, | |
d52496fd | 772 | "usage:\t%s %s [-v] [-L searchpath] [-z] [-s] incore outcore\n", pgm, argv[1]); |
887d5eed A |
773 | }); |
774 | ||
775 | char *searchpath = NULL; | |
776 | bool zf = false; | |
777 | ||
778 | int c; | |
779 | optind = 2; | |
d52496fd | 780 | while ((c = getopt(argc, argv, "dzvL:s")) != -1) { |
887d5eed A |
781 | switch (c) { |
782 | /* | |
783 | * likely documented options | |
784 | */ | |
785 | case 'L': | |
786 | searchpath = strdup(optarg); | |
787 | break; | |
788 | case 'z': | |
789 | zf = true; | |
790 | break; | |
791 | case 'v': | |
792 | options.verbose++; | |
793 | break; | |
d52496fd A |
794 | case 's': |
795 | options.dsymforuuid++; | |
796 | break; | |
887d5eed A |
797 | /* |
798 | * dev and debugging help | |
799 | */ | |
800 | #ifdef CONFIG_DEBUG | |
801 | case 'd': | |
802 | options.debug++; | |
803 | options.verbose++; | |
804 | options.preserve++; | |
805 | break; | |
806 | #endif | |
807 | default: | |
808 | errx(EX_USAGE, "unknown flag"); | |
809 | } | |
810 | } | |
811 | if (optind == argc) | |
812 | errx(EX_USAGE, "no input corefile"); | |
813 | if (optind == argc - 1) | |
814 | errx(EX_USAGE, "no output corefile"); | |
815 | if (optind < argc - 2) | |
816 | errx(EX_USAGE, "too many arguments"); | |
817 | ||
818 | const char *incore = argv[optind]; | |
819 | char *corefname = strdup(argv[optind+1]); | |
820 | ||
821 | opt = &options; | |
822 | ||
823 | setpageshift(); | |
824 | ||
825 | if (opt->ncthresh < ((vm_offset_t)1 << pageshift_host)) | |
826 | errx(EX_USAGE, "threshold %lu less than host pagesize", opt->ncthresh); | |
827 | ||
828 | const int infd = getcorefd(incore); | |
829 | struct stat cst; | |
830 | char *coretname = NULL; | |
831 | const int fd = openout(corefname, &coretname, &cst); | |
832 | int ecode = gcore_conv(infd, searchpath, zf, fd); | |
833 | ecode = closeout(fd, ecode, corefname, coretname, &cst); | |
834 | if (ecode) | |
835 | errx(ecode, "failed to convert core file successfully"); | |
836 | return 0; | |
837 | } | |
838 | #endif | |
839 | ||
840 | int | |
841 | main(int argc, char *argv[]) | |
842 | { | |
843 | if (NULL == (pgm = strrchr(*argv, '/'))) | |
844 | pgm = *argv; | |
845 | else | |
846 | pgm++; | |
847 | #ifdef CONFIG_GCORE_FREF | |
848 | if (argc > 1 && 0 == strcmp(argv[1], "fref")) { | |
849 | return gcore_fref_main(argc, argv); | |
850 | } | |
851 | #endif | |
852 | #ifdef CONFIG_GCORE_MAP | |
853 | if (argc > 1 && 0 == strcmp(argv[1], "map")) { | |
854 | return gcore_map_main(argc, argv); | |
855 | } | |
856 | #endif | |
857 | #ifdef CONFIG_GCORE_CONV | |
858 | if (argc > 1 && 0 == strcmp(argv[1], "conv")) { | |
859 | return gcore_conv_main(argc, argv); | |
860 | } | |
861 | #endif | |
862 | return gcore_main(argc, argv); | |
cf37c299 | 863 | } |