/* * Copyright (c) 2016 Apple Inc. All rights reserved. */ #include "options.h" #include "vm.h" #include "region.h" #include "utils.h" #include "dyld.h" #include "threads.h" #include "vanilla.h" #include "sparse.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * (Another optimization to consider is merging adjacent regions with * the same properties.) */ static walk_return_t simple_region_optimization(struct region *r, __unused void *arg) { assert(0 != R_SIZE(r)); /* * Elide unreadable regions */ if ((r->r_info.max_protection & VM_PROT_READ) != VM_PROT_READ) { if (OPTIONS_DEBUG(opt, 2)) printr(r, "eliding unreadable region\n"); return WALK_DELETE_REGION; } #ifdef CONFIG_SUBMAP /* * Elide submaps (here for debugging purposes?) */ if (r->r_info.is_submap) { if (OPTIONS_DEBUG(opt)) printr(r, "eliding submap\n"); return WALK_DELETE_REGION; } #endif /* * Elide guard regions */ if (r->r_info.protection == VM_PROT_NONE && (VM_MEMORY_STACK == r->r_info.user_tag || VM_MEMORY_MALLOC == r->r_info.user_tag)) { if (OPTIONS_DEBUG(opt, 2)) { hsize_str_t hstr; tag_str_t tstr; printr(r, "elide %s - %s\n", str_hsize(hstr, R_SIZE(r)), str_tagr(tstr, r)); } return WALK_DELETE_REGION; } /* * Regions full of clean zfod data e.g. VM_MEMORY_MALLOC_LARGE can be recorded as zfod */ if (r->r_info.share_mode == SM_PRIVATE && 0 == r->r_info.external_pager && 0 == r->r_info.pages_dirtied) { if (OPTIONS_DEBUG(opt, 2)) { hsize_str_t hstr; tag_str_t tstr; printr(r, "convert to zfod %s - %s\n", str_hsize(hstr, R_SIZE(r)), str_tagr(tstr, r)); } r->r_inzfodregion = true; r->r_op = &zfod_ops; } return WALK_CONTINUE; } /* * (Paranoid validation + debugging assistance.) */ void validate_core_header(const native_mach_header_t *mh, off_t corefilesize) { assert(NATIVE_MH_MAGIC == mh->magic); assert(MH_CORE == mh->filetype); if (OPTIONS_DEBUG(opt, 2)) printf("%s: core file: mh %p ncmds %u sizeofcmds %u\n", __func__, mh, mh->ncmds, mh->sizeofcmds); unsigned sizeofcmds = 0; off_t corefilemaxoff = 0; const struct load_command *lc = (const void *)(mh + 1); for (unsigned i = 0; i < mh->ncmds; i++) { if ((uintptr_t)lc < (uintptr_t)mh || (uintptr_t)lc > (uintptr_t)mh + mh->sizeofcmds) { warnx("load command %p outside mach header range [%p, 0x%lx]?", lc, mh, (uintptr_t)mh + mh->sizeofcmds); abort(); } if (OPTIONS_DEBUG(opt, 2)) printf("lc %p cmd %3u size %3u ", lc, lc->cmd, lc->cmdsize); sizeofcmds += lc->cmdsize; switch (lc->cmd) { case NATIVE_LC_SEGMENT: { const native_segment_command_t *sc = (const void *)lc; if (OPTIONS_DEBUG(opt, 2)) { printf("%8s: mem %llx-%llx file %lld-%lld %s/%s nsect %u flags %x\n", "SEGMENT", (mach_vm_offset_t)sc->vmaddr, (mach_vm_offset_t)sc->vmaddr + sc->vmsize, (off_t)sc->fileoff, (off_t)sc->fileoff + (off_t)sc->filesize, str_prot(sc->initprot), str_prot(sc->maxprot), sc->nsects, sc->flags); } if ((off_t)sc->fileoff < mh->sizeofcmds || (off_t)sc->filesize < 0) { warnx("bad segment command"); abort(); } const off_t endoff = (off_t)sc->fileoff + (off_t)sc->filesize; if ((off_t)sc->fileoff > corefilesize || endoff > corefilesize) { /* * We may have run out of space to write the data */ warnx("segment command points beyond end of file"); } corefilemaxoff = MAX(corefilemaxoff, endoff); break; } case proto_LC_COREINFO: { const struct proto_coreinfo_command *cic = (const void *)lc; if (OPTIONS_DEBUG(opt, 2)) { uuid_string_t uustr; uuid_unparse_lower(cic->uuid, uustr); printf("%8s: version %d type %d uuid %s addr %llx dyninfo %llx\n", "COREINFO", cic->version, cic->type, uustr, cic->address, cic->dyninfo); } if (cic->version < 1 || cic->type != proto_CORETYPE_USER) { warnx("bad coreinfo command"); abort(); } break; } case proto_LC_FILEREF: { const struct proto_fileref_command *frc = (const void *)lc; const char *nm = frc->filename.offset + (char *)lc; if (OPTIONS_DEBUG(opt, 2)) { printf("%8s: mem %llx-%llx file %lld-%lld %s/%s '%s'\n", "FREF", frc->vmaddr, frc->vmaddr + frc->vmsize, (off_t)frc->fileoff, (off_t)frc->fileoff + (off_t)frc->filesize, str_prot(frc->prot), str_prot(frc->maxprot), nm); } switch (FREF_ID_TYPE(frc->flags)) { case kFREF_ID_UUID: case kFREF_ID_MTIMESPEC_LE: case kFREF_ID_NONE: break; default: warnx("unknown fref id type: flags %x", frc->flags); abort(); } if (nm <= (caddr_t)lc || nm > (caddr_t)lc + lc->cmdsize || (off_t)frc->fileoff < 0 || (off_t)frc->filesize < 0) { warnx("bad fileref command"); abort(); } break; } case proto_LC_COREDATA: { const struct proto_coredata_command *cc = (const void *)lc; if (OPTIONS_DEBUG(opt, 2)) { printf("%8s: mem %llx-%llx file %lld-%lld %s/%s flags %x\n", "COREDATA", cc->vmaddr, cc->vmaddr + cc->vmsize, (off_t)cc->fileoff, (off_t)cc->fileoff + (off_t)cc->filesize, str_prot(cc->prot), str_prot(cc->maxprot), cc->flags); } if ((off_t)cc->fileoff < mh->sizeofcmds || (off_t)cc->filesize < 0) { warnx("bad COREDATA command"); abort(); } const off_t endoff = (off_t)cc->fileoff + (off_t)cc->filesize; if ((off_t)cc->fileoff > corefilesize || endoff > corefilesize) { /* * We may have run out of space to write the data */ warnx("segment command points beyond end of file"); } corefilemaxoff = MAX(corefilemaxoff, endoff); break; } case LC_THREAD: { const struct thread_command *tc = (const void *)lc; if (OPTIONS_DEBUG(opt, 2)) printf("%8s:\n", "THREAD"); uint32_t *wbuf = (void *)(tc + 1); do { const uint32_t flavor = *wbuf++; const uint32_t count = *wbuf++; if (OPTIONS_DEBUG(opt, 2)) { printf(" flavor %u count %u\n", flavor, count); if (count) { bool nl = false; for (unsigned k = 0; k < count; k++) { if (0 == (k & 7)) printf(" [%3u] ", k); printf("%08x ", *wbuf++); if (7 == (k & 7)) { printf("\n"); nl = true; } else nl = false; } if (!nl) printf("\n"); } } else wbuf += count; if (!VALID_THREAD_STATE_FLAVOR(flavor)) { warnx("bad thread state flavor"); abort(); } } while ((caddr_t) wbuf < (caddr_t)tc + tc->cmdsize); break; } default: warnx("unknown cmd %u in header", lc->cmd); abort(); } if (lc->cmdsize) lc = (const void *)((caddr_t)lc + lc->cmdsize); else break; } if (corefilemaxoff < corefilesize) warnx("unused data after corefile offset %lld", corefilemaxoff); if (sizeofcmds != mh->sizeofcmds) { warnx("inconsistent mach header %u vs. %u", sizeofcmds, mh->sizeofcmds); abort(); } } /* * The vanilla Mach-O core file consists of: * * - A Mach-O header of type MH_CORE * * A set of load commands of the following types: * * - LC_SEGMENT{,_64} pointing at memory content in the file, * each chunk consisting of a contiguous region. Regions may be zfod * (no file content present). * * - proto_LC_COREDATA pointing at memory content in the file, * each chunk consisting of a contiguous region. Regions may be zfod * (no file content present) or content may be compressed (experimental) * * - proto_LC_COREINFO (experimental), pointing at dyld (10.12 onwards) * * - proto_LC_FILEREF (experimental) pointing at memory * content to be mapped in from another uuid-tagged file at various offsets * * - LC_THREAD commands with state for each thread * * These load commands are followed by the relevant contents of memory, * pointed to by the various commands. */ int coredump_write( const task_t task, const int fd, struct regionhead *rhead, const uuid_t aout_uuid, mach_vm_offset_t aout_load_addr, mach_vm_offset_t dyld_aii_addr) { struct size_segment_data ssda; bzero(&ssda, sizeof (ssda)); if (walk_region_list(rhead, region_size_memory, &ssda) < 0) { warnx(0, "cannot count segments"); return EX_OSERR; } unsigned thread_count = 0; mach_port_t *threads = NULL; kern_return_t ret = task_threads(task, &threads, &thread_count); if (KERN_SUCCESS != ret || thread_count < 1) { err_mach(ret, NULL, "cannot retrieve threads"); thread_count = 0; } if (OPTIONS_DEBUG(opt, 3)) { print_memory_region_header(); walk_region_list(rhead, region_print_memory, NULL); printf("\nmach header %lu\n", sizeof (native_mach_header_t)); printf("threadcount %u threadsize %lu\n", thread_count, thread_count * sizeof_LC_THREAD()); printf("fileref %lu %lu %llu\n", ssda.ssd_fileref.count, ssda.ssd_fileref.headersize, ssda.ssd_fileref.memsize); printf("zfod %lu %lu %llu\n", ssda.ssd_zfod.count, ssda.ssd_zfod.headersize, ssda.ssd_zfod.memsize); printf("vanilla %lu %lu %llu\n", ssda.ssd_vanilla.count, ssda.ssd_vanilla.headersize, ssda.ssd_vanilla.memsize); printf("sparse %lu %lu %llu\n", ssda.ssd_sparse.count, ssda.ssd_sparse.headersize, ssda.ssd_sparse.memsize); } size_t headersize = sizeof (native_mach_header_t) + thread_count * sizeof_LC_THREAD() + ssda.ssd_fileref.headersize + ssda.ssd_zfod.headersize + ssda.ssd_vanilla.headersize + ssda.ssd_sparse.headersize; if (opt->extended) headersize += sizeof (struct proto_coreinfo_command); void *header = calloc(1, headersize); if (NULL == header) errx(EX_OSERR, "out of memory for header"); native_mach_header_t *mh = make_corefile_mach_header(header); struct load_command *lc = (void *)(mh + 1); if (opt->extended) { const struct proto_coreinfo_command *cc = make_coreinfo_command(mh, lc, aout_uuid, aout_load_addr, dyld_aii_addr); lc = (void *)((caddr_t)cc + cc->cmdsize); } if (opt->verbose) { const unsigned long fileref_count = ssda.ssd_fileref.count; const unsigned long segment_count = fileref_count + ssda.ssd_zfod.count + ssda.ssd_vanilla.count + ssda.ssd_sparse.count; printf("Writing %lu segments", segment_count); if (0 != fileref_count) printf(" (including %lu file reference%s (%lu bytes))", fileref_count, 1 == fileref_count ? "" : "s", ssda.ssd_fileref.headersize); printf("\n"); } mach_vm_offset_t pagesize = ((mach_vm_offset_t)1 << pageshift_host); mach_vm_offset_t pagemask = pagesize - 1; struct write_segment_data wsda = { .wsd_task = task, .wsd_mh = mh, .wsd_lc = lc, .wsd_fd = fd, .wsd_nocache = false, .wsd_foffset = ((mach_vm_offset_t)headersize + pagemask) & ~pagemask, .wsd_nwritten = 0, }; int ecode = 0; if (0 != walk_region_list(rhead, region_write_memory, &wsda)) ecode = EX_IOERR; del_region_list(rhead); struct thread_command *tc = (void *)wsda.wsd_lc; for (unsigned t = 0; t < thread_count; t++) { dump_thread_state(mh, tc, threads[t]); mach_port_deallocate(mach_task_self(), threads[t]); tc = (void *)((caddr_t)tc + tc->cmdsize); } /* * Even if we've run out of space, try our best to * write out the header. */ if (0 != bounded_pwrite(fd, header, headersize, 0, &wsda.wsd_nocache, NULL)) ecode = EX_IOERR; if (0 == ecode && headersize != sizeof (*mh) + mh->sizeofcmds) ecode = EX_SOFTWARE; if (0 == ecode) wsda.wsd_nwritten += headersize; validate_core_header(mh, wsda.wsd_foffset); if (ecode) warnx("failed to write core file correctly"); else if (opt->verbose) { hsize_str_t hsz; printf("Wrote %s to corefile ", str_hsize(hsz, wsda.wsd_nwritten)); printf("(memory image %s", str_hsize(hsz, ssda.ssd_vanilla.memsize)); if (ssda.ssd_sparse.memsize) printf("+%s", str_hsize(hsz, ssda.ssd_sparse.memsize)); if (ssda.ssd_fileref.memsize) printf(", referenced %s", str_hsize(hsz, ssda.ssd_fileref.memsize)); if (ssda.ssd_zfod.memsize) printf(", zfod %s", str_hsize(hsz, ssda.ssd_zfod.memsize)); printf(")\n"); } free(header); return ecode; } static void addfileref(struct region *r, const struct libent *le, const char *nm) { r->r_fileref = calloc(1, sizeof (*r->r_fileref)); if (r->r_fileref) { if (le) { assert(NULL == nm); r->r_fileref->fr_libent = le; r->r_fileref->fr_pathname = le->le_pathname; } else { assert(NULL == le); r->r_fileref->fr_pathname = strdup(nm); } r->r_fileref->fr_offset = r->r_pageinfo.offset; r->r_op = &fileref_ops; } } /* * Once all the info about the shared cache (filerefs) and the information from * dyld (filerefs and subregions), take one last look for mappings * of filesystem content to convert to additional filerefs. * * By default we are pessimistic: read-only mappings on read-only root. */ static walk_return_t label_mapped_files(struct region *r, void *arg) { const struct proc_bsdinfo *pbi = arg; if (r->r_fileref || r->r_insharedregion || r->r_incommregion || r->r_inzfodregion) return WALK_CONTINUE; if (r->r_nsubregions) return WALK_CONTINUE; if (!r->r_info.external_pager) return WALK_CONTINUE; if (!opt->allfilerefs) { /* must be mapped without write permission */ if (0 != (r->r_info.protection & VM_PROT_WRITE)) return WALK_CONTINUE; } char pathbuf[MAXPATHLEN+1]; pathbuf[0] = '\0'; int len = proc_regionfilename(pbi->pbi_pid, R_ADDR(r), pathbuf, sizeof (pathbuf)-1); if (len <= 0 || len > MAXPATHLEN) return WALK_CONTINUE; pathbuf[len] = 0; #if 0 /* * On the desktop, only refer to files beginning with particular * prefixes to increase the likelihood that we'll be able to * find the content later on. * * XXX Not practical with a writable root, but helpful for testing. */ static const char *white[] = { "/System", "/Library", "/usr", }; const unsigned nwhite = sizeof (white) / sizeof (white[0]); bool skip = true; for (unsigned i = 0; skip && i < nwhite; i++) skip = 0 != strncmp(white[i], pathbuf, strlen(white[i])); if (skip) { if (OPTIONS_DEBUG(opt, 3)) printf("\t(%s not included)\n", pathbuf); return WALK_CONTINUE; } static const char *black[] = { "/System/Library/Caches", "/Library/Caches", "/usr/local", }; const unsigned nblack = sizeof (black) / sizeof (black[0]); for (unsigned i = 0; !skip && i < nblack; i++) skip = 0 == strncmp(black[i], pathbuf, strlen(black[i])); if (skip) { if (OPTIONS_DEBUG(opt, 3)) printf("\t(%s excluded)\n", pathbuf); return WALK_CONTINUE; } #endif struct statfs stfs; if (-1 == statfs(pathbuf, &stfs)) { switch (errno) { case EACCES: case EPERM: case ENOENT: break; default: warnc(errno, "statfs: %s", pathbuf); break; } return WALK_CONTINUE; } do { if (OPTIONS_DEBUG(opt, 2)) printr(r, "found mapped file %s\n", pathbuf); if (!opt->allfilerefs) { if ((stfs.f_flags & MNT_ROOTFS) != MNT_ROOTFS) break; // must be on the root filesystem if ((stfs.f_flags & MNT_RDONLY) != MNT_RDONLY) break; // must be on a read-only filesystem } if (OPTIONS_DEBUG(opt, 2)) print_memory_region(r); addfileref(r, NULL, pathbuf); } while (0); return WALK_CONTINUE; } int coredump(task_t task, int fd, const struct proc_bsdinfo *__unused pbi) { /* this is the shared cache id, if any */ uuid_t sc_uuid; uuid_clear(sc_uuid); dyld_process_info dpi = NULL; if (opt->extended) { dpi = get_task_dyld_info(task); if (dpi) { get_sc_uuid(dpi, sc_uuid); } } /* this group is for LC_COREINFO */ mach_vm_offset_t dyld_addr = 0; // all_image_infos -or- dyld mach header mach_vm_offset_t aout_load_addr = 0; uuid_t aout_uuid; uuid_clear(aout_uuid); /* * Walk the address space */ int ecode = 0; struct regionhead *rhead = coredump_prepare(task, sc_uuid); if (NULL == rhead) { ecode = EX_OSERR; goto done; } if (OPTIONS_DEBUG(opt, 1)) printf("Optimizing dump content\n"); walk_region_list(rhead, simple_region_optimization, NULL); if (dpi) { /* * Snapshot dyld's info .. */ if (!libent_build_nametable(task, dpi)) warnx("error parsing dyld data => ignored"); else { /* * Find the a.out load address and uuid, and the dyld mach header for the coreinfo */ const struct libent *le; if (NULL != (le = libent_lookup_first_bytype(MH_EXECUTE))) { aout_load_addr = le->le_mhaddr; uuid_copy(aout_uuid, le->le_uuid); } if (NULL != (le = libent_lookup_first_bytype(MH_DYLINKER))) { dyld_addr = le->le_mhaddr; } /* * Use dyld's view of what's being used in the address * space to shrink the dump. */ if (OPTIONS_DEBUG(opt, 1)) printf("Decorating dump with dyld-derived data\n"); if (0 == walk_region_list(rhead, decorate_memory_region, (void *)dpi)) { if (OPTIONS_DEBUG(opt, 1)) printf("Sparse dump optimization(s)\n"); walk_region_list(rhead, sparse_region_optimization, NULL); } else { walk_region_list(rhead, undecorate_memory_region, NULL); warnx("error parsing dyld data => ignored"); } } free_task_dyld_info(dpi); } /* * Hunt for any memory mapped files that we can include by reference * Depending on whether the bsd part of the task is still present * we might be able to determine filenames of other regions mapping * them here - this allows fonts, images, and other read-only content * to be converted into file references, further reducing the size * of the dump. * * NOTE: Even though the corpse snapshots the VM, the filesystem is * not correspondingly snapshotted and thus may mutate while the dump * proceeds - so be pessimistic about inclusion. */ if (opt->extended && NULL != pbi) { if (OPTIONS_DEBUG(opt, 1)) printf("Mapped file optimization\n"); walk_region_list(rhead, label_mapped_files, (void *)pbi); } if (OPTIONS_DEBUG(opt, 1)) printf("Optimization(s) done\n"); done: if (0 == ecode) ecode = coredump_write(task, fd, rhead, aout_uuid, aout_load_addr, dyld_addr); return ecode; } struct find_shared_cache_args { task_t fsc_task; vm_object_id_t fsc_object_id; vm32_object_id_t fsc_region_object_id; uuid_t fsc_uuid; const struct libent *fsc_le; int fsc_fd; }; /* * This is "find the objid of the first shared cache" in the shared region. */ static walk_return_t find_shared_cache(struct region *r, void *arg) { struct find_shared_cache_args *fsc = arg; if (!r->r_insharedregion) return WALK_CONTINUE; /* wrong address range! */ if (0 != r->r_info.user_tag) return WALK_CONTINUE; /* must be tag zero */ if ((VM_PROT_READ | VM_PROT_EXECUTE) != r->r_info.protection || r->r_info.protection != r->r_info.max_protection) return WALK_CONTINUE; /* must be r-x / r-x */ if (r->r_pageinfo.offset != 0) return WALK_CONTINUE; /* must map beginning of file */ if (OPTIONS_DEBUG(opt, 1)) { hsize_str_t hstr; printr(r, "examining %s shared cache candidate\n", str_hsize(hstr, R_SIZE(r))); } struct copied_dyld_cache_header *ch; mach_msg_type_number_t chlen = sizeof (*ch); kern_return_t ret = mach_vm_read(fsc->fsc_task, R_ADDR(r), sizeof (*ch), (vm_offset_t *)&ch, &chlen); if (KERN_SUCCESS != ret) { err_mach(ret, NULL, "mach_vm_read() candidate shared region header"); return WALK_CONTINUE; } uuid_t scuuid; if (get_uuid_from_shared_cache_mapping(ch, chlen, scuuid) && uuid_compare(scuuid, fsc->fsc_uuid) == 0) { if (OPTIONS_DEBUG(opt, 1)) { uuid_string_t uustr; uuid_unparse_lower(fsc->fsc_uuid, uustr); printr(r, "found shared cache %s here\n", uustr); } if (!r->r_info.external_pager) { if (OPTIONS_DEBUG(opt, 1)) printf("Hmm. Found shared cache magic# + uuid, but not externally paged?\n"); #if 0 return WALK_CONTINUE; /* should be "paged" from a file */ #endif } // This is the ID associated with the first page of the mapping fsc->fsc_object_id = r->r_pageinfo.object_id; // This is the ID associated with the region fsc->fsc_region_object_id = r->r_info.object_id; } mach_vm_deallocate(mach_task_self(), (vm_offset_t)ch, chlen); if (fsc->fsc_object_id) { if (OPTIONS_DEBUG(opt, 3)) { uuid_string_t uu; uuid_unparse_lower(fsc->fsc_uuid, uu); printf("Shared cache objid %llx uuid %s\n", fsc->fsc_object_id, uu); } return WALK_TERMINATE; } return WALK_CONTINUE; } static bool compare_region_with_shared_cache(const struct region *r, struct find_shared_cache_args *fsc) { struct stat st; if (-1 == fstat(fsc->fsc_fd, &st)) { if (OPTIONS_DEBUG(opt, 1)) printr(r, "cannot fstat %s - %s\n", fsc->fsc_le->le_filename, strerror(errno)); return false; } void *file = mmap(NULL, R_SIZEOF(r), PROT_READ, MAP_PRIVATE, fsc->fsc_fd, r->r_pageinfo.offset); if ((void *)-1L == file) { if (OPTIONS_DEBUG(opt, 1)) printr(r, "mmap %s - %s\n", fsc->fsc_le->le_filename, strerror(errno)); return false; } madvise(file, R_SIZEOF(r), MADV_SEQUENTIAL); vm_offset_t data = 0; mach_msg_type_number_t data_count; const kern_return_t kr = mach_vm_read(fsc->fsc_task, R_ADDR(r), R_SIZE(r), &data, &data_count); if (KERN_SUCCESS != kr || data_count < R_SIZE(r)) { err_mach(kr, r, "mach_vm_read()"); munmap(file, R_SIZEOF(r)); return false; } mach_vm_size_t cmpsize = data_count; #ifdef RDAR_23744374 /* * Now we have the corresponding regions mapped, we should be * able to compare them. There's just one last twist that relates * to heterogenous pagesize systems: rdar://23744374 */ if (st.st_size < (off_t)(r->r_pageinfo.offset + cmpsize) && pageshift_host < pageshift_app) { /* * Looks like we're about to map close to the end of the object. * Check what's really mapped there and reduce the size accordingly. */ if (!is_actual_size(fsc->fsc_task, r, &cmpsize)) { if (OPTIONS_DEBUG(opt, 3)) printr(r, "narrowing the comparison (%llu " "-> %llu)\n", R_SIZE(r), cmpsize); } } #endif mach_vm_behavior_set(mach_task_self(), data, data_count, VM_BEHAVIOR_SEQUENTIAL); const bool thesame = memcmp(file, (void *)data, (size_t)cmpsize) == 0; #if 0 if (!thesame) { int diffcount = 0; int samecount = 0; const char *f = file; const char *d = (void *)data; for (mach_vm_size_t off = 0; off < cmpsize; off += 4096) { if (memcmp(f, d, 4096) != 0) { diffcount++; } else samecount++; f += 4096; d += 4096; } if (diffcount) printr(r, "%d of %d pages different\n", diffcount, diffcount + samecount); } #endif mach_vm_deallocate(mach_task_self(), data, data_count); munmap(file, R_SIZEOF(r)); if (!thesame && OPTIONS_DEBUG(opt, 3)) printr(r, "mapped file (%s) region is modified\n", fsc->fsc_le->le_filename); return thesame; } static walk_return_t label_shared_cache(struct region *r, void *arg) { struct find_shared_cache_args *fsc = arg; if (!r->r_insharedregion) return WALK_CONTINUE; if (!r->r_info.external_pager) return WALK_CONTINUE; if (r->r_pageinfo.object_id != fsc->fsc_object_id) { /* wrong object, or first page already modified */ return WALK_CONTINUE; } if (((r->r_info.protection | r->r_info.max_protection) & VM_PROT_WRITE) != 0) { /* potentially writable, but was it written? */ if (0 != r->r_info.pages_dirtied) return WALK_CONTINUE; if (0 != r->r_info.pages_swapped_out) return WALK_CONTINUE; if (0 != r->r_info.pages_resident && !r->r_info.external_pager) return WALK_CONTINUE; if (OPTIONS_DEBUG(opt, 1)) printr(r, "verifying shared cache content against memory image\n"); if (!compare_region_with_shared_cache(r, fsc)) { /* bits don't match */ if (OPTIONS_DEBUG(opt, 1)) printr(r, "hmm .. mismatch: using memory image\n"); return WALK_CONTINUE; } } /* * This mapped file segment will be represented as a reference * to the file, rather than as a copy of the mapped file. */ addfileref(r, libent_lookup_byuuid(fsc->fsc_uuid), NULL); return WALK_CONTINUE; } struct regionhead * coredump_prepare(task_t task, uuid_t sc_uuid) { struct regionhead *rhead = build_region_list(task); if (OPTIONS_DEBUG(opt, 2)) { printf("Region list built\n"); print_memory_region_header(); walk_region_list(rhead, region_print_memory, NULL); } if (uuid_is_null(sc_uuid)) return rhead; /* * Name the shared cache, if we can */ char *nm = shared_cache_filename(sc_uuid); const struct libent *le; if (NULL != nm) le = libent_insert(nm, sc_uuid, 0, NULL, NULL, 0); else { libent_insert("(anonymous shared cache)", sc_uuid, 0, NULL, NULL, 0); if (opt->verbose){ printf("Warning: cannot name the shared cache "); if (OPTIONS_DEBUG(opt, 1)) { uuid_string_t uustr; uuid_unparse_lower(sc_uuid, uustr); printf("(%s) ", uustr); } printf("- dump may be large!\n"); } return rhead; } if (opt->extended) { /* * See if we can replace entire regions with references to the shared cache * by looking at the VM meta-data about those regions. */ if (OPTIONS_DEBUG(opt, 1)) { uuid_string_t uustr; uuid_unparse_lower(sc_uuid, uustr); printf("Searching for shared cache with uuid %s\n", uustr); } /* * Identify the regions mapping the shared cache by comparing the UUID via * dyld with the UUID of likely-looking mappings in the right address range */ struct find_shared_cache_args fsca; bzero(&fsca, sizeof (fsca)); fsca.fsc_task = task; uuid_copy(fsca.fsc_uuid, sc_uuid); fsca.fsc_fd = -1; walk_region_list(rhead, find_shared_cache, &fsca); if (0 == fsca.fsc_object_id) { printf("Cannot identify the shared cache region(s) => ignored\n"); } else { if (opt->verbose) printf("Referenced %s\n", nm); fsca.fsc_le = le; fsca.fsc_fd = open(fsca.fsc_le->le_pathname, O_RDONLY); if (-1 == fsca.fsc_fd) errc(EX_SOFTWARE, errno, "open %s", fsca.fsc_le->le_pathname); else { walk_region_list(rhead, label_shared_cache, &fsca); close(fsca.fsc_fd); } free(nm); } } return rhead; }