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
,
301 gcore_main(int argc
, char *const *argv
)
304 #define ZOPT_CHSIZE (ZOPT_ALG + 1)
306 static char *const zoptkeys
[] = {
307 [ZOPT_ALG
] = "algorithm",
308 [ZOPT_CHSIZE
] = "chunksize",
312 err_set_exit_b(^(int eval
) {
313 if (EX_USAGE
== eval
) {
315 "usage:\t%s [-s] [-v] [[-o file] | [-c pathfmt ]] [-b size] "
321 "[-Z compression-options] "
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");
337 char *corefmt
= NULL
;
338 char *corefname
= NULL
;
343 while ((c
= getopt(argc
, argv
, "vdsxCFZ:o:c:b:t:")) != -1) {
349 case 's': /* FreeBSD compat: stop while gathering */
352 case 'o': /* Linux (& SunOS) compat: basic name */
353 corefname
= strdup(optarg
);
355 case 'c': /* FreeBSD compat: basic name */
356 /* (also allows pattern-based naming) */
357 corefmt
= strdup(optarg
);
360 case 'b': /* bound the size of the core file */
361 if (NULL
!= optarg
) {
362 off_t bsize
= atoi(optarg
) * oneM
;
364 options
.sizebound
= bsize
;
366 errx(EX_USAGE
, "invalid bound");
368 errx(EX_USAGE
, "no bound specified");
370 case 'v': /* verbose output */
375 * dev and debugging help
378 case 'd': /* debugging */
385 * Remaining options are experimental and/or
386 * affect the content of the core file
388 case 'x': /* write extended format (small) core files */
390 options
.chunksize
= DEFAULT_COMPRESSION_CHUNKSIZE
;
392 case 'C': /* forcibly corpsify rather than suspend */
395 case 'Z': /* control compression options */
397 * Only LZFSE and LZ4 seem practical.
398 * (Default to LZ4 compression when the
399 * process is suspended, LZFSE when corpsed?)
401 if (0 == options
.extended
)
402 errx(EX_USAGE
, "illegal flag combination");
407 switch (getsubopt(&sopts
, zoptkeys
, &value
)) {
408 case ZOPT_ALG
: /* change the algorithm */
410 errx(EX_USAGE
, "missing algorithm for "
413 if (strcmp(value
, "lz4") == 0)
414 options
.calgorithm
= COMPRESSION_LZ4
;
415 else if (strcmp(value
, "zlib") == 0)
416 options
.calgorithm
= COMPRESSION_ZLIB
;
417 else if (strcmp(value
, "lzma") == 0)
418 options
.calgorithm
= COMPRESSION_LZMA
;
419 else if (strcmp(value
, "lzfse") == 0)
420 options
.calgorithm
= COMPRESSION_LZFSE
;
422 errx(EX_USAGE
, "unknown algorithm '%s'"
424 value
, zoptkeys
[ZOPT_ALG
]);
426 case ZOPT_CHSIZE
: /* set the chunksize */
428 errx(EX_USAGE
, "no value specified for "
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
;
439 errx(EX_USAGE
, "illegal suboption '%s'",
442 errx(EX_USAGE
, "missing suboption");
446 case 't': /* set the F_NOCACHE threshold */
447 if (NULL
!= optarg
) {
448 size_t tsize
= atoi(optarg
) * oneK
;
450 options
.ncthresh
= tsize
;
452 errx(EX_USAGE
, "invalid nc threshold");
454 errx(EX_USAGE
, "no threshold specified");
456 case 'F': /* maximize filerefs */
457 options
.allfilerefs
++;
460 errx(EX_USAGE
, "unknown flag");
465 errx(EX_USAGE
, "no pid specified");
467 errx(EX_USAGE
, "too many arguments");
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");
477 if (opt
->ncthresh
< ((vm_offset_t
)1 << pageshift_host
))
478 errx(EX_USAGE
, "threshold %lu less than host pagesize", opt
->ncthresh
);
480 const pid_t apid
= atoi(argv
[optind
]);
482 mach_port_t corpse
= MACH_PORT_NULL
;
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
));
494 ret
= pid_for_task(tcorpse
, &tpid
);
495 if (KERN_SUCCESS
== ret
&& tpid
!= getpid()) {
502 if (pid
< 1 || getpid() == pid
)
503 errx(EX_DATAERR
, "invalid pid: %d", pid
);
505 if (0 == apid
&& MACH_PORT_NULL
== corpse
)
506 errx(EX_DATAERR
, "missing or bad corpse from parent");
508 task_t task
= TASK_NULL
;
509 const struct proc_bsdinfo
*pbi
= NULL
;
510 const int rc
= kill(pid
, 0);
513 /* process or corpse that may respond to signals */
514 pbi
= get_bsdinfo(pid
);
517 if (rc
== 0 && pbi
!= NULL
) {
518 /* process or corpse that responds to signals */
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
);
524 if (!proc_same_data_model(pbi
))
525 errx(EX_OSERR
, "cannot match data model of %d", pid
);
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
);
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
);
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");
540 errx(EX_NOPERM
, "task_for_pid: %s", mach_error_string(ret
));
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.
549 * If we are unable to match the target credentials, bail out.
551 change_credentials(pbi
->pbi_uid
, pbi
->pbi_gid
);
553 if (MACH_PORT_NULL
== corpse
) {
555 errx(EX_OSERR
, "cannot get process info for %d", pid
);
559 errc(EX_DATAERR
, errno
, "no process with pid %d", pid
);
561 errc(EX_DATAERR
, errno
, "pid %d", pid
);
564 /* a corpse with no live process backing it */
566 assert(0 == apid
&& TASK_NULL
== task
);
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");
576 if (opt
->suspend
|| opt
->corpsify
)
577 errx(EX_USAGE
, "cannot use -s or -C option with a corpse");
579 errx(EX_USAGE
, "cannot use -c with a corpse");
580 if (NULL
== corefname
)
581 corefname
= make_gcore_path(&corefmt
, pid
, -2, "corpse");
584 * Only have a corpse, thus no process credentials.
587 change_credentials(-2, -2);
591 char *coretname
= NULL
;
592 const int fd
= openout(corefname
, &coretname
, &cst
);
595 printf("Dumping core ");
596 if (OPTIONS_DEBUG(opt
, 1)) {
597 printf("(%s", opt
->extended
? "extended" : "vanilla");
598 if (0 != opt
->sizebound
) {
600 printf(", <= %s", str_hsize(hstr
, opt
->sizebound
));
604 printf("for pid %d to %s\n", pid
, corefname
);
609 if (MACH_PORT_NULL
== corpse
) {
610 assert(TASK_NULL
!= task
);
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.
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.
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.
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.
633 int trycorpse
= 1; /* default: use corpses */
634 int badcorpse_is_fatal
= 1; /* default: failure to create is an error */
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;
650 * Create a corpse from the image before dumping it
652 ret
= task_generate_corpse(task
, &corpse
);
655 if (OPTIONS_DEBUG(opt
, 1))
656 printf("Corpse generated on port %x, task %x\n",
658 ecode
= coredump(corpse
, fd
, pbi
);
659 mach_port_deallocate(mach_task_self(), corpse
);
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
;
670 ecode
= coredump(task
, fd
, pbi
);
675 * Examine the task directly
677 ecode
= coredump(task
, fd
, pbi
);
685 * Handed a corpse by our parent.
687 ecode
= coredump(corpse
, fd
, pbi
);
688 mach_port_deallocate(mach_task_self(), corpse
);
691 ecode
= closeout(fd
, ecode
, corefname
, coretname
, &cst
);
693 errx(ecode
, "failed to dump core for pid %d", pid
);
697 #if defined(CONFIG_GCORE_FREF) || defined(CONFIG_GCORE_MAP) || defined(GCONFIG_GCORE_CONV)
700 getcorefd(const char *infile
)
702 const int fd
= open(infile
, O_RDONLY
| O_CLOEXEC
);
704 errc(EX_DATAERR
, errno
, "cannot open %s", infile
);
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
);
710 static const char cant_match_data_model
[] = "cannot match the data model of %s";
712 if (-1 == reexec_to_match_lp64ness(MH_MAGIC_64
== mh
.magic
))
713 errc(1, errno
, cant_match_data_model
, infile
);
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
);
724 #ifdef CONFIG_GCORE_FREF
727 gcore_fref_main(int argc
, char *argv
[])
729 err_set_exit_b(^(int eval
) {
730 if (EX_USAGE
== eval
) {
731 fprintf(stderr
, "usage:\t%s %s corefile\n", pgm
, argv
[1]);
735 errx(EX_USAGE
, "no input corefile");
737 errx(EX_USAGE
, "too many arguments");
739 return gcore_fref(getcorefd(argv
[2]));
742 #endif /* CONFIG_GCORE_FREF */
744 #ifdef CONFIG_GCORE_MAP
747 gcore_map_main(int argc
, char *argv
[])
749 err_set_exit_b(^(int eval
) {
750 if (EX_USAGE
== eval
) {
751 fprintf(stderr
, "usage:\t%s %s corefile\n", pgm
, argv
[1]);
755 errx(EX_USAGE
, "no input corefile");
757 errx(EX_USAGE
, "too many arguments");
759 return gcore_map(getcorefd(argv
[2]));
764 #ifdef CONFIG_GCORE_CONV
767 gcore_conv_main(int argc
, char *argv
[])
769 err_set_exit_b(^(int eval
) {
770 if (EX_USAGE
== eval
)
772 "usage:\t%s %s [-v] [-L searchpath] [-z] [-s] incore outcore\n", pgm
, argv
[1]);
775 char *searchpath
= NULL
;
780 while ((c
= getopt(argc
, argv
, "dzvL:s")) != -1) {
783 * likely documented options
786 searchpath
= strdup(optarg
);
795 options
.dsymforuuid
++;
798 * dev and debugging help
808 errx(EX_USAGE
, "unknown flag");
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");
818 const char *incore
= argv
[optind
];
819 char *corefname
= strdup(argv
[optind
+1]);
825 if (opt
->ncthresh
< ((vm_offset_t
)1 << pageshift_host
))
826 errx(EX_USAGE
, "threshold %lu less than host pagesize", opt
->ncthresh
);
828 const int infd
= getcorefd(incore
);
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
);
835 errx(ecode
, "failed to convert core file successfully");
841 main(int argc
, char *argv
[])
843 if (NULL
== (pgm
= strrchr(*argv
, '/')))
847 #ifdef CONFIG_GCORE_FREF
848 if (argc
> 1 && 0 == strcmp(argv
[1], "fref")) {
849 return gcore_fref_main(argc
, argv
);
852 #ifdef CONFIG_GCORE_MAP
853 if (argc
> 1 && 0 == strcmp(argv
[1], "map")) {
854 return gcore_map_main(argc
, argv
);
857 #ifdef CONFIG_GCORE_CONV
858 if (argc
> 1 && 0 == strcmp(argv
[1], "conv")) {
859 return gcore_conv_main(argc
, argv
);
862 return gcore_main(argc
, argv
);