2 * Copyright (c) 2016 Apple Inc. All rights reserved.
14 #include <sys/types.h>
15 #include <sys/sysctl.h>
33 #include <mach/mach.h>
36 vanilla_region_optimization(struct region
*r
, __unused
void *arg
)
38 assert(0 != R_SIZE(r
));
41 * Elide unreadable regions
43 if ((r
->r_info
.max_protection
& VM_PROT_READ
) != VM_PROT_READ
) {
45 printr(r
, "eliding unreadable region\n");
46 return WALK_DELETE_REGION
;
50 * Elide submaps (here for debugging purposes?)
52 if (r
->r_info
.is_submap
) {
54 printr(r
, "eliding submap\n");
55 return WALK_DELETE_REGION
;
61 if (r
->r_info
.protection
== VM_PROT_NONE
&&
62 (VM_MEMORY_STACK
== r
->r_info
.user_tag
||
63 VM_MEMORY_MALLOC
== r
->r_info
.user_tag
)) {
66 printr(r
, "eliding %s - guard\n",
67 str_hsize(hstr
, R_SIZE(r
)));
69 return WALK_DELETE_REGION
;
75 * (Paranoid validation + debugging assistance.)
78 validate_core_header(const native_mach_header_t
*mh
, off_t corefilesize
)
81 printf("Core file: mh %p ncmds %u sizeofcmds %u\n",
82 mh
, mh
->ncmds
, mh
->sizeofcmds
);
84 const struct load_command
*lc
= (const void *)(mh
+ 1);
85 for (unsigned i
= 0; i
< mh
->ncmds
; i
++) {
87 if ((uintptr_t)lc
< (uintptr_t)mh
||
88 (uintptr_t)lc
> (uintptr_t)mh
+ mh
->sizeofcmds
) {
89 warnx("load command %p outside mach header range [%p, 0x%lx]?",
90 lc
, mh
, (uintptr_t)mh
+ mh
->sizeofcmds
);
94 printf("lc %p cmd %u cmdsize %u ", lc
, lc
->cmd
, lc
->cmdsize
);
96 const native_segment_command_t
*sc
;
97 const struct proto_coreinfo_command
*cic
;
98 const struct proto_fileref_command
*frc
;
99 const struct thread_command
*tc
;
102 case NATIVE_LC_SEGMENT
:
103 sc
= (const void *)lc
;
105 printf("%8s: mem %llx-%llx file %lld-%lld %x/%x flags %x\n",
107 (mach_vm_offset_t
)sc
->vmaddr
,
108 (mach_vm_offset_t
)sc
->vmaddr
+ sc
->vmsize
,
110 (off_t
)sc
->fileoff
+ (off_t
)sc
->filesize
,
111 sc
->initprot
, sc
->maxprot
, sc
->flags
);
113 if ((off_t
)sc
->fileoff
< mh
->sizeofcmds
||
114 (off_t
)sc
->filesize
< 0) {
115 warnx("bad segment command");
118 if ((off_t
)sc
->fileoff
> corefilesize
||
119 (off_t
)sc
->fileoff
+ (off_t
)sc
->filesize
> corefilesize
) {
121 * We may have run out of space to write the data
123 warnx("segment command points beyond end of file");
127 case proto_LC_COREINFO
:
128 cic
= (const void *)lc
;
131 uuid_unparse_lower(cic
->uuid
, uustr
);
132 printf("%8s: version %d type %d uuid %s addr %llx dyninfo %llx\n",
133 "COREINFO", cic
->version
, cic
->type
, uustr
, cic
->address
, cic
->dyninfo
);
135 if (cic
->version
< 1 ||
136 cic
->type
!= proto_CORETYPE_USER
) {
137 warnx("bad coreinfo command");
142 case proto_LC_FILEREF
:
143 frc
= (const void *)lc
;
144 const char *nm
= frc
->filename
.offset
+ (char *)lc
;
147 uuid_unparse_lower(frc
->uuid
, uustr
);
148 printf("%8s: mem %llx-%llx file %lld-%lld %x/%x '%s' %.12s..\n",
151 frc
->vmaddr
+ frc
->vmsize
,
153 (off_t
)frc
->fileoff
+ (off_t
)frc
->filesize
,
154 frc
->initprot
, frc
->maxprot
, nm
, uustr
);
156 if (nm
<= (caddr_t
)lc
||
157 nm
> (caddr_t
)lc
+ lc
->cmdsize
||
158 (off_t
)frc
->fileoff
< 0 || (off_t
)frc
->filesize
< 0) {
159 warnx("bad fileref command");
165 tc
= (const void *)lc
;
167 printf("%8s:\n", "THREAD");
168 uint32_t *wbuf
= (void *)(tc
+ 1);
170 const uint32_t flavor
= *wbuf
++;
171 const uint32_t count
= *wbuf
++;
174 printf(" flavor %u count %u\n", flavor
, count
);
176 boolean_t nl
= false;
177 for (unsigned k
= 0; k
< count
; k
++) {
179 printf(" [%3u] ", k
);
180 printf("%08x ", *wbuf
++);
193 if (!VALID_THREAD_STATE_FLAVOR(flavor
)) {
194 warnx("bad thread state flavor");
197 } while ((caddr_t
) wbuf
< (caddr_t
)tc
+ tc
->cmdsize
);
201 warnx("unknown cmd %u in header\n", lc
->cmd
);
205 lc
= (const void *)((caddr_t
)lc
+ lc
->cmdsize
);
212 * The vanilla Mach-O core file consists of:
214 * - A Mach-O header of type MH_CORE
216 * A set of load commands of the following types:
218 * - LC_SEGMENT{,_64} pointing at memory content in the file,
219 * each chunk consisting of a contiguous region. Regions may be zfod
220 * (no file content present) or content may be compressed (experimental)
222 * - prototype_LC_COREINFO (experimental), pointing at dyld (10.12 onwards)
224 * - prototype_LC_FILEREF (experimental) pointing at memory
225 * content to be mapped in from another file at various offsets
227 * - LC_THREAD commands with state for each thread
229 * These load commands are followed by the relevant contents of memory,
230 * pointed to by the various commands.
237 struct regionhead
*rhead
,
238 const uuid_t aout_uuid
,
239 mach_vm_offset_t aout_load_addr
,
240 mach_vm_offset_t dyld_aii_addr
)
242 struct size_segment_data ssda
;
243 bzero(&ssda
, sizeof (ssda
));
245 if (walk_region_list(rhead
, region_size_memory
, &ssda
) < 0) {
246 warnx(0, "cannot count segments");
250 unsigned thread_count
= 0;
251 mach_port_t
*threads
= NULL
;
252 kern_return_t ret
= task_threads(task
, &threads
, &thread_count
);
253 if (KERN_SUCCESS
!= ret
|| thread_count
< 1) {
254 err_mach(ret
, NULL
, "cannot retrieve threads");
259 print_memory_region_header();
260 walk_region_list(rhead
, region_print_memory
, NULL
);
263 size_t headersize
= sizeof (native_mach_header_t
) +
264 thread_count
* sizeof_LC_THREAD() +
265 ssda
.ssd_fileref
.headersize
+
266 ssda
.ssd_zfod
.headersize
+
267 ssda
.ssd_vanilla
.headersize
+
268 ssda
.ssd_sparse
.headersize
;
270 headersize
+= sizeof (struct proto_coreinfo_command
);
272 void *header
= calloc(1, headersize
);
274 errx(EX_OSERR
, "out of memory for header");
276 native_mach_header_t
*mh
= make_corefile_mach_header(header
);
277 struct load_command
*lc
= (void *)(mh
+ 1);
280 const struct proto_coreinfo_command
*cc
=
281 make_coreinfo_command(mh
, lc
, aout_uuid
, aout_load_addr
, dyld_aii_addr
);
282 lc
= (void *)((caddr_t
)cc
+ cc
->cmdsize
);
286 const unsigned long fileref_count
= ssda
.ssd_fileref
.count
;
287 const unsigned long segment_count
= fileref_count
+
288 ssda
.ssd_zfod
.count
+ ssda
.ssd_vanilla
.count
+ ssda
.ssd_sparse
.count
;
289 printf("Dumping %lu memory segments", segment_count
);
290 if (0 != fileref_count
)
291 printf(" (including %lu file reference%s (%lu bytes))",
292 fileref_count
, 1 == fileref_count
? "" : "s",
293 ssda
.ssd_fileref
.headersize
);
297 vm_size_t pagesize
= ((vm_offset_t
)1 << pageshift_host
);
298 vm_offset_t pagemask
= (vm_offset_t
)(pagesize
- 1);
300 struct write_segment_data wsda
= {
305 .wsd_foffset
= ((vm_offset_t
)headersize
+ pagemask
) & ~pagemask
,
310 if (0 != walk_region_list(rhead
, region_write_memory
, &wsda
))
313 del_region_list(rhead
);
315 struct thread_command
*tc
= (void *)wsda
.wsd_lc
;
317 for (unsigned t
= 0; t
< thread_count
; t
++) {
318 dump_thread_state(mh
, tc
, threads
[t
]);
319 mach_port_deallocate(mach_task_self(), threads
[t
]);
320 tc
= (void *)((caddr_t
)tc
+ tc
->cmdsize
);
324 * Even if we've run out of space, try our best to
325 * write out the header.
327 if (-1 == pwrite(fd
, header
, headersize
, 0))
330 wsda
.wsd_nwritten
+= headersize
;
332 validate_core_header(mh
, wsda
.wsd_foffset
);
335 warnx("failed to write core file correctly");
336 else if (opt
->verbose
) {
338 printf("Wrote %s to corefile ", str_hsize(hsz
, wsda
.wsd_nwritten
));
339 printf("(memory image %s", str_hsize(hsz
, ssda
.ssd_vanilla
.memsize
));
340 if (ssda
.ssd_sparse
.memsize
)
341 printf("+%s", str_hsize(hsz
, ssda
.ssd_sparse
.memsize
));
342 if (ssda
.ssd_fileref
.memsize
)
343 printf(", referenced %s", str_hsize(hsz
, ssda
.ssd_fileref
.memsize
));
344 if (ssda
.ssd_zfod
.memsize
)
345 printf(", zfod %s", str_hsize(hsz
, ssda
.ssd_zfod
.memsize
));
353 coredump(task_t task
, int fd
)
355 /* this is the shared cache id, if any */
359 dyld_process_info dpi
= get_task_dyld_info(task
);
361 get_sc_uuid(dpi
, sc_uuid
);
364 /* this group is for LC_COREINFO */
365 mach_vm_offset_t dyld_addr
= 0; // all_image_infos -or- dyld mach header
366 mach_vm_offset_t aout_load_addr
= 0;
368 uuid_clear(aout_uuid
);
371 * Walk the address space
374 struct regionhead
*rhead
= coredump_prepare(task
, sc_uuid
);
381 printf("Optimizing dump content\n");
382 walk_region_list(rhead
, vanilla_region_optimization
, NULL
);
385 if (opt
->coreinfo
|| opt
->sparse
) {
387 * Snapshot dyld's info ..
389 if (!libent_build_nametable(task
, dpi
))
390 warnx("error parsing dyld data => ignored");
394 * Find the a.out load address and uuid, and the dyld mach header for the coreinfo
396 const struct libent
*le
;
397 if (NULL
!= (le
= libent_lookup_first_bytype(MH_EXECUTE
))) {
398 aout_load_addr
= le
->le_mhaddr
;
399 uuid_copy(aout_uuid
, le
->le_uuid
);
401 if (NULL
!= (le
= libent_lookup_first_bytype(MH_DYLINKER
))) {
402 dyld_addr
= le
->le_mhaddr
;
407 * Use dyld's view of what's being used in the address
408 * space to shrink the dump.
410 if (0 == walk_region_list(rhead
, decorate_memory_region
, (void *)dpi
)) {
412 printf("Performing sparse dump optimization(s)\n");
413 walk_region_list(rhead
, sparse_region_optimization
, NULL
);
415 walk_region_list(rhead
, undecorate_memory_region
, NULL
);
416 warnx("error parsing dyld data => ignored");
421 free_task_dyld_info(dpi
);
425 printf("Optimization(s) done\n");
428 ecode
= coredump_write(task
, fd
, rhead
, aout_uuid
, aout_load_addr
, dyld_addr
);
434 struct find_shared_cache_args
{
436 vm_object_id_t fsc_object_id
;
437 vm32_object_id_t fsc_region_object_id
;
439 const struct libent
*fsc_le
;
444 * This is "find the objid of the first shared cache" in the shared region.
447 find_shared_cache(struct region
*r
, void *arg
)
449 struct find_shared_cache_args
*fsc
= arg
;
451 if (!r
->r_insharedregion
)
452 return WALK_CONTINUE
; /* wrong address range! */
453 if (0 != r
->r_info
.user_tag
)
454 return WALK_CONTINUE
; /* must be tag zero */
455 if ((VM_PROT_READ
| VM_PROT_EXECUTE
) != r
->r_info
.protection
||
456 r
->r_info
.protection
!= r
->r_info
.max_protection
)
457 return WALK_CONTINUE
; /* must be r-x / r-x */
458 if (r
->r_pageinfo
.offset
!= 0)
459 return WALK_CONTINUE
; /* must map beginning of file */
463 printf("Examining shared cache candidate %llx-%llx (%s)\n",
464 R_ADDR(r
), R_ENDADDR(r
), str_hsize(hstr
, R_SIZE(r
)));
467 struct copied_dyld_cache_header
*ch
;
468 mach_msg_type_number_t chlen
= sizeof (*ch
);
469 kern_return_t ret
= mach_vm_read(fsc
->fsc_task
, R_ADDR(r
), sizeof (*ch
), (vm_offset_t
*)&ch
, &chlen
);
471 if (KERN_SUCCESS
!= ret
) {
472 err_mach(ret
, NULL
, "mapping candidate shared region");
473 return WALK_CONTINUE
;
477 if (get_uuid_from_shared_cache_mapping(ch
, chlen
, scuuid
) &&
478 uuid_compare(scuuid
, fsc
->fsc_uuid
) == 0) {
479 if (opt
->debug
> 2) {
481 uuid_unparse_lower(fsc
->fsc_uuid
, uustr
);
482 printr(r
, "found shared cache %s here\n", uustr
);
484 if (!r
->r_info
.external_pager
) {
486 printf("Hmm. Found shared cache magic# + uuid, but not externally paged?\n");
488 return WALK_CONTINUE
; /* should be "paged" from a file */
491 // This is the ID associated with the first page of the mapping
492 fsc
->fsc_object_id
= r
->r_pageinfo
.object_id
;
493 // This is the ID associated with the region
494 fsc
->fsc_region_object_id
= r
->r_info
.object_id
;
496 mach_vm_deallocate(mach_task_self(), (vm_offset_t
)ch
, chlen
);
497 if (fsc
->fsc_object_id
) {
500 uuid_unparse_lower(fsc
->fsc_uuid
, uu
);
501 printf("Shared cache objid %llx uuid %s\n",
502 fsc
->fsc_object_id
, uu
);
504 return WALK_TERMINATE
;
506 return WALK_CONTINUE
;
510 compare_region_with_shared_cache(const struct region
*r
, struct find_shared_cache_args
*fsc
)
513 if (-1 == fstat(fsc
->fsc_fd
, &st
)) {
515 printr(r
, "%s - %s\n",
516 fsc
->fsc_le
->le_filename
, strerror(errno
));
519 void *file
= mmap(NULL
, (size_t)R_SIZE(r
), PROT_READ
, MAP_PRIVATE
, fsc
->fsc_fd
, r
->r_pageinfo
.offset
);
520 if ((void *)-1L == file
) {
522 printr(r
, "mmap %s - %s\n", fsc
->fsc_le
->le_filename
, strerror(errno
));
525 madvise(file
, (size_t)R_SIZE(r
), MADV_SEQUENTIAL
);
527 vm_offset_t data
= 0;
528 mach_msg_type_number_t data_count
;
529 const kern_return_t kr
= mach_vm_read(fsc
->fsc_task
, R_ADDR(r
), R_SIZE(r
), &data
, &data_count
);
531 if (KERN_SUCCESS
!= kr
|| data_count
< R_SIZE(r
)) {
532 err_mach(kr
, r
, "mach_vm_read()");
533 munmap(file
, (size_t)R_SIZE(r
));
537 mach_vm_size_t cmpsize
= data_count
;
541 * Now we have the corresponding regions mapped, we should be
542 * able to compare them. There's just one last twist that relates
543 * to heterogenous pagesize systems: rdar://23744374
545 if (st
.st_size
< (off_t
)(r
->r_pageinfo
.offset
+ cmpsize
) &&
546 pageshift_host
< pageshift_app
) {
548 * Looks like we're about to map close to the end of the object.
549 * Check what's really mapped there and reduce the size accordingly.
551 if (!is_actual_size(fsc
->fsc_task
, r
, &cmpsize
)) {
553 printr(r
, "narrowing the comparison (%llu "
554 "-> %llu)\n", R_SIZE(r
), cmpsize
);
559 mach_vm_behavior_set(mach_task_self(), data
, data_count
, VM_BEHAVIOR_SEQUENTIAL
);
561 const boolean_t thesame
= memcmp(file
, (void *)data
, (size_t)cmpsize
) == 0;
566 const char *f
= file
;
567 const char *d
= (void *)data
;
568 for (mach_vm_size_t off
= 0; off
< cmpsize
; off
+= 4096) {
569 if (memcmp(f
, d
, 4096) != 0) {
576 printr(r
, "%d of %d pages different\n", diffcount
, diffcount
+ samecount
);
579 mach_vm_deallocate(mach_task_self(), data
, data_count
);
580 munmap(file
, (size_t)R_SIZE(r
));
582 if (!thesame
&& opt
->debug
)
583 printr(r
, "mapped file (%s) region is modified\n", fsc
->fsc_le
->le_filename
);
588 label_shared_cache(struct region
*r
, void *arg
)
590 struct find_shared_cache_args
*fsc
= arg
;
592 if (!r
->r_insharedregion
)
593 return WALK_CONTINUE
;
594 if (!r
->r_info
.external_pager
)
595 return WALK_CONTINUE
;
596 if (r
->r_pageinfo
.object_id
!= fsc
->fsc_object_id
) {
597 /* wrong object, or first page already modified */
598 return WALK_CONTINUE
;
600 if (((r
->r_info
.protection
| r
->r_info
.max_protection
) & VM_PROT_WRITE
) != 0) {
601 /* writable, but was it written? */
602 if (r
->r_info
.pages_dirtied
+ r
->r_info
.pages_swapped_out
!= 0)
603 return WALK_CONTINUE
; // a heuristic ..
604 if (!compare_region_with_shared_cache(r
, fsc
)) {
605 /* bits don't match */
606 return WALK_CONTINUE
;
610 if (opt
->debug
> 2) {
611 /* this validation is -really- expensive */
612 if (!compare_region_with_shared_cache(r
, fsc
))
613 printr(r
, "WARNING: region should match, but doesn't\n");
617 * This mapped file segment will be represented as a reference
618 * to the file, rather than as a copy of the file.
620 const struct libent
*le
= libent_lookup_byuuid(fsc
->fsc_uuid
);
621 r
->r_fileref
= calloc(1, sizeof (*r
->r_fileref
));
623 r
->r_fileref
->fr_libent
= le
;
624 if (r
->r_fileref
->fr_libent
) {
625 r
->r_fileref
->fr_offset
= r
->r_pageinfo
.offset
;
626 r
->r_op
= &fileref_ops
;
632 return WALK_CONTINUE
;
634 #endif /* CONFIG_REFSC */
637 coredump_prepare(task_t task
, uuid_t sc_uuid
)
639 struct regionhead
*rhead
= build_region_list(task
);
642 print_memory_region_header();
643 walk_region_list(rhead
, region_print_memory
, NULL
);
646 if (uuid_is_null(sc_uuid
))
650 * Name the shared cache, if we can
652 char *nm
= shared_cache_filename(sc_uuid
);
653 const struct libent
*le
;
656 le
= libent_insert(nm
, sc_uuid
, 0, NULL
);
658 le
= libent_insert("(shared cache)", sc_uuid
, 0, NULL
);
661 uuid_unparse_lower(sc_uuid
, uustr
);
662 printf("Shared cache UUID: %s, but no filename => ignored\n", uustr
);
668 if (opt
->scfileref
) {
670 * See if we can replace entire regions with references to the shared cache
671 * by looking at the VM meta-data about those regions.
675 uuid_unparse_lower(sc_uuid
, uustr
);
676 printf("Searching for shared cache with uuid %s\n", uustr
);
680 * Identify the regions mapping the shared cache by comparing the UUID via
681 * dyld with the UUID of likely-looking mappings in the right address range
683 struct find_shared_cache_args fsca
;
684 bzero(&fsca
, sizeof (fsca
));
685 fsca
.fsc_task
= task
;
686 uuid_copy(fsca
.fsc_uuid
, sc_uuid
);
689 walk_region_list(rhead
, find_shared_cache
, &fsca
);
691 if (0 == fsca
.fsc_object_id
) {
692 printf("Cannot identify the shared cache region(s) => ignored\n");
695 printf("Referenced %s\n", nm
);
697 fsca
.fsc_fd
= open(fsca
.fsc_le
->le_filename
, O_RDONLY
);
699 walk_region_list(rhead
, label_shared_cache
, &fsca
);
705 #endif /* CONFIG_REFSC */