2 * Copyright (c) 2016 Apple Inc. All rights reserved.
10 #include "dyld_shared_cache.h"
13 #include <sys/types.h>
16 #include <sys/queue.h>
17 #include <sys/param.h>
18 #include <mach-o/fat.h>
19 #include <uuid/uuid.h>
29 #if defined(CONFIG_GCORE_MAP) || defined(CONFIG_GCORE_CONV) || defined(CONFIG_GCORE_FREF)
32 mmapfile(int fd
, off_t off
, off_t
*filesize
)
35 if (-1 == fstat(fd
, &st
))
36 errc(EX_OSERR
, errno
, "can't stat input file");
38 const size_t size
= (size_t)(st
.st_size
- off
);
39 if ((off_t
)size
!= (st
.st_size
- off
))
40 errc(EX_OSERR
, EOVERFLOW
, "input file too large?");
42 const void *addr
= mmap(0, size
, PROT_READ
, MAP_PRIVATE
, fd
, off
);
43 if ((void *)-1 == addr
)
44 errc(EX_OSERR
, errno
, "can't mmap input file");
45 *filesize
= st
.st_size
;
51 const native_mach_header_t
*mh
,
52 void (^coreinfo
)(const struct proto_coreinfo_command
*),
53 void (^frefdata
)(const struct proto_fileref_command
*),
54 void (^coredata
)(const struct proto_coredata_command
*),
55 void (^segdata
)(const native_segment_command_t
*),
56 void (^thrdata
)(const struct thread_command
*))
58 const struct load_command
*lc
= (const void *)(mh
+ 1);
59 for (unsigned i
= 0; i
< mh
->ncmds
; i
++) {
61 case proto_LC_COREINFO
:
63 coreinfo((const void *)lc
);
65 case proto_LC_FILEREF
:
67 frefdata((const void *)lc
);
69 case proto_LC_COREDATA
:
71 coredata((const void *)lc
);
73 case NATIVE_LC_SEGMENT
:
75 segdata((const void *)lc
);
79 thrdata((const void *)lc
);
84 if (NULL
== (lc
= next_lc(lc
)))
91 #ifdef CONFIG_GCORE_FREF
97 const void *corebase
= mmapfile(fd
, 0, &filesize
);
101 STAILQ_ENTRY(flist
) f_linkage
;
103 unsigned long f_nmhash
;
105 STAILQ_HEAD(flisthead
, flist
) __flh
, *flh
= &__flh
;
108 walkcore(corebase
, NULL
, ^(const struct proto_fileref_command
*fc
) {
109 const char *nm
= fc
->filename
.offset
+ (const char *)fc
;
110 const unsigned long nmhash
= simple_namehash(nm
);
112 STAILQ_FOREACH(f
, flh
, f_linkage
) {
113 if (nmhash
== f
->f_nmhash
&& 0 == strcmp(f
->f_nm
, nm
))
114 return; /* skip duplicates */
116 struct flist
*nf
= calloc(1, sizeof (*nf
));
118 nf
->f_nmhash
= nmhash
;
119 STAILQ_INSERT_TAIL(flh
, nf
, f_linkage
);
120 }, NULL
, NULL
, NULL
);
122 struct flist
*f
, *tf
;
123 STAILQ_FOREACH_SAFE(f
, flh
, f_linkage
, tf
) {
124 printf("%s\n", f
->f_nm
);
129 munmap((void *)corebase
, (size_t)filesize
);
133 #endif /* CONFIG_GCORE_FREF */
135 #ifdef CONFIG_GCORE_MAP
138 * A pale imitation of vmmap, but for core files
144 const void *corebase
= mmapfile(fd
, 0, &filesize
);
146 __block
int coreversion
= 0;
148 walkcore(corebase
, ^(const struct proto_coreinfo_command
*ci
) {
149 coreversion
= ci
->version
;
150 }, NULL
, NULL
, NULL
, NULL
);
152 if (0 == coreversion
) {
153 const char titlfmt
[] = "%16s-%-16s [%7s] %3s/%3s\n";
154 const char *segcfmt
= "%016llx-%016llx [%7s] %3s/%3s\n";
156 printf(titlfmt
, "start ", " end", "vsize", "prt", "max");
157 walkcore(corebase
, NULL
, NULL
, NULL
, ^(const native_segment_command_t
*sc
) {
159 printf(segcfmt
, (mach_vm_offset_t
)sc
->vmaddr
, (mach_vm_offset_t
)sc
->vmaddr
+ sc
->vmsize
, str_hsize(vstr
, sc
->vmsize
), str_prot(sc
->initprot
), str_prot(sc
->maxprot
));
162 const char titlfmt
[] = "%-23s %16s-%-16s [%7s] %3s/%3s %6s %4s %-14s\n";
163 const char *freffmt
= "%-23s %016llx-%016llx [%7s] %3s/%3s %6s %4s %-14s @%lld\n";
164 const char *datafmt
= "%-23s %016llx-%016llx [%7s] %3s/%3s %6s %4s %-14s\n";
166 printf(titlfmt
, "region type", "start ", " end", "vsize", "prt", "max", "shrmod", "purge", "region detail");
167 walkcore(corebase
, NULL
, ^(const struct proto_fileref_command
*fc
) {
168 const char *nm
= fc
->filename
.offset
+ (const char *)fc
;
171 printf(freffmt
, str_tag(tstr
, fc
->tag
, fc
->share_mode
, fc
->prot
, fc
->extp
),
172 fc
->vmaddr
, fc
->vmaddr
+ fc
->vmsize
,
173 str_hsize(vstr
, fc
->vmsize
), str_prot(fc
->prot
),
174 str_prot(fc
->maxprot
), str_shared(fc
->share_mode
),
175 str_purgable(fc
->purgable
, fc
->share_mode
), nm
, fc
->fileoff
);
176 }, ^(const struct proto_coredata_command
*cc
) {
179 printf(datafmt
, str_tag(tstr
, cc
->tag
, cc
->share_mode
, cc
->prot
, cc
->extp
),
180 cc
->vmaddr
, cc
->vmaddr
+ cc
->vmsize
,
181 str_hsize(vstr
, cc
->vmsize
), str_prot(cc
->prot
),
182 str_prot(cc
->maxprot
), str_shared(cc
->share_mode
),
183 str_purgable(cc
->purgable
, cc
->share_mode
),
184 cc
->vmsize
&& 0 == cc
->filesize
? "(zfod)" : "");
185 }, ^(const native_segment_command_t
*sc
) {
187 printf(datafmt
, "", (mach_vm_offset_t
)sc
->vmaddr
,
188 (mach_vm_offset_t
)sc
->vmaddr
+ sc
->vmsize
,
189 str_hsize(vstr
, sc
->vmsize
), str_prot(sc
->initprot
),
190 str_prot(sc
->maxprot
), "", "",
191 sc
->vmsize
&& 0 == sc
->filesize
? "(zfod)" : "");
195 munmap((void *)corebase
, (size_t)filesize
);
201 #ifdef CONFIG_GCORE_CONV
204 * Convert an input core file into an "old" format core file
205 * (a) convert all fileref segments into regular segments
206 * (b) uncompress anything we find compressed.
207 * This should be equivalent to a copy for an "old" format core file.
211 machocmp(const native_mach_header_t
*tmh
, const native_mach_header_t
*mh
, const struct proto_fileref_command
*fr
)
213 if (tmh
->magic
== mh
->magic
) {
214 const struct load_command
*lc
= (const void *)(tmh
+ 1);
215 for (unsigned i
= 0; i
< tmh
->ncmds
; i
++) {
216 if (LC_UUID
== lc
->cmd
&& lc
->cmdsize
>= sizeof (struct uuid_command
)) {
217 const struct uuid_command
*uc
= (const void *)lc
;
218 return uuid_compare(uc
->uuid
, fr
->id
);
220 if (NULL
== (lc
= next_lc(lc
)))
228 fat_machocmp(const struct fat_header
*fh
, const native_mach_header_t
*mh
, const struct proto_fileref_command
*fr
, off_t
*reloff
)
230 const uint32_t (^get32
)(uint32_t);
232 if (FAT_MAGIC
== fh
->magic
) {
233 get32
= ^(uint32_t val
) {
237 get32
= ^(uint32_t val
) {
239 for (unsigned i
= 0; i
< sizeof (uint32_t); i
++)
240 ((uint8_t *)&result
)[i
] = ((uint8_t *)&val
)[3-i
];
245 assert(FAT_MAGIC
== get32(fh
->magic
));
246 assert(kFREF_ID_UUID
== FREF_ID_TYPE(fr
->flags
) && !uuid_is_null(fr
->id
));
248 const struct fat_arch
*fa
= (const struct fat_arch
*)(fh
+ 1);
249 uint32_t narch
= get32(fh
->nfat_arch
);
250 for (unsigned n
= 0; n
< narch
; n
++, fa
++) {
251 const native_mach_header_t
*tmh
= (const void *)(((const char *)fh
) + get32(fa
->offset
));
252 if (tmh
->magic
== mh
->magic
&& 0 == machocmp(tmh
, mh
, fr
)) {
253 *reloff
= get32(fa
->offset
);
266 static struct convstats
{
270 int64_t uncompressed
;
271 } cstat
, *cstats
= &cstat
;
274 * A fileref segment references a read-only file that contains pages from
275 * the image. The file may be a Mach binary or dylib identified with a uuid.
278 convert_fileref_with_file(const char *filename
, const native_mach_header_t
*inmh
, const struct proto_fileref_command
*infr
, const struct vm_range
*invr
, struct load_command
*lc
, struct output_info
*oi
)
280 assert(invr
->addr
== infr
->vmaddr
&& invr
->size
== infr
->vmsize
);
283 const int rfd
= open(filename
, O_RDONLY
);
284 if (-1 == rfd
|| -1 == fstat(rfd
, &st
)) {
285 warnc(errno
, "%s: open", filename
);
288 const size_t rlen
= (size_t)st
.st_size
;
289 void *raddr
= mmap(NULL
, rlen
, PROT_READ
, MAP_PRIVATE
, rfd
, 0);
290 if ((void *)-1 == raddr
) {
291 warnc(errno
, "%s: mmap", filename
);
297 off_t fatoff
= 0; /* for FAT objects */
298 int ecode
= EX_DATAERR
;
300 switch (FREF_ID_TYPE(infr
->flags
)) {
301 case kFREF_ID_UUID
: {
302 /* file should be a mach binary: check that uuid matches */
303 const uint32_t magic
= *(uint32_t *)raddr
;
307 if (0 == fat_machocmp(raddr
, inmh
, infr
, &fatoff
))
310 case NATIVE_MH_MAGIC
:
311 if (0 == machocmp(raddr
, inmh
, infr
))
316 * Maybe this is the shared cache?
319 if (get_uuid_from_shared_cache_mapping(raddr
, rlen
, uu
) && uuid_compare(uu
, infr
->id
) == 0)
326 case kFREF_ID_MTIMESPEC_LE
:
327 /* file should have the same mtime */
328 if (0 == memcmp(&st
.st_mtimespec
, infr
->id
, sizeof (infr
->id
)))
332 /* file has no uniquifier, copy it anyway */
338 warnx("%s doesn't match corefile content", filename
);
342 const off_t fileoff
= fatoff
+ infr
->fileoff
;
343 const void *start
= (const char *)raddr
+ fileoff
;
344 const size_t len
= (size_t)infr
->filesize
;
348 if (fileoff
+ (off_t
)infr
->filesize
> (off_t
)rlen
) {
350 * the file content needed (as described on machine with
351 * larger pagesize) extends beyond the end of the mapped
352 * file using our smaller pagesize. Zero pad it.
354 const size_t pagesize_host
= 1ul << pageshift_host
;
355 void *endaddr
= (caddr_t
)raddr
+ roundup(rlen
, pagesize_host
);
356 zlen
= (size_t)(fileoff
+ infr
->filesize
- rlen
);
357 zaddr
= mmap(endaddr
, zlen
, PROT_READ
, MAP_FIXED
| MAP_PRIVATE
| MAP_ANON
, -1, 0);
358 if ((void *)-1 == zaddr
) {
360 warnc(errno
, "cannot zero-pad %s mapping for %s", str_hsize(hstr
, zlen
),filename
);
366 if (-1 == madvise((void *)start
, len
, MADV_SEQUENTIAL
))
367 warnc(errno
, "%s: madvise", filename
);
369 const int error
= bounded_pwrite(oi
->oi_fd
, start
, len
, oi
->oi_foffset
, &oi
->oi_nocache
, NULL
);
372 if (-1 == munmap(zaddr
, zlen
))
373 warnc(errno
, "%s: munmap zero pad", filename
);
375 if (-1 == munmap(raddr
, rlen
))
376 warnc(errno
, "%s: munmap", filename
);
378 warnc(error
, "while copying %s to core file", filename
);
382 const struct file_range fr
= {
383 .off
= oi
->oi_foffset
,
384 .size
= infr
->filesize
,
386 make_native_segment_command(lc
, invr
, &fr
, infr
->maxprot
, infr
->prot
);
387 oi
->oi_foffset
+= fr
.size
;
388 cstats
->added
+= infr
->filesize
;
393 * bind the file reference into the output core file.
394 * filename optionally prefixed with names from a ':'-separated PATH variable
397 convert_fileref(const char *path
, bool zf
, const native_mach_header_t
*inmh
, const struct proto_fileref_command
*infr
, struct load_command
*lc
, struct output_info
*oi
)
399 const char *nm
= infr
->filename
.offset
+ (const char *)infr
;
401 const struct vm_range invr
= {
402 .addr
= infr
->vmaddr
,
403 .size
= infr
->vmsize
,
408 printvr(&invr
, "adding %s from '%s'",
409 str_hsize(hstr
, (off_t
)infr
->filesize
), nm
);
410 switch (FREF_ID_TYPE(infr
->flags
)) {
414 uuid_unparse_lower(infr
->id
, uustr
);
415 printf(" (%s)", uustr
);
417 case kFREF_ID_MTIMESPEC_LE
: {
420 char tbuf
[4 + 2 + 2 + 2 + 2 + 1 + 2 + 1]; /* touch -t */
421 memcpy(&mts
, &infr
->id
, sizeof (mts
));
422 localtime_r(&mts
.tv_sec
, &tm
);
423 strftime(tbuf
, sizeof (tbuf
), "%Y%m%d%H%M.%S", &tm
);
424 printf(" (%s)", tbuf
);
430 const size_t pathsize
= path
? strlen(path
) : 0;
431 int ecode
= EX_DATAERR
;
433 ecode
= convert_fileref_with_file(nm
, inmh
, infr
, &invr
, lc
, oi
);
435 /* search the : separated path (-L) for possible matches */
436 char *pathcopy
= strdup(path
);
437 char *searchpath
= pathcopy
;
440 while ((token
= strsep(&searchpath
, ":")) != NULL
) {
441 const size_t buflen
= strlen(token
) + 1 + strlen(nm
) + 1;
442 char *buf
= malloc(buflen
);
443 snprintf(buf
, buflen
, "%s%s%s", token
, '/' == nm
[0] ? "" : "/", nm
);
445 printf("\tTrying '%s'", buf
);
446 if (0 == access(buf
, R_OK
)) {
449 ecode
= convert_fileref_with_file(buf
, inmh
, infr
, &invr
, lc
, oi
);
454 } else if (opt
->verbose
)
456 0 == access(buf
, F_OK
) ? "Unreadable" : "Not present");
462 if (0 != ecode
&& zf
) {
464 * Failed to find the file reference. If this was a fileref that uses
465 * a file metadata tagging method (e.g. mtime), allow the user to subsitute a
466 * zfod region: assumes that it's better to have something to debug
467 * vs. nothing. UUID-tagged filerefs are Mach-O tags, and are
468 * assumed to be never substitutable.
470 switch (FREF_ID_TYPE(infr
->flags
)) {
472 case kFREF_ID_MTIMESPEC_LE
: { // weak tagging, allow zfod substitution
473 const struct file_range outfr
= {
474 .off
= oi
->oi_foffset
,
478 printf("\tWARNING: no file matched. Missing content is now zfod\n");
480 printvr(&invr
, "WARNING: missing content (%s) now zfod\n", nm
);
481 make_native_segment_command(lc
, &invr
, &outfr
, infr
->maxprot
, infr
->prot
);
493 segment_uncompflags(unsigned algnum
, compression_algorithm
*ca
)
497 *ca
= COMPRESSION_LZ4
;
500 *ca
= COMPRESSION_ZLIB
;
503 *ca
= COMPRESSION_LZMA
;
506 *ca
= COMPRESSION_LZFSE
;
509 warnx("unknown compression flavor %d", algnum
);
516 convert_region(const void *inbase
, const struct vm_range
*invr
, const struct file_range
*infr
, const vm_prot_t prot
, const vm_prot_t maxprot
, const int flavor
, struct load_command
*lc
, struct output_info
*oi
)
521 void *input
= (const caddr_t
)inbase
+ F_OFF(infr
);
528 printvr(invr
, "copying %s\n", str_hsize(hstr
, F_SIZE(infr
)));
531 compression_algorithm ca
;
533 if (0 != (ecode
= segment_uncompflags(flavor
, &ca
)))
536 hsize_str_t hstr1
, hstr2
;
537 printvr(invr
, "uncompressing %s to %s\n",
538 str_hsize(hstr1
, F_SIZE(infr
)), str_hsize(hstr2
, V_SIZE(invr
)));
540 const size_t buflen
= V_SIZEOF(invr
);
541 buf
= malloc(buflen
);
542 const size_t dstsize
= compression_decode_buffer(buf
, buflen
, input
, (size_t)F_SIZE(infr
), NULL
, ca
);
543 if (buflen
!= dstsize
) {
544 warnx("failed to uncompress segment");
548 cstats
->compressed
+= F_SIZE(infr
);
550 const int error
= bounded_pwrite(oi
->oi_fd
, buf
, V_SIZEOF(invr
), oi
->oi_foffset
, &oi
->oi_nocache
, NULL
);
552 warnc(error
, "failed to write data to core file");
560 const struct file_range outfr
= {
561 .off
= oi
->oi_foffset
,
562 .size
= V_SIZE(invr
),
564 make_native_segment_command(lc
, invr
, &outfr
, maxprot
, prot
);
565 oi
->oi_foffset
+= outfr
.size
;
568 cstats
->copied
+= outfr
.size
;
570 cstats
->uncompressed
+= outfr
.size
;
574 printvr(invr
, "%s remains zfod\n", str_hsize(hstr
, V_SIZE(invr
)));
576 const struct file_range outfr
= {
577 .off
= oi
->oi_foffset
,
580 make_native_segment_command(lc
, invr
, &outfr
, maxprot
, prot
);
586 convert_coredata(const void *inbase
, const native_mach_header_t
*__unused inmh
, const struct proto_coredata_command
*cc
, struct load_command
*lc
, struct output_info
*oi
)
588 const struct vm_range vr
= {
592 const struct file_range fr
= {
594 .size
= cc
->filesize
,
596 return convert_region(inbase
, &vr
, &fr
, cc
->prot
, cc
->maxprot
, COMP_ALG_TYPE(cc
->flags
), lc
, oi
);
600 convert_segment(const void *inbase
, const native_mach_header_t
*__unused inmh
, const native_segment_command_t
*sc
, struct load_command
*lc
, struct output_info
*oi
)
602 const struct vm_range vr
= {
606 const struct file_range fr
= {
608 .size
= sc
->filesize
,
610 return convert_region(inbase
, &vr
, &fr
, sc
->initprot
, sc
->maxprot
, 0, lc
, oi
);
613 /* pass-through - content is all in the header */
616 convert_thread(struct thread_command
*dst
, const struct thread_command
*src
)
618 assert(LC_THREAD
== src
->cmd
);
619 memcpy(dst
, src
, src
->cmdsize
);
620 cstats
->copied
+= src
->cmdsize
;
625 gcore_conv(int infd
, const char *searchpath
, bool zf
, int fd
)
628 const void *corebase
= mmapfile(infd
, 0, &filesize
);
631 * Check to see if the input file is "sane" as far as we're concerned.
632 * XXX Note that this -won't- necessarily work for other ISAs than
635 const native_mach_header_t
*inmh
= corebase
;
636 validate_core_header(inmh
, filesize
);
639 * The sparse file may have created many more segments, but there's no
640 * attempt to change their numbers here. Just count all the segment
641 * types needed to figure out the size of the output file header.
643 * (Size assertions to be deleted once data structures stable!)
645 __block
size_t headersize
= sizeof (native_mach_header_t
);
646 __block
unsigned pageshift_target
= pageshift_host
;
648 walkcore(inmh
, ^(const struct proto_coreinfo_command
*ci
) {
649 assert(sizeof (*ci
) == ci
->cmdsize
);
651 printf("Converting version %d core file to pre-versioned format\n", ci
->version
);
652 if (0 < ci
->pageshift
&& ci
->pageshift
< 31)
653 pageshift_target
= ci
->pageshift
;
654 else if (CPU_TYPE_ARM64
== inmh
->cputype
)
655 pageshift_target
= 14; // compatibility hack, should go soon
656 }, ^(const struct proto_fileref_command
*__unused fc
) {
657 const char *nm
= fc
->filename
.offset
+ (const char *)fc
;
658 size_t nmlen
= strlen(nm
) + 1;
659 size_t cmdsize
= sizeof (*fc
) + roundup(nmlen
, sizeof (long));
660 assert(cmdsize
== fc
->cmdsize
);
662 headersize
+= sizeof (native_segment_command_t
);
663 }, ^(const struct proto_coredata_command
*__unused cc
) {
664 assert(sizeof (*cc
) == cc
->cmdsize
);
665 headersize
+= sizeof (native_segment_command_t
);
666 }, ^(const native_segment_command_t
*sc
) {
667 headersize
+= sc
->cmdsize
;
668 }, ^(const struct thread_command
*tc
) {
669 headersize
+= tc
->cmdsize
;
672 void *header
= calloc(1, headersize
);
674 errx(EX_OSERR
, "out of memory for header");
676 native_mach_header_t
*mh
= memcpy(header
, inmh
, sizeof (*mh
));
680 assert(0 < pageshift_target
&& pageshift_target
< 31);
681 const vm_offset_t pagesize_target
= ((vm_offset_t
)1 << pageshift_target
);
682 const vm_offset_t pagemask_target
= pagesize_target
- 1;
684 const struct load_command
*inlc
= (const void *)(inmh
+ 1);
685 struct load_command
*lc
= (void *)(mh
+ 1);
688 struct output_info oi
= {
690 .oi_foffset
= ((vm_offset_t
)headersize
+ pagemask_target
) & ~pagemask_target
,
694 for (unsigned i
= 0; i
< inmh
->ncmds
; i
++) {
696 case proto_LC_FILEREF
:
697 ecode
= convert_fileref(searchpath
, zf
, inmh
, (const void *)inlc
, lc
, &oi
);
699 case proto_LC_COREDATA
:
700 ecode
= convert_coredata(corebase
, inmh
, (const void *)inlc
, lc
, &oi
);
702 case NATIVE_LC_SEGMENT
:
703 ecode
= convert_segment(corebase
, inmh
, (const void *)inlc
, lc
, &oi
);
706 ecode
= convert_thread((void *)lc
, (const void *)inlc
);
709 if (OPTIONS_DEBUG(opt
, 1))
710 printf("discarding load command %d\n", inlc
->cmd
);
715 if (NATIVE_LC_SEGMENT
== lc
->cmd
|| LC_THREAD
== lc
->cmd
) {
716 mach_header_inc_ncmds(mh
, 1);
717 mach_header_inc_sizeofcmds(mh
, lc
->cmdsize
);
718 lc
= (void *)next_lc(lc
);
720 if (NULL
== (inlc
= next_lc(inlc
)))
725 * Even if we've encountered an error, try and write out the header
727 if (0 != bounded_pwrite(fd
, header
, headersize
, 0, &oi
.oi_nocache
, NULL
))
729 if (0 == ecode
&& sizeof (*mh
) + mh
->sizeofcmds
!= headersize
)
731 validate_core_header(mh
, oi
.oi_foffset
);
733 warnx("failed to write new core file correctly");
734 else if (opt
->verbose
) {
736 printf("Conversion complete: %s copied", str_hsize(hstr
, cstats
->copied
));
737 const int64_t delta
= cstats
->uncompressed
- cstats
->compressed
;
739 printf(", %s uncompressed", str_hsize(hstr
, delta
));
740 const int64_t added
= cstats
->added
+ ((int)mh
->sizeofcmds
- (int)inmh
->sizeofcmds
);
742 printf(", %s added", str_hsize(hstr
, added
));
746 munmap((void *)corebase
, (size_t)filesize
);