darling-system_cmds/gcore.tproj/vanilla.c
2020-08-20 16:13:48 -04:00

912 lines
30 KiB
C

/*
* 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 <sys/types.h>
#include <sys/sysctl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <libproc.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <fcntl.h>
#include <assert.h>
#include <sysexits.h>
#include <mach/mach.h>
/*
* (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;
}