2 * Copyright (c) 2016 Apple Inc. All rights reserved.
12 #include <sys/types.h>
13 #include <sys/sysctl.h>
18 #include <sys/kauth.h>
34 #include <mach/mach.h>
39 char *(^sysc_string
)(const char *name
) = ^(const char *name
) {
43 if (-1 == sysctlbyname(name
, NULL
, &len
, NULL
, 0)) {
44 warnc(errno
, "sysctl: %s", name
);
45 } else if (0 != len
) {
47 if (-1 == sysctlbyname(name
, p
, &len
, NULL
, 0)) {
48 warnc(errno
, "sysctl: %s", name
);
56 char *s
= sysc_string("kern.corefile");
58 s
= strdup("/cores/core.%P");
62 static const struct proc_bsdinfo
*
63 get_bsdinfo(pid_t pid
)
67 struct proc_bsdinfo
*pbi
= calloc(1, sizeof (*pbi
));
68 if (0 != proc_pidinfo(pid
, PROC_PIDTBSDINFO
, 0, pbi
, sizeof (*pbi
)))
75 format_gcore_name(const char *fmt
, pid_t pid
, uid_t uid
, const char *nm
)
77 __block
size_t resid
= MAXPATHLEN
;
78 __block
char *p
= calloc(1, resid
);
81 int (^outchar
)(int c
) = ^(int c
) {
90 ptrdiff_t (^outstr
)(const char *str
) = ^(const char *str
) {
92 while (*s
&& 0 != outchar(*s
++))
97 ptrdiff_t (^outint
)(int value
) = ^(int value
) {
99 snprintf(id
, sizeof (id
), "%u", value
);
103 ptrdiff_t (^outtstamp
)(void) = ^(void) {
109 strftime(tstamp
, sizeof (tstamp
), "%Y%m%dT%H%M%SZ", &tm
);
110 return outstr(tstamp
);
115 for (int i
= 0; resid
> 0; i
++)
116 switch (c
= fmt
[i
]) {
122 switch (c
= fmt
[i
]) {
136 outtstamp(); // ISO 8601 format
140 err(EX_DATAERR
, "unknown format char: %%%c", c
);
142 err(EX_DATAERR
, "bad format char %%\\%03o", c
);
144 err(EX_DATAERR
, "bad format specifier");
156 make_gcore_path(char **corefmtp
, pid_t pid
, uid_t uid
, const char *nm
)
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
);
164 // use the same directory as kern.corefile
165 char *p
= strrchr(corefmt
, '/');
168 size_t len
= strlen(corefmt
) + strlen(defcore
) + 2;
169 char *buf
= malloc(len
);
170 snprintf(buf
, len
, "%s/%s", corefmt
, defcore
);
174 if (OPTIONS_DEBUG(opt
, 3))
175 printf("corefmt '%s'\n", corefmt
);
178 char *path
= format_gcore_name(corefmt
, pid
, uid
, nm
);
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;
188 return (pbi
->pbi_flags
& PROC_FLAG_LP64
) == 0;
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;
196 return (tfid
->flags
& TF_LP64
) == 0;
201 * Change credentials for writing out the file
204 change_credentials(gid_t uid
, uid_t gid
)
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");
214 openout(const char *corefname
, char **coretname
, struct stat
*st
)
216 const int tfd
= open(corefname
, O_WRONLY
);
218 if (ENOENT
== errno
) {
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.
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
);
235 errc(EX_CANTCREAT
, errno
, "%s", corefname
);
236 } else if (-1 == fstat(tfd
, st
)) {
238 errx(EX_CANTCREAT
, "%s: fstat", corefname
);
239 } else if (S_ISBLK(st
->st_mode
) || S_ISCHR(st
->st_mode
)) {
241 * Write dump to a device, no rename!
247 errc(EX_CANTCREAT
, EEXIST
, "%s", corefname
);
252 closeout(int fd
, int ecode
, char *corefname
, char *coretname
, const struct stat
*st
)
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
);
262 if (NULL
!= coretname
) {
263 if (0 == ecode
&& -1 == rename(coretname
, corefname
)) {
264 warnc(errno
, "cannot rename %s to %s", coretname
, corefname
);
275 const struct options
*opt
;
277 static const size_t oneK
= 1024;
278 static const size_t oneM
= oneK
* oneK
;
280 #define LARGEST_CHUNKSIZE INT32_MAX
281 #define DEFAULT_COMPRESSION_CHUNKSIZE (16 * oneM)
282 #define DEFAULT_NC_THRESHOLD (17 * oneK)
284 static struct options options
= {
295 .calgorithm
= COMPRESSION_LZFSE
,
296 .ncthresh
= DEFAULT_NC_THRESHOLD
,
300 gcore_main(int argc
, char *const *argv
)
303 #define ZOPT_CHSIZE (ZOPT_ALG + 1)
305 static char *const zoptkeys
[] = {
306 [ZOPT_ALG
] = "algorithm",
307 [ZOPT_CHSIZE
] = "chunksize",
311 err_set_exit_b(^(int eval
) {
312 if (EX_USAGE
== eval
) {
314 "usage:\t%s [-s] [-v] [[-o file] | [-c pathfmt ]] [-b size] "
320 "[-Z compression-options] "
326 fprintf(stderr
, "where compression-options:\n");
327 const char zvalfmt
[] = "\t%s=%s\t\t%s\n";
328 fprintf(stderr
, zvalfmt
, zoptkeys
[ZOPT_ALG
], "alg",
329 "set compression algorithm");
330 fprintf(stderr
, zvalfmt
, zoptkeys
[ZOPT_CHSIZE
], "size",
331 "set compression chunksize, Mib");
336 char *corefmt
= NULL
;
337 char *corefname
= NULL
;
342 while ((c
= getopt(argc
, argv
, "vdsxCFZ:o:c:b:t:")) != -1) {
348 case 's': /* FreeBSD compat: stop while gathering */
351 case 'o': /* Linux (& SunOS) compat: basic name */
352 corefname
= strdup(optarg
);
354 case 'c': /* FreeBSD compat: basic name */
355 /* (also allows pattern-based naming) */
356 corefmt
= strdup(optarg
);
359 case 'b': /* bound the size of the core file */
360 if (NULL
!= optarg
) {
361 off_t bsize
= atoi(optarg
) * oneM
;
363 options
.sizebound
= bsize
;
365 errx(EX_USAGE
, "invalid bound");
367 errx(EX_USAGE
, "no bound specified");
369 case 'v': /* verbose output */
374 * dev and debugging help
377 case 'd': /* debugging */
384 * Remaining options are experimental and/or
385 * affect the content of the core file
387 case 'x': /* write extended format (small) core files */
389 options
.chunksize
= DEFAULT_COMPRESSION_CHUNKSIZE
;
391 case 'C': /* forcibly corpsify rather than suspend */
394 case 'Z': /* control compression options */
396 * Only LZFSE and LZ4 seem practical.
397 * (Default to LZ4 compression when the
398 * process is suspended, LZFSE when corpsed?)
400 if (0 == options
.extended
)
401 errx(EX_USAGE
, "illegal flag combination");
406 switch (getsubopt(&sopts
, zoptkeys
, &value
)) {
407 case ZOPT_ALG
: /* change the algorithm */
409 errx(EX_USAGE
, "missing algorithm for "
412 if (strcmp(value
, "lz4") == 0)
413 options
.calgorithm
= COMPRESSION_LZ4
;
414 else if (strcmp(value
, "zlib") == 0)
415 options
.calgorithm
= COMPRESSION_ZLIB
;
416 else if (strcmp(value
, "lzma") == 0)
417 options
.calgorithm
= COMPRESSION_LZMA
;
418 else if (strcmp(value
, "lzfse") == 0)
419 options
.calgorithm
= COMPRESSION_LZFSE
;
421 errx(EX_USAGE
, "unknown algorithm '%s'"
423 value
, zoptkeys
[ZOPT_ALG
]);
425 case ZOPT_CHSIZE
: /* set the chunksize */
427 errx(EX_USAGE
, "no value specified for "
429 zoptkeys
[ZOPT_CHSIZE
]);
430 if ((chsize
= atoi(value
)) < 1)
431 errx(EX_USAGE
, "chunksize %lu too small", chsize
);
432 if (chsize
> (LARGEST_CHUNKSIZE
/ oneM
))
433 errx(EX_USAGE
, "chunksize %lu too large", chsize
);
434 options
.chunksize
= chsize
* oneM
;
438 errx(EX_USAGE
, "illegal suboption '%s'",
441 errx(EX_USAGE
, "missing suboption");
445 case 't': /* set the F_NOCACHE threshold */
446 if (NULL
!= optarg
) {
447 size_t tsize
= atoi(optarg
) * oneK
;
449 options
.ncthresh
= tsize
;
451 errx(EX_USAGE
, "invalid nc threshold");
453 errx(EX_USAGE
, "no threshold specified");
455 case 'F': /* maximize filerefs */
456 options
.allfilerefs
++;
459 errx(EX_USAGE
, "unknown flag");
464 errx(EX_USAGE
, "no pid specified");
466 errx(EX_USAGE
, "too many arguments");
469 if (NULL
!= corefname
&& NULL
!= corefmt
)
470 errx(EX_USAGE
, "specify only one of -o and -c");
471 if (!opt
->extended
&& opt
->allfilerefs
)
472 errx(EX_USAGE
, "unknown flag");
476 if (opt
->ncthresh
< ((vm_offset_t
)1 << pageshift_host
))
477 errx(EX_USAGE
, "threshold %lu less than host pagesize", opt
->ncthresh
);
479 const pid_t apid
= atoi(argv
[optind
]);
481 mach_port_t corpse
= MACH_PORT_NULL
;
485 /* look for corpse - dead or alive */
486 mach_port_array_t parray
= NULL
;
487 mach_msg_type_number_t pcount
= 0;
488 ret
= mach_ports_lookup(mach_task_self(), &parray
, &pcount
);
489 if (KERN_SUCCESS
== ret
&& pcount
> 0) {
490 task_t tcorpse
= parray
[0];
491 mig_deallocate((vm_address_t
)parray
, pcount
* sizeof (*parray
));
493 ret
= pid_for_task(tcorpse
, &tpid
);
494 if (KERN_SUCCESS
== ret
&& tpid
!= getpid()) {
501 if (pid
< 1 || getpid() == pid
)
502 errx(EX_DATAERR
, "invalid pid: %d", pid
);
504 if (0 == apid
&& MACH_PORT_NULL
== corpse
)
505 errx(EX_DATAERR
, "missing or bad corpse from parent");
507 task_t task
= TASK_NULL
;
508 const struct proc_bsdinfo
*pbi
= NULL
;
510 if (-1 != kill(pid
, 0)) {
511 /* process or corpse that responds to signals */
512 pbi
= get_bsdinfo(pid
);
514 errx(EX_OSERR
, "cannot get process info for %d", pid
);
516 /* make our data model match the data model of the target */
517 if (-1 == reexec_to_match_lp64ness(pbi
->pbi_flags
& PROC_FLAG_LP64
))
518 errc(1, errno
, "cannot match data model of %d", pid
);
520 if (!proc_same_data_model(pbi
))
521 errx(EX_OSERR
, "cannot match data model of %d", pid
);
523 if (pbi
->pbi_ruid
!= pbi
->pbi_svuid
||
524 pbi
->pbi_rgid
!= pbi
->pbi_svgid
)
525 errx(EX_NOPERM
, "pid %d - not dumping a set-id process", pid
);
527 if (NULL
== corefname
)
528 corefname
= make_gcore_path(&corefmt
, pbi
->pbi_pid
, pbi
->pbi_uid
, pbi
->pbi_name
[0] ? pbi
->pbi_name
: pbi
->pbi_comm
);
530 if (MACH_PORT_NULL
== corpse
) {
531 ret
= task_for_pid(mach_task_self(), pid
, &task
);
532 if (KERN_SUCCESS
!= ret
) {
533 if (KERN_FAILURE
== ret
)
534 errx(EX_NOPERM
, "insufficient privilege");
536 errx(EX_NOPERM
, "task_for_pid: %s", mach_error_string(ret
));
541 * Have either the corpse port or the task port so adopt the
542 * credentials of the target process, *before* opening the
543 * core file, and analyzing the address space.
545 * If we are unable to match the target credentials, bail out.
547 change_credentials(pbi
->pbi_uid
, pbi
->pbi_gid
);
549 if (MACH_PORT_NULL
== corpse
) {
552 errc(EX_DATAERR
, errno
, "no process with pid %d", pid
);
554 errc(EX_DATAERR
, errno
, "pid %d", pid
);
557 /* a corpse with no live process backing it */
559 assert(0 == apid
&& TASK_NULL
== task
);
561 task_flags_info_data_t tfid
;
562 mach_msg_type_number_t count
= TASK_FLAGS_INFO_COUNT
;
563 ret
= task_info(corpse
, TASK_FLAGS_INFO
, (task_info_t
)&tfid
, &count
);
564 if (KERN_SUCCESS
!= ret
)
565 err_mach(ret
, NULL
, "task_info");
566 if (!task_same_data_model(&tfid
))
567 errx(EX_OSERR
, "data model mismatch for target corpse");
569 if (opt
->suspend
|| opt
->corpsify
)
570 errx(EX_USAGE
, "cannot use -s or -C option with a corpse");
572 errx(EX_USAGE
, "cannot use -c with a corpse");
573 if (NULL
== corefname
)
574 corefname
= make_gcore_path(&corefmt
, pid
, -2, "corpse");
577 * Only have a corpse, thus no process credentials.
580 change_credentials(-2, -2);
584 char *coretname
= NULL
;
585 const int fd
= openout(corefname
, &coretname
, &cst
);
588 printf("Dumping core ");
589 if (OPTIONS_DEBUG(opt
, 1)) {
590 printf("(%s", opt
->extended
? "extended" : "vanilla");
591 if (0 != opt
->sizebound
) {
593 printf(", <= %s", str_hsize(hstr
, opt
->sizebound
));
597 printf("for pid %d to %s\n", pid
, corefname
);
602 if (MACH_PORT_NULL
== corpse
) {
603 assert(TASK_NULL
!= task
);
606 * The "traditional" way to capture a consistent core dump is to
607 * suspend the process while examining it and writing it out.
608 * Yet suspending a large process for a long time can have
609 * unpleasant side-effects. Alternatively dumping from the live
610 * process can lead to an inconsistent state in the core file.
612 * Instead we can ask xnu to create a 'corpse' - the process is transiently
613 * suspended while a COW snapshot of the address space is constructed
614 * in the kernel and dump from that. This vastly reduces the suspend
615 * time, but it is more resource hungry and thus may fail.
617 * The -s flag (opt->suspend) causes a task_suspend/task_resume
618 * The -C flag (opt->corpse) causes a corpse be taken, exiting if that fails.
619 * Both flags can be specified, in which case corpse errors are ignored.
621 * With no flags, we imitate traditional behavior though more
622 * efficiently: we try to take a corpse-based dump, in the event that
623 * fails, dump the live process.
626 int trycorpse
= 1; /* default: use corpses */
627 int badcorpse_is_fatal
= 1; /* default: failure to create is an error */
629 if (!opt
->suspend
&& !opt
->corpsify
) {
630 /* try a corpse dump, else dump the live process */
631 badcorpse_is_fatal
= 0;
632 } else if (opt
->suspend
) {
633 trycorpse
= opt
->corpsify
;
634 /* if suspended anyway, ignore corpse-creation errors */
635 badcorpse_is_fatal
= 0;
643 * Create a corpse from the image before dumping it
645 ret
= task_generate_corpse(task
, &corpse
);
648 if (OPTIONS_DEBUG(opt
, 1))
649 printf("Corpse generated on port %x, task %x\n",
651 ecode
= coredump(corpse
, fd
, pbi
);
652 mach_port_deallocate(mach_task_self(), corpse
);
655 if (badcorpse_is_fatal
|| opt
->verbose
) {
656 warnx("failed to snapshot pid %d: %s\n",
657 pid
, mach_error_string(ret
));
658 if (badcorpse_is_fatal
) {
659 ecode
= KERN_RESOURCE_SHORTAGE
== ret
? EX_TEMPFAIL
: EX_OSERR
;
663 ecode
= coredump(task
, fd
, pbi
);
668 * Examine the task directly
670 ecode
= coredump(task
, fd
, pbi
);
678 * Handed a corpse by our parent.
680 ecode
= coredump(corpse
, fd
, pbi
);
681 mach_port_deallocate(mach_task_self(), corpse
);
684 ecode
= closeout(fd
, ecode
, corefname
, coretname
, &cst
);
686 errx(ecode
, "failed to dump core for pid %d", pid
);
690 #if defined(CONFIG_GCORE_FREF) || defined(CONFIG_GCORE_MAP) || defined(GCONFIG_GCORE_CONV)
693 getcorefd(const char *infile
)
695 const int fd
= open(infile
, O_RDONLY
| O_CLOEXEC
);
697 errc(EX_DATAERR
, errno
, "cannot open %s", infile
);
699 struct mach_header mh
;
700 if (-1 == pread(fd
, &mh
, sizeof (mh
), 0))
701 errc(EX_OSERR
, errno
, "cannot read mach header from %s", infile
);
703 static const char cant_match_data_model
[] = "cannot match the data model of %s";
705 if (-1 == reexec_to_match_lp64ness(MH_MAGIC_64
== mh
.magic
))
706 errc(1, errno
, cant_match_data_model
, infile
);
708 if (NATIVE_MH_MAGIC
!= mh
.magic
)
709 errx(EX_OSERR
, cant_match_data_model
, infile
);
710 if (MH_CORE
!= mh
.filetype
)
711 errx(EX_DATAERR
, "%s is not a mach core file", infile
);
717 #ifdef CONFIG_GCORE_FREF
720 gcore_fref_main(int argc
, char *argv
[])
722 err_set_exit_b(^(int eval
) {
723 if (EX_USAGE
== eval
) {
724 fprintf(stderr
, "usage:\t%s %s corefile\n", pgm
, argv
[1]);
728 errx(EX_USAGE
, "no input corefile");
730 errx(EX_USAGE
, "too many arguments");
732 return gcore_fref(getcorefd(argv
[2]));
735 #endif /* CONFIG_GCORE_FREF */
737 #ifdef CONFIG_GCORE_MAP
740 gcore_map_main(int argc
, char *argv
[])
742 err_set_exit_b(^(int eval
) {
743 if (EX_USAGE
== eval
) {
744 fprintf(stderr
, "usage:\t%s %s corefile\n", pgm
, argv
[1]);
748 errx(EX_USAGE
, "no input corefile");
750 errx(EX_USAGE
, "too many arguments");
752 return gcore_map(getcorefd(argv
[2]));
757 #ifdef CONFIG_GCORE_CONV
760 gcore_conv_main(int argc
, char *argv
[])
762 err_set_exit_b(^(int eval
) {
763 if (EX_USAGE
== eval
)
765 "usage:\t%s %s [-v] [-L searchpath] [-z] incore outcore\n", pgm
, argv
[1]);
768 char *searchpath
= NULL
;
773 while ((c
= getopt(argc
, argv
, "dzvL:")) != -1) {
776 * likely documented options
779 searchpath
= strdup(optarg
);
789 * dev and debugging help
799 errx(EX_USAGE
, "unknown flag");
803 errx(EX_USAGE
, "no input corefile");
804 if (optind
== argc
- 1)
805 errx(EX_USAGE
, "no output corefile");
806 if (optind
< argc
- 2)
807 errx(EX_USAGE
, "too many arguments");
809 const char *incore
= argv
[optind
];
810 char *corefname
= strdup(argv
[optind
+1]);
816 if (opt
->ncthresh
< ((vm_offset_t
)1 << pageshift_host
))
817 errx(EX_USAGE
, "threshold %lu less than host pagesize", opt
->ncthresh
);
819 const int infd
= getcorefd(incore
);
821 char *coretname
= NULL
;
822 const int fd
= openout(corefname
, &coretname
, &cst
);
823 int ecode
= gcore_conv(infd
, searchpath
, zf
, fd
);
824 ecode
= closeout(fd
, ecode
, corefname
, coretname
, &cst
);
826 errx(ecode
, "failed to convert core file successfully");
832 main(int argc
, char *argv
[])
834 if (NULL
== (pgm
= strrchr(*argv
, '/')))
838 #ifdef CONFIG_GCORE_FREF
839 if (argc
> 1 && 0 == strcmp(argv
[1], "fref")) {
840 return gcore_fref_main(argc
, argv
);
843 #ifdef CONFIG_GCORE_MAP
844 if (argc
> 1 && 0 == strcmp(argv
[1], "map")) {
845 return gcore_map_main(argc
, argv
);
848 #ifdef CONFIG_GCORE_CONV
849 if (argc
> 1 && 0 == strcmp(argv
[1], "conv")) {
850 return gcore_conv_main(argc
, argv
);
853 return gcore_main(argc
, argv
);