summaryrefslogtreecommitdiffstats
path: root/system_cmds/gcore.tproj
diff options
context:
space:
mode:
Diffstat (limited to 'system_cmds/gcore.tproj')
-rw-r--r--system_cmds/gcore.tproj/convert.c1117
-rw-r--r--system_cmds/gcore.tproj/convert.h24
-rw-r--r--system_cmds/gcore.tproj/corefile.c852
-rw-r--r--system_cmds/gcore.tproj/corefile.h71
-rw-r--r--system_cmds/gcore.tproj/dyld.c314
-rw-r--r--system_cmds/gcore.tproj/dyld.h35
-rw-r--r--system_cmds/gcore.tproj/dyld_shared_cache.c108
-rw-r--r--system_cmds/gcore.tproj/dyld_shared_cache.h36
-rw-r--r--system_cmds/gcore.tproj/gcore-entitlements.plist8
-rw-r--r--system_cmds/gcore.tproj/gcore-internal.1201
-rw-r--r--system_cmds/gcore.tproj/gcore.1105
-rw-r--r--system_cmds/gcore.tproj/loader_additions.h103
-rw-r--r--system_cmds/gcore.tproj/main.c863
-rw-r--r--system_cmds/gcore.tproj/options.h62
-rw-r--r--system_cmds/gcore.tproj/region.h133
-rw-r--r--system_cmds/gcore.tproj/sparse.c497
-rw-r--r--system_cmds/gcore.tproj/sparse.h72
-rw-r--r--system_cmds/gcore.tproj/threads.c81
-rw-r--r--system_cmds/gcore.tproj/threads.h14
-rw-r--r--system_cmds/gcore.tproj/utils.c421
-rw-r--r--system_cmds/gcore.tproj/utils.h42
-rw-r--r--system_cmds/gcore.tproj/vanilla.c911
-rw-r--r--system_cmds/gcore.tproj/vanilla.h17
-rw-r--r--system_cmds/gcore.tproj/vm.c493
-rw-r--r--system_cmds/gcore.tproj/vm.h47
25 files changed, 6627 insertions, 0 deletions
diff --git a/system_cmds/gcore.tproj/convert.c b/system_cmds/gcore.tproj/convert.c
new file mode 100644
index 0000000..b945733
--- /dev/null
+++ b/system_cmds/gcore.tproj/convert.c
@@ -0,0 +1,1117 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "convert.h"
+#include "corefile.h"
+#include "vanilla.h"
+#include "threads.h"
+#include "vm.h"
+#include "dyld_shared_cache.h"
+#include "utils.h"
+
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/queue.h>
+#include <sys/param.h>
+#include <mach-o/fat.h>
+#include <uuid/uuid.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <err.h>
+#include <sysexits.h>
+#include <time.h>
+#include <glob.h>
+#include <spawn.h>
+#include <signal.h>
+#include <xpc/xpc.h>
+#include <xpc/private.h>
+#include <sys/event.h>
+#include <sys/time.h>
+
+#if defined(CONFIG_GCORE_MAP) || defined(CONFIG_GCORE_CONV) || defined(CONFIG_GCORE_FREF)
+
+static const void *
+mmapfile(int fd, off_t off, off_t *filesize)
+{
+ struct stat st;
+ if (-1 == fstat(fd, &st))
+ errc(EX_OSERR, errno, "can't stat input file");
+
+ const size_t size = (size_t)(st.st_size - off);
+ if ((off_t)size != (st.st_size - off))
+ errc(EX_OSERR, EOVERFLOW, "input file too large?");
+
+ const void *addr = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, off);
+ if ((void *)-1 == addr)
+ errc(EX_OSERR, errno, "can't mmap input file");
+ *filesize = st.st_size;
+ return addr;
+}
+
+static void
+walkcore(
+ const native_mach_header_t *mh,
+ void (^coreinfo)(const struct proto_coreinfo_command *),
+ void (^frefdata)(const struct proto_fileref_command *),
+ void (^coredata)(const struct proto_coredata_command *),
+ void (^segdata)(const native_segment_command_t *),
+ void (^thrdata)(const struct thread_command *))
+{
+ const struct load_command *lc = (const void *)(mh + 1);
+ for (unsigned i = 0; i < mh->ncmds; i++) {
+ switch (lc->cmd) {
+ case proto_LC_COREINFO:
+ if (coreinfo)
+ coreinfo((const void *)lc);
+ break;
+ case proto_LC_FILEREF:
+ if (frefdata)
+ frefdata((const void *)lc);
+ break;
+ case proto_LC_COREDATA:
+ if (coredata)
+ coredata((const void *)lc);
+ break;
+ case NATIVE_LC_SEGMENT:
+ if (segdata)
+ segdata((const void *)lc);
+ break;
+ case LC_THREAD:
+ if (thrdata)
+ thrdata((const void *)lc);
+ break;
+ default:
+ break;
+ }
+ if (NULL == (lc = next_lc(lc)))
+ break;
+ }
+}
+
+#endif
+
+#ifdef CONFIG_GCORE_FREF
+
+int
+gcore_fref(int fd)
+{
+ off_t filesize;
+ const void *corebase = mmapfile(fd, 0, &filesize);
+
+ close(fd);
+ struct flist {
+ STAILQ_ENTRY(flist) f_linkage;
+ const char *f_nm;
+ unsigned long f_nmhash;
+ };
+ STAILQ_HEAD(flisthead, flist) __flh, *flh = &__flh;
+ STAILQ_INIT(flh);
+
+ walkcore(corebase, NULL, ^(const struct proto_fileref_command *fc) {
+ const char *nm = fc->filename.offset + (const char *)fc;
+ const unsigned long nmhash = simple_namehash(nm);
+ struct flist *f;
+ STAILQ_FOREACH(f, flh, f_linkage) {
+ if (nmhash == f->f_nmhash && 0 == strcmp(f->f_nm, nm))
+ return; /* skip duplicates */
+ }
+ struct flist *nf = calloc(1, sizeof (*nf));
+ nf->f_nm = nm;
+ nf->f_nmhash = nmhash;
+ STAILQ_INSERT_TAIL(flh, nf, f_linkage);
+ }, NULL, NULL, NULL);
+
+ struct flist *f, *tf;
+ STAILQ_FOREACH_SAFE(f, flh, f_linkage, tf) {
+ printf("%s\n", f->f_nm);
+ free(f);
+ f = NULL;
+ }
+
+ munmap((void *)corebase, (size_t)filesize);
+ return 0;
+}
+
+#endif /* CONFIG_GCORE_FREF */
+
+#ifdef CONFIG_GCORE_MAP
+
+/*
+ * A pale imitation of vmmap, but for core files
+ */
+int
+gcore_map(int fd)
+{
+ off_t filesize;
+ const void *corebase = mmapfile(fd, 0, &filesize);
+
+ __block int coreversion = 0;
+
+ walkcore(corebase, ^(const struct proto_coreinfo_command *ci) {
+ coreversion = ci->version;
+ }, NULL, NULL, NULL, NULL);
+
+ if (0 == coreversion) {
+ const char titlfmt[] = "%16s-%-16s [%7s] %3s/%3s\n";
+ const char *segcfmt = "%016llx-%016llx [%7s] %3s/%3s\n";
+
+ printf(titlfmt, "start ", " end", "vsize", "prt", "max");
+ walkcore(corebase, NULL, NULL, NULL, ^(const native_segment_command_t *sc) {
+ hsize_str_t vstr;
+ 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));
+ }, NULL);
+ } else {
+ const char titlfmt[] = "%-23s %16s-%-16s [%7s] %3s/%3s %6s %4s %-14s\n";
+ const char *freffmt = "%-23s %016llx-%016llx [%7s] %3s/%3s %6s %4s %-14s @%lld\n";
+ const char *datafmt = "%-23s %016llx-%016llx [%7s] %3s/%3s %6s %4s %-14s\n";
+
+ printf(titlfmt, "region type", "start ", " end", "vsize", "prt", "max", "shrmod", "purge", "region detail");
+ walkcore(corebase, NULL, ^(const struct proto_fileref_command *fc) {
+ const char *nm = fc->filename.offset + (const char *)fc;
+ tag_str_t tstr;
+ hsize_str_t vstr;
+ printf(freffmt, str_tag(tstr, fc->tag, fc->share_mode, fc->prot, fc->extp),
+ fc->vmaddr, fc->vmaddr + fc->vmsize,
+ str_hsize(vstr, fc->vmsize), str_prot(fc->prot),
+ str_prot(fc->maxprot), str_shared(fc->share_mode),
+ str_purgable(fc->purgable, fc->share_mode), nm, fc->fileoff);
+ }, ^(const struct proto_coredata_command *cc) {
+ tag_str_t tstr;
+ hsize_str_t vstr;
+ printf(datafmt, str_tag(tstr, cc->tag, cc->share_mode, cc->prot, cc->extp),
+ cc->vmaddr, cc->vmaddr + cc->vmsize,
+ str_hsize(vstr, cc->vmsize), str_prot(cc->prot),
+ str_prot(cc->maxprot), str_shared(cc->share_mode),
+ str_purgable(cc->purgable, cc->share_mode),
+ cc->vmsize && 0 == cc->filesize ? "(zfod)" : "");
+ }, ^(const native_segment_command_t *sc) {
+ hsize_str_t vstr;
+ printf(datafmt, "", (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), "", "",
+ sc->vmsize && 0 == sc->filesize ? "(zfod)" : "");
+ }, NULL);
+ }
+ close(fd);
+ munmap((void *)corebase, (size_t)filesize);
+ return 0;
+}
+
+#endif
+
+#ifdef CONFIG_GCORE_CONV
+
+/*
+ * Convert an input core file into an "old" format core file
+ * (a) convert all fileref segments into regular segments
+ * (b) uncompress anything we find compressed.
+ * This should be equivalent to a copy for an "old" format core file.
+ */
+
+static int
+machocmp(const native_mach_header_t *tmh, const native_mach_header_t *mh, const struct proto_fileref_command *fr)
+{
+ if (tmh->magic == mh->magic) {
+ const struct load_command *lc = (const void *)(tmh + 1);
+ for (unsigned i = 0; i < tmh->ncmds; i++) {
+ if (LC_UUID == lc->cmd && lc->cmdsize >= sizeof (struct uuid_command)) {
+ const struct uuid_command *uc = (const void *)lc;
+ return uuid_compare(uc->uuid, fr->id);
+ }
+ if (NULL == (lc = next_lc(lc)))
+ break;
+ }
+ }
+ return -1;
+}
+
+static int
+fat_machocmp(const struct fat_header *fh, const native_mach_header_t *mh, const struct proto_fileref_command *fr, off_t *reloff)
+{
+ const uint32_t (^get32)(uint32_t);
+
+ if (FAT_MAGIC == fh->magic) {
+ get32 = ^(uint32_t val) {
+ return val;
+ };
+ } else {
+ get32 = ^(uint32_t val) {
+ uint32_t result = 0;
+ for (unsigned i = 0; i < sizeof (uint32_t); i++)
+ ((uint8_t *)&result)[i] = ((uint8_t *)&val)[3-i];
+ return result;
+ };
+ }
+
+ assert(FAT_MAGIC == get32(fh->magic));
+ assert(kFREF_ID_UUID == FREF_ID_TYPE(fr->flags) && !uuid_is_null(fr->id));
+
+ const struct fat_arch *fa = (const struct fat_arch *)(fh + 1);
+ uint32_t narch = get32(fh->nfat_arch);
+ for (unsigned n = 0; n < narch; n++, fa++) {
+ const native_mach_header_t *tmh = (const void *)(((const char *)fh) + get32(fa->offset));
+ if (tmh->magic == mh->magic && 0 == machocmp(tmh, mh, fr)) {
+ *reloff = get32(fa->offset);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+struct output_info {
+ int oi_fd;
+ off_t oi_foffset;
+ bool oi_nocache;
+};
+
+static struct convstats {
+ int64_t copied;
+ int64_t added;
+ int64_t compressed;
+ int64_t uncompressed;
+} cstat, *cstats = &cstat;
+
+/*
+ * A fileref segment references a read-only file that contains pages from
+ * the image. The file may be a Mach binary or dylib identified with a uuid.
+ */
+static int
+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)
+{
+ assert(invr->addr == infr->vmaddr && invr->size == infr->vmsize);
+
+ struct stat st;
+ const int rfd = open(filename, O_RDONLY);
+ if (-1 == rfd || -1 == fstat(rfd, &st)) {
+ warnc(errno, "%s: open", filename);
+ return EX_IOERR;
+ }
+ const size_t rlen = (size_t)st.st_size;
+ void *raddr = mmap(NULL, rlen, PROT_READ, MAP_PRIVATE, rfd, 0);
+ if ((void *)-1 == raddr) {
+ warnc(errno, "%s: mmap", filename);
+ close(rfd);
+ return EX_IOERR;
+ }
+ close(rfd);
+
+ off_t fatoff = 0; /* for FAT objects */
+ int ecode = EX_DATAERR;
+
+ switch (FREF_ID_TYPE(infr->flags)) {
+ case kFREF_ID_UUID: {
+ /* file should be a mach binary: check that uuid matches */
+ const uint32_t magic = *(uint32_t *)raddr;
+ switch (magic) {
+ case FAT_MAGIC:
+ case FAT_CIGAM:
+ if (0 == fat_machocmp(raddr, inmh, infr, &fatoff))
+ ecode = 0;
+ break;
+ case NATIVE_MH_MAGIC:
+ if (0 == machocmp(raddr, inmh, infr))
+ ecode = 0;
+ break;
+ default: {
+ /*
+ * Maybe this is the shared cache?
+ */
+ uuid_t uu;
+ if (get_uuid_from_shared_cache_mapping(raddr, rlen, uu) && uuid_compare(uu, infr->id) == 0)
+ ecode = 0;
+ break;
+ }
+ }
+ break;
+ }
+ case kFREF_ID_MTIMESPEC_LE:
+ /* file should have the same mtime */
+ if (0 == memcmp(&st.st_mtimespec, infr->id, sizeof (infr->id)))
+ ecode = 0;
+ break;
+ case kFREF_ID_NONE:
+ /* file has no uniquifier, copy it anyway */
+ break;
+ }
+
+ if (0 != ecode) {
+ munmap(raddr, rlen);
+ warnx("%s doesn't match corefile content", filename);
+ return ecode;
+ }
+
+ const off_t fileoff = fatoff + infr->fileoff;
+ const void *start = (const char *)raddr + fileoff;
+ const size_t len = (size_t)infr->filesize;
+ void *zaddr = NULL;
+ size_t zlen = 0;
+
+ if (fileoff + (off_t)infr->filesize > (off_t)rlen) {
+ /*
+ * the file content needed (as described on machine with
+ * larger pagesize) extends beyond the end of the mapped
+ * file using our smaller pagesize. Zero pad it.
+ */
+ const size_t pagesize_host = 1ul << pageshift_host;
+ void *endaddr = (caddr_t)raddr + roundup(rlen, pagesize_host);
+ zlen = (size_t)(fileoff + infr->filesize - rlen);
+ zaddr = mmap(endaddr, zlen, PROT_READ, MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
+ if ((void *)-1 == zaddr) {
+ hsize_str_t hstr;
+ warnc(errno, "cannot zero-pad %s mapping for %s", str_hsize(hstr, zlen),filename);
+ munmap(raddr, rlen);
+ return EX_IOERR;
+ }
+ }
+
+ if (-1 == madvise((void *)start, len, MADV_SEQUENTIAL))
+ warnc(errno, "%s: madvise", filename);
+
+ const int error = bounded_pwrite(oi->oi_fd, start, len, oi->oi_foffset, &oi->oi_nocache, NULL);
+
+ if (zlen) {
+ if (-1 == munmap(zaddr, zlen))
+ warnc(errno, "%s: munmap zero pad", filename);
+ }
+ if (-1 == munmap(raddr, rlen))
+ warnc(errno, "%s: munmap", filename);
+ if (error) {
+ warnc(error, "while copying %s to core file", filename);
+ return EX_IOERR;
+ }
+
+ const struct file_range fr = {
+ .off = oi->oi_foffset,
+ .size = infr->filesize,
+ };
+ make_native_segment_command(lc, invr, &fr, infr->maxprot, infr->prot);
+ oi->oi_foffset += fr.size;
+ cstats->added += infr->filesize;
+ return 0;
+}
+
+/*
+ * expanduser tries to expand the leading '~' (if there is any) in the given
+ * path and returns a copy of the expanded path; it returns NULL on failures.
+ * The caller is responsible for freeing the returned string.
+ */
+static char *
+expanduser(const char *path)
+{
+ if (path == NULL) {
+ return NULL;
+ }
+ if (path[0] != '~') {
+ /*
+ * For consistency, still dup the string so that the caller always
+ * needs to free the string.
+ */
+ return strdup(path);
+ }
+
+ char *expanded = NULL;
+ glob_t globbuf = {};
+ if (OPTIONS_DEBUG(opt, 1)) {
+ printf("Expanding %s\n", path);
+ }
+ int rc = glob(path, GLOB_TILDE, NULL, &globbuf);
+ if (rc == 0) {
+ if (OPTIONS_DEBUG(opt, 3)) {
+ printf("expanduser - gl_pathc: %zu\n", globbuf.gl_pathc);
+ for (size_t i = 0; i < globbuf.gl_pathc; i++) {
+ printf("expanduser - gl_pathv[%zu]: %s\n", i, globbuf.gl_pathv[i]);
+ }
+ }
+ if (globbuf.gl_pathc == 1) {
+ expanded = strdup(globbuf.gl_pathv[0]);
+ if (OPTIONS_DEBUG(opt, 1)) {
+ printf("Expanded path: %s\n", expanded);
+ }
+ }
+ globfree(&globbuf);
+ }
+
+ return expanded;
+}
+
+#define RESPONSE_BUFF_SIZE (2048)
+
+/*
+ * read_response dynamically allocates buffer for reading bytes from the given
+ * fd. Upon success, this function sets response to point to the buffer and
+ * returns bytes being read; otherwise, it returns -1. The caller is
+ * responsible for freeing the response buffer.
+ */
+static ssize_t
+read_response(int fd, char **response)
+{
+ if (response == NULL || *response) {
+ warnx("Invalid response buffer pointer");
+ return -1;
+ }
+
+ ssize_t bytes_read = 0;
+ size_t buff_size = RESPONSE_BUFF_SIZE;
+
+ if (OPTIONS_DEBUG(opt, 3)) {
+ printf("Allocating response buffer (%zu)\n", buff_size);
+ }
+ char *buff = malloc(buff_size);
+ if (buff == NULL) {
+ warn("Failed to allocate response buffer (%zu)", buff_size);
+ return -1;
+ }
+
+ size_t total = 0;
+ bool failed = false;
+
+ do {
+ bytes_read = read(fd, buff + total, buff_size - total);
+ if (bytes_read == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ failed = true;
+ break;
+ }
+
+ total += (size_t)bytes_read;
+ if (total == buff_size) {
+ size_t new_buff_size = buff_size * 2;
+ if (OPTIONS_DEBUG(opt, 3)) {
+ printf("Reallocating response buffer (%zu)\n", new_buff_size);
+ }
+ char *new_buff = realloc(buff, new_buff_size);
+ if (new_buff == NULL) {
+ warn("Failed to reallocate response buffer (%zu)", new_buff_size);
+ failed = true;
+ break;
+ }
+ buff_size = new_buff_size;
+ buff = new_buff;
+ }
+ } while (bytes_read != 0);
+
+ if (failed) {
+ if (buff != NULL) {
+ free(buff);
+ }
+ return -1;
+ }
+
+ assert(total < buff_size);
+ buff[total] = '\0';
+ *response = buff;
+
+ return (ssize_t)total;
+}
+
+#define WAITPID_WTO_SIGALRM (100) /* alternative for SIGALRM for kevent timeout */
+#define WAITPID_WTO_SIGERR (101) /* sig for error when waiting for pid */
+
+/*
+ * waitpid_with_timeout returns true if the process exits successfully within
+ * timeout; otherwise, it returns false along with setting exitstatus and
+ * signal_no if the pointers are given.
+ */
+static bool
+waitpid_with_timeout(pid_t pid, int *exitstatus, int *signal_no, int timeout)
+{
+ int status;
+ int kq = -1;
+
+ if (timeout > 0) {
+ kq = kqueue();
+ struct kevent64_s event = {
+ .ident = (uint64_t)pid,
+ .filter = EVFILT_PROC,
+ .flags = EV_ADD | EV_ONESHOT,
+ .fflags = NOTE_EXIT
+ };
+ struct timespec tmout = {
+ .tv_sec = timeout
+ };
+ int ret = kevent64(kq, &event, 1, &event, 1, 0, &tmout);
+ int kevent64_errno = errno;
+
+ close(kq);
+ if (ret == 0) { /* timeout */
+ if (exitstatus) {
+ *exitstatus = 0;
+ }
+ if (signal_no) {
+ *signal_no = WAITPID_WTO_SIGALRM;
+ }
+ return false;
+ }
+
+ if (ret == -1) {
+ warnx("kevent64(): errno=%d (%s)\n", kevent64_errno, strerror(kevent64_errno));
+ goto waitpid_error;
+ }
+
+ if (event.flags == EV_ERROR && event.data != ESRCH) {
+ warnx("event.data (%lld) is not ESRCH when event.flags is EV_ERROR\n", event.data);
+ goto waitpid_error;
+ }
+
+ if (event.ident != (uint64_t)pid) {
+ warnx("event.ident is %lld (should be pid %d)\n", event.ident, pid);
+ goto waitpid_error;
+ }
+
+ if (event.filter != EVFILT_PROC) {
+ warnx("event.filter (%d) is not EVFILT_PROC\n", event.filter);
+ goto waitpid_error;
+ }
+ }
+
+ while (waitpid(pid, &status, 0) < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+ warnx("waitpid(): errno=%d (%s)\n", errno, strerror(errno));
+ goto waitpid_error;
+ }
+ if (WIFEXITED(status)) {
+ if (exitstatus) {
+ *exitstatus = WEXITSTATUS(status);
+ }
+ if (signal_no) {
+ *signal_no = 0;
+ }
+ return WEXITSTATUS(status) == 0;
+ }
+ if (WIFSIGNALED(status)) {
+ if (exitstatus) {
+ *exitstatus = 0;
+ }
+ if (signal_no) {
+ *signal_no = WTERMSIG(status);
+ }
+ return false;
+ }
+
+waitpid_error:
+ if (exitstatus) *exitstatus = 0;
+ if (signal_no) *signal_no = WAITPID_WTO_SIGERR;
+ return false;
+}
+
+#define DSYMFORUUID_PATH "/usr/local/bin/dsymForUUID"
+
+/*
+ * exec_dsymForUUID spawns dsymForUUID to query dsym UUID info and responds the
+ * result plist. Upon success, this function sets response point to the buffer
+ * and returns bytes being read; otherwise, it returns -1. The caller is
+ * responsible for freeing the response buffer.
+ */
+static ssize_t
+exec_dsymForUUID(uuid_string_t id, char **response)
+{
+ int pipe_fds[2] = {-1, -1};
+ bool file_actions_inited = false;
+ ssize_t bytes_read = -1;
+ int rc;
+
+ rc = pipe(pipe_fds);
+ if (rc == -1) {
+ goto cleanup;
+ }
+
+ posix_spawn_file_actions_t file_actions;
+ rc = posix_spawn_file_actions_init(&file_actions);
+ if (rc) {
+ goto cleanup;
+ }
+ file_actions_inited = true;
+
+ rc = posix_spawn_file_actions_addclose(&file_actions, pipe_fds[0]);
+ if (rc) {
+ goto cleanup;
+ }
+
+ rc = posix_spawn_file_actions_adddup2(&file_actions, pipe_fds[1], STDOUT_FILENO);
+ if (rc) {
+ goto cleanup;
+ }
+
+ rc = posix_spawn_file_actions_addclose(&file_actions, pipe_fds[1]);
+ if (rc) {
+ goto cleanup;
+ }
+
+ char *command[] = {DSYMFORUUID_PATH, id, NULL};
+ pid_t child;
+ rc = posix_spawn(&child, command[0], &file_actions, NULL, command, NULL);
+ if (rc) {
+ goto cleanup;
+ }
+
+ close(pipe_fds[1]);
+ pipe_fds[1] = -1;
+
+ bytes_read = read_response(pipe_fds[0], response);
+
+ waitpid_with_timeout(child, NULL, NULL, 3);
+
+cleanup:
+ if (pipe_fds[1] != -1) {
+ close(pipe_fds[1]);
+ }
+ if (pipe_fds[0] != -1) {
+ close(pipe_fds[0]);
+ }
+ if (file_actions_inited) {
+ posix_spawn_file_actions_destroy(&file_actions);
+ }
+
+ return bytes_read;
+}
+
+/*
+ * get_symbol_rich_executable_path_via_dsymForUUID spawns dsymForUUID to query
+ * dsym uuid info for the given uuid and returns the string of
+ * DBGSymbolRichExecutable; otherwise, it returns NULL on failures. The caller
+ * is responsible for freeing the returned string.
+ */
+static char *
+get_symbol_rich_executable_path_via_dsymForUUID(const uuid_t uuid)
+{
+ char *response;
+ ssize_t size;
+ uuid_string_t uuid_str;
+ xpc_object_t plist = NULL;
+ xpc_object_t uuid_info = NULL;
+ xpc_object_t exec_path = NULL;
+ char *expanded_exec_path = NULL;
+
+ uuid_unparse_upper(uuid, uuid_str);
+
+ size = exec_dsymForUUID(uuid_str, &response);
+ if (size <= 0) {
+ goto cleanup;
+ }
+
+ if (OPTIONS_DEBUG(opt, 3)) {
+ printf("dsymForUUID response:\n%s\n", response);
+ }
+
+ plist = xpc_create_from_plist(response, (size_t)size);
+ if (plist == NULL) {
+ goto cleanup;
+ }
+ if (xpc_get_type(plist) != XPC_TYPE_DICTIONARY) {
+ goto cleanup;
+ }
+
+ uuid_info = xpc_dictionary_get_value(plist, uuid_str);
+ if (uuid_info == NULL) {
+ goto cleanup;
+ }
+ if (xpc_get_type(uuid_info) != XPC_TYPE_DICTIONARY) {
+ goto cleanup;
+ }
+
+ exec_path = xpc_dictionary_get_value(uuid_info, "DBGSymbolRichExecutable");
+ if (exec_path == NULL) {
+ goto cleanup;
+ }
+ if (xpc_get_type(exec_path) != XPC_TYPE_STRING) {
+ goto cleanup;
+ }
+
+ expanded_exec_path = expanduser(xpc_string_get_string_ptr(exec_path));
+
+cleanup:
+ if (plist) {
+ xpc_release(plist);
+ }
+ if (response) {
+ free(response);
+ }
+
+ return expanded_exec_path;
+}
+
+/*
+ * bind the file reference into the output core file.
+ * filename optionally prefixed with names from a ':'-separated PATH variable
+ */
+static int
+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)
+{
+ const char *nm = infr->filename.offset + (const char *)infr;
+ uuid_string_t uustr;
+ const struct vm_range invr = {
+ .addr = infr->vmaddr,
+ .size = infr->vmsize,
+ };
+
+ if (opt->verbose) {
+ hsize_str_t hstr;
+ printvr(&invr, "adding %s from '%s'",
+ str_hsize(hstr, (off_t)infr->filesize), nm);
+ switch (FREF_ID_TYPE(infr->flags)) {
+ case kFREF_ID_NONE:
+ break;
+ case kFREF_ID_UUID:
+ uuid_unparse_lower(infr->id, uustr);
+ printf(" (%s)", uustr);
+ break;
+ case kFREF_ID_MTIMESPEC_LE: {
+ struct timespec mts;
+ struct tm tm;
+ char tbuf[4 + 2 + 2 + 2 + 2 + 1 + 2 + 1]; /* touch -t */
+ memcpy(&mts, &infr->id, sizeof (mts));
+ localtime_r(&mts.tv_sec, &tm);
+ strftime(tbuf, sizeof (tbuf), "%Y%m%d%H%M.%S", &tm);
+ printf(" (%s)", tbuf);
+ } break;
+ }
+ printf("\n");
+ }
+
+ int ecode = 0;
+ if (opt->dsymforuuid && (FREF_ID_TYPE(infr->flags) == kFREF_ID_UUID)) {
+ /* Try to use dsymForUUID to get the symbol-rich executable */
+ char *symrich_filepath = get_symbol_rich_executable_path_via_dsymForUUID(infr->id);
+ if (symrich_filepath) {
+ if (opt->verbose) {
+ printf("\tTrying %s from dsymForUUID\n", symrich_filepath);
+ }
+ ecode = convert_fileref_with_file(symrich_filepath, inmh, infr, &invr, lc, oi);
+ free(symrich_filepath);
+ if (ecode == 0) {
+ return (ecode);
+ }
+ warnx("Failed to convert fileref with dsymForUUID. Fall back to local paths");
+ }
+ }
+
+ const size_t pathsize = path ? strlen(path) : 0;
+ ecode = EX_DATAERR;
+ if (0 == pathsize)
+ ecode = convert_fileref_with_file(nm, inmh, infr, &invr, lc, oi);
+ else {
+ /* search the : separated path (-L) for possible matches */
+ char *pathcopy = strdup(path);
+ char *searchpath = pathcopy;
+ const char *token;
+
+ while ((token = strsep(&searchpath, ":")) != NULL) {
+ const size_t buflen = strlen(token) + 1 + strlen(nm) + 1;
+ char *buf = malloc(buflen);
+ snprintf(buf, buflen, "%s%s%s", token, '/' == nm[0] ? "" : "/", nm);
+ if (opt->verbose)
+ printf("\tTrying '%s'", buf);
+ if (0 == access(buf, R_OK)) {
+ if (opt->verbose)
+ printf("\n");
+ ecode = convert_fileref_with_file(buf, inmh, infr, &invr, lc, oi);
+ if (0 == ecode) {
+ free(buf);
+ break;
+ }
+ } else if (opt->verbose)
+ printf(": %s.\n",
+ 0 == access(buf, F_OK) ? "Unreadable" : "Not present");
+ free(buf);
+ }
+ free(pathcopy);
+ }
+
+ if (0 != ecode && zf) {
+ /*
+ * Failed to find the file reference. If this was a fileref that uses
+ * a file metadata tagging method (e.g. mtime), allow the user to subsitute a
+ * zfod region: assumes that it's better to have something to debug
+ * vs. nothing. UUID-tagged filerefs are Mach-O tags, and are
+ * assumed to be never substitutable.
+ */
+ switch (FREF_ID_TYPE(infr->flags)) {
+ case kFREF_ID_NONE:
+ case kFREF_ID_MTIMESPEC_LE: { // weak tagging, allow zfod substitution
+ const struct file_range outfr = {
+ .off = oi->oi_foffset,
+ .size = 0,
+ };
+ if (opt->verbose)
+ printf("\tWARNING: no file matched. Missing content is now zfod\n");
+ else
+ printvr(&invr, "WARNING: missing content (%s) now zfod\n", nm);
+ make_native_segment_command(lc, &invr, &outfr, infr->maxprot, infr->prot);
+ ecode = 0;
+ } break;
+ default:
+ break;
+ }
+ }
+
+ return (ecode);
+}
+
+static int
+segment_uncompflags(unsigned algnum, compression_algorithm *ca)
+{
+ switch (algnum) {
+ case kCOMP_LZ4:
+ *ca = COMPRESSION_LZ4;
+ break;
+ case kCOMP_ZLIB:
+ *ca = COMPRESSION_ZLIB;
+ break;
+ case kCOMP_LZMA:
+ *ca = COMPRESSION_LZMA;
+ break;
+ case kCOMP_LZFSE:
+ *ca = COMPRESSION_LZFSE;
+ break;
+ default:
+ warnx("unknown compression flavor %d", algnum);
+ return EX_DATAERR;
+ }
+ return 0;
+}
+
+static int
+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)
+{
+ int ecode = 0;
+
+ if (F_SIZE(infr)) {
+ void *input = (const caddr_t)inbase + F_OFF(infr);
+ void *buf;
+
+ if (0 == flavor) {
+ buf = input;
+ if (opt->verbose) {
+ hsize_str_t hstr;
+ printvr(invr, "copying %s\n", str_hsize(hstr, F_SIZE(infr)));
+ }
+ } else {
+ compression_algorithm ca;
+
+ if (0 != (ecode = segment_uncompflags(flavor, &ca)))
+ return ecode;
+ if (opt->verbose) {
+ hsize_str_t hstr1, hstr2;
+ printvr(invr, "uncompressing %s to %s\n",
+ str_hsize(hstr1, F_SIZE(infr)), str_hsize(hstr2, V_SIZE(invr)));
+ }
+ const size_t buflen = V_SIZEOF(invr);
+ buf = malloc(buflen);
+ const size_t dstsize = compression_decode_buffer(buf, buflen, input, (size_t)F_SIZE(infr), NULL, ca);
+ if (buflen != dstsize) {
+ warnx("failed to uncompress segment");
+ free(buf);
+ return EX_DATAERR;
+ }
+ cstats->compressed += F_SIZE(infr);
+ }
+ const int error = bounded_pwrite(oi->oi_fd, buf, V_SIZEOF(invr), oi->oi_foffset, &oi->oi_nocache, NULL);
+ if (error) {
+ warnc(error, "failed to write data to core file");
+ ecode = EX_IOERR;
+ }
+ if (buf != input)
+ free(buf);
+ if (ecode)
+ return ecode;
+
+ const struct file_range outfr = {
+ .off = oi->oi_foffset,
+ .size = V_SIZE(invr),
+ };
+ make_native_segment_command(lc, invr, &outfr, maxprot, prot);
+ oi->oi_foffset += outfr.size;
+
+ if (0 == flavor)
+ cstats->copied += outfr.size;
+ else
+ cstats->uncompressed += outfr.size;
+ } else {
+ if (opt->verbose) {
+ hsize_str_t hstr;
+ printvr(invr, "%s remains zfod\n", str_hsize(hstr, V_SIZE(invr)));
+ }
+ const struct file_range outfr = {
+ .off = oi->oi_foffset,
+ .size = 0,
+ };
+ make_native_segment_command(lc, invr, &outfr, maxprot, prot);
+ }
+ return ecode;
+}
+
+static int
+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)
+{
+ const struct vm_range vr = {
+ .addr = cc->vmaddr,
+ .size = cc->vmsize,
+ };
+ const struct file_range fr = {
+ .off = cc->fileoff,
+ .size = cc->filesize,
+ };
+ return convert_region(inbase, &vr, &fr, cc->prot, cc->maxprot, COMP_ALG_TYPE(cc->flags), lc, oi);
+}
+
+static int
+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)
+{
+ const struct vm_range vr = {
+ .addr = sc->vmaddr,
+ .size = sc->vmsize,
+ };
+ const struct file_range fr = {
+ .off = sc->fileoff,
+ .size = sc->filesize,
+ };
+ return convert_region(inbase, &vr, &fr, sc->initprot, sc->maxprot, 0, lc, oi);
+}
+
+/* pass-through - content is all in the header */
+
+static int
+convert_thread(struct thread_command *dst, const struct thread_command *src)
+{
+ assert(LC_THREAD == src->cmd);
+ memcpy(dst, src, src->cmdsize);
+ cstats->copied += src->cmdsize;
+ return 0;
+}
+
+int
+gcore_conv(int infd, const char *searchpath, bool zf, int fd)
+{
+ off_t filesize;
+ const void *corebase = mmapfile(infd, 0, &filesize);
+ close(infd);
+ /*
+ * Check to see if the input file is "sane" as far as we're concerned.
+ * XXX Note that this -won't- necessarily work for other ISAs than
+ * our own!
+ */
+ const native_mach_header_t *inmh = corebase;
+ validate_core_header(inmh, filesize);
+
+ /*
+ * The sparse file may have created many more segments, but there's no
+ * attempt to change their numbers here. Just count all the segment
+ * types needed to figure out the size of the output file header.
+ *
+ * (Size assertions to be deleted once data structures stable!)
+ */
+ __block size_t headersize = sizeof (native_mach_header_t);
+ __block unsigned pageshift_target = pageshift_host;
+
+ walkcore(inmh, ^(const struct proto_coreinfo_command *ci) {
+ assert(sizeof (*ci) == ci->cmdsize);
+ if (opt->verbose)
+ printf("Converting version %d core file to pre-versioned format\n", ci->version);
+ if (0 < ci->pageshift && ci->pageshift < 31)
+ pageshift_target = ci->pageshift;
+ else if (CPU_TYPE_ARM64 == inmh->cputype)
+ pageshift_target = 14; // compatibility hack, should go soon
+ }, ^(const struct proto_fileref_command *__unused fc) {
+ const char *nm = fc->filename.offset + (const char *)fc;
+ size_t nmlen = strlen(nm) + 1;
+ size_t cmdsize = sizeof (*fc) + roundup(nmlen, sizeof (long));
+ assert(cmdsize == fc->cmdsize);
+
+ headersize += sizeof (native_segment_command_t);
+ }, ^(const struct proto_coredata_command *__unused cc) {
+ assert(sizeof (*cc) == cc->cmdsize);
+ headersize += sizeof (native_segment_command_t);
+ }, ^(const native_segment_command_t *sc) {
+ headersize += sc->cmdsize;
+ }, ^(const struct thread_command *tc) {
+ headersize += tc->cmdsize;
+ });
+
+ void *header = calloc(1, headersize);
+ if (NULL == header)
+ errx(EX_OSERR, "out of memory for header");
+
+ native_mach_header_t *mh = memcpy(header, inmh, sizeof (*mh));
+ mh->ncmds = 0;
+ mh->sizeofcmds = 0;
+
+ assert(0 < pageshift_target && pageshift_target < 31);
+ const vm_offset_t pagesize_target = ((vm_offset_t)1 << pageshift_target);
+ const vm_offset_t pagemask_target = pagesize_target - 1;
+
+ const struct load_command *inlc = (const void *)(inmh + 1);
+ struct load_command *lc = (void *)(mh + 1);
+ int ecode = 0;
+
+ struct output_info oi = {
+ .oi_fd = fd,
+ .oi_foffset = ((vm_offset_t)headersize + pagemask_target) & ~pagemask_target,
+ .oi_nocache = false,
+ };
+
+ for (unsigned i = 0; i < inmh->ncmds; i++) {
+ switch (inlc->cmd) {
+ case proto_LC_FILEREF:
+ ecode = convert_fileref(searchpath, zf, inmh, (const void *)inlc, lc, &oi);
+ break;
+ case proto_LC_COREDATA:
+ ecode = convert_coredata(corebase, inmh, (const void *)inlc, lc, &oi);
+ break;
+ case NATIVE_LC_SEGMENT:
+ ecode = convert_segment(corebase, inmh, (const void *)inlc, lc, &oi);
+ break;
+ case LC_THREAD:
+ ecode = convert_thread((void *)lc, (const void *)inlc);
+ break;
+ default:
+ if (OPTIONS_DEBUG(opt, 1))
+ printf("discarding load command %d\n", inlc->cmd);
+ break;
+ }
+ if (0 != ecode)
+ break;
+ if (NATIVE_LC_SEGMENT == lc->cmd || LC_THREAD == lc->cmd) {
+ mach_header_inc_ncmds(mh, 1);
+ mach_header_inc_sizeofcmds(mh, lc->cmdsize);
+ lc = (void *)next_lc(lc);
+ }
+ if (NULL == (inlc = next_lc(inlc)))
+ break;
+ }
+
+ /*
+ * Even if we've encountered an error, try and write out the header
+ */
+ if (0 != bounded_pwrite(fd, header, headersize, 0, &oi.oi_nocache, NULL))
+ ecode = EX_IOERR;
+ if (0 == ecode && sizeof (*mh) + mh->sizeofcmds != headersize)
+ ecode = EX_SOFTWARE;
+ validate_core_header(mh, oi.oi_foffset);
+ if (ecode)
+ warnx("failed to write new core file correctly");
+ else if (opt->verbose) {
+ hsize_str_t hstr;
+ printf("Conversion complete: %s copied", str_hsize(hstr, cstats->copied));
+ const int64_t delta = cstats->uncompressed - cstats->compressed;
+ if (delta > 0)
+ printf(", %s uncompressed", str_hsize(hstr, delta));
+ const int64_t added = cstats->added + ((int)mh->sizeofcmds - (int)inmh->sizeofcmds);
+ if (added > 0)
+ printf(", %s added", str_hsize(hstr, added));
+ printf("\n");
+ }
+ free(header);
+ munmap((void *)corebase, (size_t)filesize);
+ return ecode;
+}
+#endif
diff --git a/system_cmds/gcore.tproj/convert.h b/system_cmds/gcore.tproj/convert.h
new file mode 100644
index 0000000..03854b8
--- /dev/null
+++ b/system_cmds/gcore.tproj/convert.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "options.h"
+
+#include <stdbool.h>
+
+#ifndef _CONVERT_H
+#define _CONVERT_H
+
+#ifdef CONFIG_GCORE_FREF
+extern int gcore_fref(int);
+#endif
+
+#ifdef CONFIG_GCORE_MAP
+extern int gcore_map(int);
+#endif
+
+#ifdef CONFIG_GCORE_CONV
+extern int gcore_conv(int, const char *, bool, int);
+#endif
+
+#endif /* _CONVERT_H */
diff --git a/system_cmds/gcore.tproj/corefile.c b/system_cmds/gcore.tproj/corefile.c
new file mode 100644
index 0000000..b1e4421
--- /dev/null
+++ b/system_cmds/gcore.tproj/corefile.c
@@ -0,0 +1,852 @@
+/*
+ * Copyright (c) 2016-2018 Apple Inc. All rights reserved.
+ */
+
+#include "options.h"
+#include "corefile.h"
+#include "sparse.h"
+#include "utils.h"
+#include "vm.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <compression.h>
+#include <sys/param.h>
+#include <libgen.h>
+#include <sys/stat.h>
+
+native_mach_header_t *
+make_corefile_mach_header(void *data)
+{
+ native_mach_header_t *mh = data;
+ mh->magic = NATIVE_MH_MAGIC;
+ mh->filetype = MH_CORE;
+#if defined(__LP64__)
+ const int is64 = 1;
+#else
+ const int is64 = 0;
+#endif
+#if defined(__i386__) || defined(__x86_64__)
+ mh->cputype = is64 ? CPU_TYPE_X86_64 : CPU_TYPE_I386;
+ mh->cpusubtype = is64 ? CPU_SUBTYPE_X86_64_ALL : CPU_SUBTYPE_I386_ALL;
+#elif defined(__arm__) || defined(__arm64__)
+ mh->cputype = is64 ? CPU_TYPE_ARM64 : CPU_TYPE_ARM;
+ mh->cpusubtype = is64 ? CPU_SUBTYPE_ARM64_ALL : CPU_SUBTYPE_ARM_ALL;
+#else
+#error undefined
+#endif
+ return mh;
+}
+
+struct proto_coreinfo_command *
+make_coreinfo_command(native_mach_header_t *mh, void *data, const uuid_t aoutid, uint64_t address, uint64_t dyninfo)
+{
+ struct proto_coreinfo_command *cc = data;
+ cc->cmd = proto_LC_COREINFO;
+ cc->cmdsize = sizeof (*cc);
+ cc->version = 1;
+ cc->type = proto_CORETYPE_USER;
+ cc->pageshift = (uint16_t)pageshift_host;
+ cc->address = address;
+ uuid_copy(cc->uuid, aoutid);
+ cc->dyninfo = dyninfo;
+ mach_header_inc_ncmds(mh, 1);
+ mach_header_inc_sizeofcmds(mh, cc->cmdsize);
+ return cc;
+}
+
+native_segment_command_t *
+make_native_segment_command(void *data, const struct vm_range *vr, const struct file_range *fr, vm_prot_t maxprot, vm_prot_t initprot)
+{
+ native_segment_command_t *sc = data;
+ sc->cmd = NATIVE_LC_SEGMENT;
+ sc->cmdsize = sizeof (*sc);
+ assert(V_SIZE(vr));
+ sc->vmaddr = (unsigned long)V_ADDR(vr);
+ sc->vmsize = (unsigned long)V_SIZE(vr);
+ sc->fileoff = (unsigned long)F_OFF(fr);
+ sc->filesize = (unsigned long)F_SIZE(fr);
+ sc->maxprot = maxprot;
+ sc->initprot = initprot;
+ sc->nsects = 0;
+ sc->flags = 0;
+ return sc;
+}
+
+static struct proto_coredata_command *
+make_coredata_command(void *data, const struct vm_range *vr, const struct file_range *fr, const vm_region_submap_info_data_64_t *info, unsigned comptype, unsigned purgable)
+{
+ struct proto_coredata_command *cc = data;
+ cc->cmd = proto_LC_COREDATA;
+ cc->cmdsize = sizeof (*cc);
+ assert(V_SIZE(vr));
+ cc->vmaddr = V_ADDR(vr);
+ cc->vmsize = V_SIZE(vr);
+ cc->fileoff = F_OFF(fr);
+ cc->filesize = F_SIZE(fr);
+ cc->maxprot = info->max_protection;
+ cc->prot = info->protection;
+ cc->flags = COMP_MAKE_FLAGS(comptype);
+ cc->share_mode = info->share_mode;
+ assert(purgable <= UINT8_MAX);
+ cc->purgable = (uint8_t)purgable;
+ assert(info->user_tag <= UINT8_MAX);
+ cc->tag = (uint8_t)info->user_tag;
+ cc->extp = info->external_pager;
+ return cc;
+}
+
+static size_t
+sizeof_segment_command(void) {
+ return opt->extended ?
+ sizeof (struct proto_coredata_command) : sizeof (native_segment_command_t);
+}
+
+static struct load_command *
+make_segment_command(void *data, const struct vm_range *vr, const struct file_range *fr, const vm_region_submap_info_data_64_t *info, unsigned comptype, int purgable)
+{
+ if (opt->extended)
+ make_coredata_command(data, vr, fr, info, comptype, purgable);
+ else
+ make_native_segment_command(data, vr, fr, info->max_protection, info->protection);
+ return data;
+}
+
+/*
+ * Increment the mach-o header data when we succeed
+ */
+static void
+commit_load_command(struct write_segment_data *wsd, const struct load_command *lc)
+{
+ wsd->wsd_lc = (caddr_t)lc + lc->cmdsize;
+ native_mach_header_t *mh = wsd->wsd_mh;
+ mach_header_inc_ncmds(mh, 1);
+ mach_header_inc_sizeofcmds(mh, lc->cmdsize);
+}
+
+#pragma mark -- Regions written as "file references" --
+
+static size_t
+cmdsize_fileref_command(const char *nm)
+{
+ size_t cmdsize = sizeof (struct proto_fileref_command);
+ size_t len;
+ if (0 != (len = strlen(nm))) {
+ len++; // NUL-terminated for mmap sanity
+ cmdsize += roundup(len, sizeof (long));
+ }
+ return cmdsize;
+}
+
+static void
+size_fileref_subregion(const struct subregion *s, struct size_core *sc)
+{
+ assert(S_LIBENT(s));
+
+ size_t cmdsize = cmdsize_fileref_command(S_PATHNAME(s));
+ sc->headersize += cmdsize;
+ sc->count++;
+ sc->memsize += S_SIZE(s);
+}
+
+static void
+size_fileref_region(const struct region *r, struct size_core *sc)
+{
+ assert(0 == r->r_nsubregions);
+ assert(!r->r_inzfodregion);
+
+ size_t cmdsize = cmdsize_fileref_command(r->r_fileref->fr_pathname);
+ sc->headersize += cmdsize;
+ sc->count++;
+ sc->memsize += R_SIZE(r);
+}
+
+static struct proto_fileref_command *
+make_fileref_command(void *data, const char *pathname, const uuid_t uuid,
+ const struct vm_range *vr, const struct file_range *fr,
+ const vm_region_submap_info_data_64_t *info, unsigned purgable)
+{
+ struct proto_fileref_command *fc = data;
+ size_t len;
+
+ fc->cmd = proto_LC_FILEREF;
+ fc->cmdsize = sizeof (*fc);
+ if (0 != (len = strlen(pathname))) {
+ /*
+ * Strings live immediately after the
+ * command, and are included in the cmdsize
+ */
+ fc->filename.offset = sizeof (*fc);
+ void *s = fc + 1;
+ strlcpy(s, pathname, ++len); // NUL-terminated for mmap sanity
+ fc->cmdsize += roundup(len, sizeof (long));
+ assert(cmdsize_fileref_command(pathname) == fc->cmdsize);
+ }
+
+ /*
+ * A file reference allows different kinds of identifiers for
+ * the reference to be reconstructed.
+ */
+ assert(info->external_pager);
+
+ if (!uuid_is_null(uuid)) {
+ uuid_copy(fc->id, uuid);
+ fc->flags = FREF_MAKE_FLAGS(kFREF_ID_UUID);
+ } else {
+ struct stat st;
+ if (-1 != stat(pathname, &st) && 0 != st.st_mtimespec.tv_sec) {
+ /* "little-endian format timespec structure" */
+ struct timespec ts = st.st_mtimespec;
+ ts.tv_nsec = 0; // allow touch(1) to fix things
+ memset(fc->id, 0, sizeof(fc->id));
+ memcpy(fc->id, &ts, sizeof(ts));
+ fc->flags = FREF_MAKE_FLAGS(kFREF_ID_MTIMESPEC_LE);
+ } else
+ fc->flags = FREF_MAKE_FLAGS(kFREF_ID_NONE);
+ }
+
+ fc->vmaddr = V_ADDR(vr);
+ assert(V_SIZE(vr));
+ fc->vmsize = V_SIZE(vr);
+
+ assert(F_OFF(fr) >= 0);
+ fc->fileoff = F_OFF(fr);
+ fc->filesize = F_SIZE(fr);
+
+ assert(info->max_protection & VM_PROT_READ);
+ fc->maxprot = info->max_protection;
+ fc->prot = info->protection;
+
+ fc->share_mode = info->share_mode;
+ assert(purgable <= UINT8_MAX);
+ fc->purgable = (uint8_t)purgable;
+ assert(info->user_tag <= UINT8_MAX);
+ fc->tag = (uint8_t)info->user_tag;
+ fc->extp = info->external_pager;
+ return fc;
+}
+
+/*
+ * It's almost always more efficient to write out a reference to the
+ * data than write out the data itself.
+ */
+static walk_return_t
+write_fileref_subregion(const struct region *r, const struct subregion *s, struct write_segment_data *wsd)
+{
+ assert(S_LIBENT(s));
+ if (OPTIONS_DEBUG(opt, 1) && !issubregiontype(s, SEG_TEXT) && !issubregiontype(s, SEG_LINKEDIT))
+ printf("%s: unusual segment type %s from %s\n", __func__, S_MACHO_TYPE(s), S_FILENAME(s));
+ assert((r->r_info.max_protection & VM_PROT_READ) == VM_PROT_READ);
+ assert((r->r_info.protection & VM_PROT_WRITE) == 0);
+
+ const struct libent *le = S_LIBENT(s);
+ const struct file_range fr = {
+ .off = S_MACHO_FILEOFF(s),
+ .size = S_SIZE(s),
+ };
+ const struct proto_fileref_command *fc = make_fileref_command(wsd->wsd_lc, le->le_pathname, le->le_uuid, S_RANGE(s), &fr, &r->r_info, r->r_purgable);
+
+ commit_load_command(wsd, (const void *)fc);
+ if (OPTIONS_DEBUG(opt, 3)) {
+ hsize_str_t hstr;
+ printr(r, "ref '%s' %s (vm %llx-%llx, file offset %lld for %s)\n", S_FILENAME(s), S_MACHO_TYPE(s), (uint64_t)fc->vmaddr, (uint64_t)fc->vmaddr + fc->vmsize, (int64_t)fc->fileoff, str_hsize(hstr, fc->filesize));
+ }
+ return WALK_CONTINUE;
+}
+
+/*
+ * Note that we may be asked to write reference segments whose protections
+ * are rw- -- this -should- be ok as we don't convert the region to a file
+ * reference unless we know it hasn't been modified.
+ */
+static walk_return_t
+write_fileref_region(const struct region *r, struct write_segment_data *wsd)
+{
+ assert(0 == r->r_nsubregions);
+ assert(r->r_info.user_tag != VM_MEMORY_IOKIT);
+ assert((r->r_info.max_protection & VM_PROT_READ) == VM_PROT_READ);
+ assert(!r->r_inzfodregion);
+
+ const struct libent *le = r->r_fileref->fr_libent;
+ const char *pathname = r->r_fileref->fr_pathname;
+ const struct file_range fr = {
+ .off = r->r_fileref->fr_offset,
+ .size = R_SIZE(r),
+ };
+ const struct proto_fileref_command *fc = make_fileref_command(wsd->wsd_lc, pathname, le ? le->le_uuid : UUID_NULL, R_RANGE(r), &fr, &r->r_info, r->r_purgable);
+
+ commit_load_command(wsd, (const void *)fc);
+ if (OPTIONS_DEBUG(opt, 3)) {
+ hsize_str_t hstr;
+ printr(r, "ref '%s' %s (vm %llx-%llx, file offset %lld for %s)\n", pathname, "(type?)", (uint64_t)fc->vmaddr, (uint64_t)fc->vmaddr + fc->vmsize, (int64_t)fc->fileoff, str_hsize(hstr, fc->filesize));
+ }
+ return WALK_CONTINUE;
+}
+
+const struct regionop fileref_ops = {
+ print_memory_region,
+ write_fileref_region,
+ del_fileref_region,
+};
+
+
+#pragma mark -- ZFOD segments written only to the header --
+
+static void
+size_zfod_region(const struct region *r, struct size_core *sc)
+{
+ assert(0 == r->r_nsubregions);
+ assert(r->r_inzfodregion);
+ sc->headersize += sizeof_segment_command();
+ sc->count++;
+ sc->memsize += R_SIZE(r);
+}
+
+static walk_return_t
+write_zfod_region(const struct region *r, struct write_segment_data *wsd)
+{
+ assert(r->r_info.user_tag != VM_MEMORY_IOKIT);
+ assert((r->r_info.max_protection & VM_PROT_READ) == VM_PROT_READ);
+
+ const struct file_range fr = {
+ .off = wsd->wsd_foffset,
+ .size = 0,
+ };
+ make_segment_command(wsd->wsd_lc, R_RANGE(r), &fr, &r->r_info, 0, VM_PURGABLE_EMPTY);
+ commit_load_command(wsd, wsd->wsd_lc);
+ return WALK_CONTINUE;
+}
+
+const struct regionop zfod_ops = {
+ print_memory_region,
+ write_zfod_region,
+ del_zfod_region,
+};
+
+#pragma mark -- Regions containing data --
+
+static walk_return_t
+pwrite_memory(struct write_segment_data *wsd, const void *addr, size_t size, const struct vm_range *vr)
+{
+ assert(size);
+
+ ssize_t nwritten;
+ const int error = bounded_pwrite(wsd->wsd_fd, addr, size, wsd->wsd_foffset, &wsd->wsd_nocache, &nwritten);
+
+ if (error || OPTIONS_DEBUG(opt, 3)) {
+ hsize_str_t hsz;
+ printvr(vr, "writing %ld bytes at offset %lld -> ", size, wsd->wsd_foffset);
+ if (error)
+ printf("err #%d - %s ", error, strerror(error));
+ else {
+ printf("%s ", str_hsize(hsz, nwritten));
+ if (size != (size_t)nwritten)
+ printf("[%zd - incomplete write!] ", nwritten);
+ else if (size != V_SIZE(vr))
+ printf("(%s in memory) ",
+ str_hsize(hsz, V_SIZE(vr)));
+ }
+ printf("\n");
+ }
+
+ walk_return_t step = WALK_CONTINUE;
+ switch (error) {
+ case 0:
+ if (size != (size_t)nwritten)
+ step = WALK_ERROR;
+ else {
+ wsd->wsd_foffset += nwritten;
+ wsd->wsd_nwritten += nwritten;
+ }
+ break;
+ case EFAULT: // transient mapping failure?
+ break;
+ default: // EROFS, ENOSPC, EFBIG etc. */
+ step = WALK_ERROR;
+ break;
+ }
+ return step;
+}
+
+
+/*
+ * Write a contiguous range of memory into the core file.
+ * Apply compression, and chunk if necessary.
+ */
+static int
+segment_compflags(compression_algorithm ca, unsigned *algnum)
+{
+ switch (ca) {
+ case COMPRESSION_LZ4:
+ *algnum = kCOMP_LZ4;
+ break;
+ case COMPRESSION_ZLIB:
+ *algnum = kCOMP_ZLIB;
+ break;
+ case COMPRESSION_LZMA:
+ *algnum = kCOMP_LZMA;
+ break;
+ case COMPRESSION_LZFSE:
+ *algnum = kCOMP_LZFSE;
+ break;
+ default:
+ err(EX_SOFTWARE, "unsupported compression algorithm %x", ca);
+ }
+ return 0;
+}
+
+static bool
+is_file_mapped_shared(const struct region *r)
+{
+ if (r->r_info.external_pager)
+ switch (r->r_info.share_mode) {
+ case SM_TRUESHARED: // sm=shm
+ case SM_SHARED: // sm=ali
+ case SM_SHARED_ALIASED: // sm=s/a
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static walk_return_t
+map_memory_range(struct write_segment_data *wsd, const struct region *r, const struct vm_range *vr, struct vm_range *dp)
+{
+ if (r->r_incommregion) {
+ /*
+ * Special case: for commpage access, copy from our own address space.
+ */
+ V_SETADDR(dp, 0);
+ V_SETSIZE(dp, V_SIZE(vr));
+
+ kern_return_t kr = mach_vm_allocate(mach_task_self(), &dp->addr, dp->size, VM_FLAGS_ANYWHERE);
+ if (KERN_SUCCESS != kr || 0 == dp->addr) {
+ err_mach(kr, r, "mach_vm_allocate c %llx-%llx", V_ADDR(vr), V_ENDADDR(vr));
+ print_one_memory_region(r);
+ return WALK_ERROR;
+ }
+ if (OPTIONS_DEBUG(opt, 3))
+ printr(r, "copying from self %llx-%llx\n", V_ADDR(vr), V_ENDADDR(vr));
+ memcpy((void *)dp->addr, (const void *)V_ADDR(vr), V_SIZE(vr));
+ return WALK_CONTINUE;
+ }
+
+ if (!r->r_insharedregion && 0 == (r->r_info.protection & VM_PROT_READ)) {
+ assert(0 != (r->r_info.max_protection & VM_PROT_READ)); // simple_region_optimization()
+
+ /*
+ * Special case: region that doesn't currently have read permission.
+ * (e.g. --x/r-x permissions with tag 64 - JS JIT generated code
+ * from com.apple.WebKit.WebContent)
+ */
+ const mach_vm_offset_t pagesize_host = 1u << pageshift_host;
+ if (OPTIONS_DEBUG(opt, 3))
+ printr(r, "unreadable (%s/%s), remap with read permission\n",
+ str_prot(r->r_info.protection), str_prot(r->r_info.max_protection));
+ V_SETADDR(dp, 0);
+ V_SETSIZE(dp, V_SIZE(vr));
+ vm_prot_t cprot, mprot;
+ kern_return_t kr = mach_vm_remap(mach_task_self(), &dp->addr, V_SIZE(dp), pagesize_host - 1, true, wsd->wsd_task, V_ADDR(vr), true, &cprot, &mprot, VM_INHERIT_NONE);
+ if (KERN_SUCCESS != kr) {
+ err_mach(kr, r, "mach_vm_remap() %llx-%llx", V_ADDR(vr), V_ENDADDR(vr));
+ return WALK_ERROR;
+ }
+ assert(r->r_info.protection == cprot && r->r_info.max_protection == mprot);
+ kr = mach_vm_protect(mach_task_self(), V_ADDR(dp), V_SIZE(dp), false, VM_PROT_READ);
+ if (KERN_SUCCESS != kr) {
+ err_mach(kr, r, "mach_vm_protect() %llx-%llx", V_ADDR(vr), V_ENDADDR(vr));
+ mach_vm_deallocate(mach_task_self(), V_ADDR(dp), V_SIZE(dp));
+ return WALK_ERROR;
+ }
+ return WALK_CONTINUE;
+ }
+
+ /*
+ * Most segments with data are read here
+ */
+ vm_offset_t data32 = 0;
+ mach_msg_type_number_t data32_count;
+ kern_return_t kr = mach_vm_read(wsd->wsd_task, V_ADDR(vr), V_SIZE(vr), &data32, &data32_count);
+ switch (kr) {
+ case KERN_SUCCESS:
+ V_SETADDR(dp, data32);
+ V_SETSIZE(dp, data32_count);
+ break;
+ case KERN_INVALID_ADDRESS:
+ if (!r->r_insharedregion &&
+ (VM_MEMORY_SKYWALK == r->r_info.user_tag || is_file_mapped_shared(r))) {
+ if (OPTIONS_DEBUG(opt, 1)) {
+ /* not necessarily an error: mitigation below */
+ tag_str_t tstr;
+ printr(r, "mach_vm_read() failed (%s) -- substituting zeroed region\n", str_tagr(tstr, r));
+ if (OPTIONS_DEBUG(opt, 2))
+ print_one_memory_region(r);
+ }
+ V_SETSIZE(dp, V_SIZE(vr));
+ kr = mach_vm_allocate(mach_task_self(), &dp->addr, V_SIZE(dp), VM_FLAGS_ANYWHERE);
+ if (KERN_SUCCESS != kr || 0 == V_ADDR(dp))
+ err_mach(kr, r, "mach_vm_allocate() z %llx-%llx", V_ADDR(vr), V_ENDADDR(vr));
+ break;
+ }
+ /*FALLTHROUGH*/
+ default:
+ err_mach(kr, r, "mach_vm_read() %llx-%llx", V_ADDR(vr), V_SIZE(vr));
+ if (OPTIONS_DEBUG(opt, 1))
+ print_one_memory_region(r);
+ break;
+ }
+ if (kr != KERN_SUCCESS) {
+ V_SETADDR(dp, 0);
+ return WALK_ERROR;
+ }
+
+ /*
+ * Sometimes (e.g. searchd) we may not be able to fetch all the pages
+ * from the underlying mapped file, in which case replace those pages
+ * with zfod pages (at least they compress efficiently) rather than
+ * taking a SIGBUS when compressing them.
+ *
+ * XXX Perhaps we should just catch the SIGBUS, and if the faulting address
+ * is in the right range, substitute zfod pages and rerun region compression?
+ * Complex though, because the compression code may be multithreaded.
+ */
+ if (!r->r_insharedregion && is_file_mapped_shared(r)) {
+ const mach_vm_offset_t pagesize_host = 1u << pageshift_host;
+
+ if (r->r_info.pages_resident * pagesize_host == V_SIZE(dp))
+ return WALK_CONTINUE; // all pages resident, so skip ..
+
+ if (OPTIONS_DEBUG(opt, 2))
+ printr(r, "probing %llu pages in mapped-shared file\n", V_SIZE(dp) / pagesize_host);
+
+ kr = KERN_SUCCESS;
+ for (mach_vm_offset_t a = V_ADDR(dp); a < V_ENDADDR(dp); a += pagesize_host) {
+
+ mach_msg_type_number_t pCount = VM_PAGE_INFO_BASIC_COUNT;
+ vm_page_info_basic_data_t pInfo;
+
+ kr = mach_vm_page_info(mach_task_self(), a, VM_PAGE_INFO_BASIC, (vm_page_info_t)&pInfo, &pCount);
+ if (KERN_SUCCESS != kr) {
+ err_mach(kr, NULL, "mach_vm_page_info() at %llx", a);
+ break;
+ }
+ /* If the VM has the page somewhere, assume we can bring it back */
+ if (pInfo.disposition & (VM_PAGE_QUERY_PAGE_PRESENT | VM_PAGE_QUERY_PAGE_REF | VM_PAGE_QUERY_PAGE_DIRTY))
+ continue;
+
+ /* Force the page to be fetched to see if it faults */
+ mach_vm_size_t tsize = pagesize_host;
+ void *tmp = valloc((size_t)tsize);
+ const mach_vm_address_t vtmp = (mach_vm_address_t)tmp;
+
+ switch (kr = mach_vm_read_overwrite(mach_task_self(), a, tsize, vtmp, &tsize)) {
+ case KERN_SUCCESS:
+ break;
+ case KERN_INVALID_ADDRESS: {
+ /* Content can't be found: replace it and the rest of the region with zero-fill pages */
+ if (OPTIONS_DEBUG(opt, 2)) {
+ printr(r, "mach_vm_read_overwrite() failed after %llu pages -- substituting zfod\n", (a - V_ADDR(dp)) / pagesize_host);
+ print_one_memory_region(r);
+ }
+ mach_vm_address_t va = a;
+ kr = mach_vm_allocate(mach_task_self(), &va, V_ENDADDR(dp) - va, VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE);
+ if (KERN_SUCCESS != kr) {
+ err_mach(kr, r, "mach_vm_allocate() %llx", a);
+ } else {
+ assert(a == va);
+ a = V_ENDADDR(dp); // no need to look any further
+ }
+ break;
+ }
+ default:
+ err_mach(kr, r, "mach_vm_overwrite() %llx", a);
+ break;
+ }
+ free(tmp);
+ if (KERN_SUCCESS != kr)
+ break;
+ }
+ if (KERN_SUCCESS != kr) {
+ kr = mach_vm_deallocate(mach_task_self(), V_ADDR(dp), V_SIZE(dp));
+ if (KERN_SUCCESS != kr && OPTIONS_DEBUG(opt, 1))
+ err_mach(kr, r, "mach_vm_deallocate() pre %llx-%llx", V_ADDR(dp), V_ENDADDR(dp));
+ V_SETADDR(dp, 0);
+ return WALK_ERROR;
+ }
+ }
+
+ return WALK_CONTINUE;
+}
+
+static walk_return_t
+write_memory_range(struct write_segment_data *wsd, const struct region *r, mach_vm_offset_t vmaddr, mach_vm_offset_t vmsize)
+{
+ assert(R_ADDR(r) <= vmaddr && R_ENDADDR(r) >= vmaddr + vmsize);
+
+ mach_vm_offset_t resid = vmsize;
+ walk_return_t step = WALK_CONTINUE;
+
+ do {
+ vmsize = resid;
+
+ /*
+ * Since some regions can be inconveniently large,
+ * chop them into multiple chunks as we compress them.
+ * (mach_vm_read has 32-bit limitations too).
+ */
+ vmsize = vmsize > INT32_MAX ? INT32_MAX : vmsize;
+ if (opt->chunksize > 0 && vmsize > opt->chunksize)
+ vmsize = opt->chunksize;
+ assert(vmsize <= INT32_MAX);
+
+ const struct vm_range vr = {
+ .addr = vmaddr,
+ .size = vmsize,
+ };
+ struct vm_range d, *dp = &d;
+
+ step = map_memory_range(wsd, r, &vr, dp);
+ if (WALK_CONTINUE != step)
+ break;
+ assert(0 != V_ADDR(dp) && 0 != V_SIZE(dp));
+ const void *srcaddr = (const void *)V_ADDR(dp);
+
+ mach_vm_behavior_set(mach_task_self(), V_ADDR(dp), V_SIZE(dp), VM_BEHAVIOR_SEQUENTIAL);
+
+ void *dstbuf = NULL;
+ unsigned algorithm = 0;
+ size_t filesize;
+
+ if (opt->extended) {
+ dstbuf = malloc(V_SIZEOF(dp));
+ if (dstbuf) {
+ filesize = compression_encode_buffer(dstbuf, V_SIZEOF(dp), srcaddr, V_SIZEOF(dp), NULL, opt->calgorithm);
+ if (filesize > 0 && filesize < V_SIZEOF(dp)) {
+ srcaddr = dstbuf; /* the data source is now heap, compressed */
+ mach_vm_deallocate(mach_task_self(), V_ADDR(dp), V_SIZE(dp));
+ V_SETADDR(dp, 0);
+ if (segment_compflags(opt->calgorithm, &algorithm) != 0) {
+ free(dstbuf);
+ mach_vm_deallocate(mach_task_self(), V_ADDR(dp), V_SIZE(dp));
+ V_SETADDR(dp, 0);
+ step = WALK_ERROR;
+ break;
+ }
+ } else {
+ free(dstbuf);
+ dstbuf = NULL;
+ filesize = V_SIZEOF(dp);
+ }
+ } else
+ filesize = V_SIZEOF(dp);
+ assert(filesize <= V_SIZEOF(dp));
+ } else
+ filesize = V_SIZEOF(dp);
+
+ assert(filesize);
+
+ const struct file_range fr = {
+ .off = wsd->wsd_foffset,
+ .size = filesize,
+ };
+ make_segment_command(wsd->wsd_lc, &vr, &fr, &r->r_info, algorithm, r->r_purgable);
+ step = pwrite_memory(wsd, srcaddr, filesize, &vr);
+ if (dstbuf)
+ free(dstbuf);
+ if (V_ADDR(dp)) {
+ kern_return_t kr = mach_vm_deallocate(mach_task_self(), V_ADDR(dp), V_SIZE(dp));
+ if (KERN_SUCCESS != kr && OPTIONS_DEBUG(opt, 1))
+ err_mach(kr, r, "mach_vm_deallocate() post %llx-%llx", V_ADDR(dp), V_SIZE(dp));
+ }
+
+ if (WALK_ERROR == step)
+ break;
+ commit_load_command(wsd, wsd->wsd_lc);
+ resid -= vmsize;
+ vmaddr += vmsize;
+ } while (resid);
+
+ return step;
+}
+
+#ifdef RDAR_23744374
+/*
+ * Sigh. This is a workaround.
+ * Find the vmsize as if the VM system manages ranges in host pagesize units
+ * rather than application pagesize units.
+ */
+static mach_vm_size_t
+getvmsize_host(const task_t task, const struct region *r)
+{
+ mach_vm_size_t vmsize_host = R_SIZE(r);
+
+ if (pageshift_host != pageshift_app) {
+ is_actual_size(task, r, &vmsize_host);
+ if (OPTIONS_DEBUG(opt, 1) && R_SIZE(r) != vmsize_host)
+ printr(r, "(region size tweak: was %llx, is %llx)\n", R_SIZE(r), vmsize_host);
+ }
+ return vmsize_host;
+}
+#else
+static __inline mach_vm_size_t
+getvmsize_host(__unused const task_t task, const struct region *r)
+{
+ return R_SIZE(r);
+}
+#endif
+
+static walk_return_t
+write_sparse_region(const struct region *r, struct write_segment_data *wsd)
+{
+ assert(r->r_nsubregions);
+ assert(!r->r_inzfodregion);
+ assert(NULL == r->r_fileref);
+
+ const mach_vm_size_t vmsize_host = getvmsize_host(wsd->wsd_task, r);
+ walk_return_t step = WALK_CONTINUE;
+
+ for (unsigned i = 0; i < r->r_nsubregions; i++) {
+ const struct subregion *s = r->r_subregions[i];
+
+ if (s->s_isuuidref)
+ step = write_fileref_subregion(r, s, wsd);
+ else {
+ /* Write this one out as real data */
+ mach_vm_size_t vmsize = S_SIZE(s);
+ if (R_SIZE(r) != vmsize_host) {
+ if (S_ADDR(s) + vmsize > R_ADDR(r) + vmsize_host) {
+ vmsize = R_ADDR(r) + vmsize_host - S_ADDR(s);
+ if (OPTIONS_DEBUG(opt, 3))
+ printr(r, "(subregion size tweak: was %llx, is %llx)\n",
+ S_SIZE(s), vmsize);
+ }
+ }
+ step = write_memory_range(wsd, r, S_ADDR(s), vmsize);
+ }
+ if (WALK_ERROR == step)
+ break;
+ }
+ return step;
+}
+
+static walk_return_t
+write_vanilla_region(const struct region *r, struct write_segment_data *wsd)
+{
+ assert(0 == r->r_nsubregions);
+ assert(!r->r_inzfodregion);
+ assert(NULL == r->r_fileref);
+
+ const mach_vm_size_t vmsize_host = getvmsize_host(wsd->wsd_task, r);
+ return write_memory_range(wsd, r, R_ADDR(r), vmsize_host);
+}
+
+walk_return_t
+region_write_memory(struct region *r, void *arg)
+{
+ assert(r->r_info.user_tag != VM_MEMORY_IOKIT); // elided in walk_regions()
+ assert((r->r_info.max_protection & VM_PROT_READ) == VM_PROT_READ);
+ return ROP_WRITE(r, arg);
+}
+
+/*
+ * Handles the cases where segments are broken into chunks i.e. when
+ * writing compressed segments.
+ */
+static unsigned long
+count_memory_range(mach_vm_offset_t vmsize)
+{
+ unsigned long count;
+ if (opt->chunksize) {
+ count = (size_t)vmsize / opt->chunksize;
+ if (vmsize != (mach_vm_offset_t)count * opt->chunksize)
+ count++;
+ } else
+ count = 1;
+ return count;
+}
+
+/*
+ * A sparse region is likely a writable data segment described by
+ * native_segment_command_t somewhere in the address space.
+ */
+static void
+size_sparse_subregion(const struct subregion *s, struct size_core *sc)
+{
+ const unsigned long count = count_memory_range(S_SIZE(s));
+ sc->headersize += sizeof_segment_command() * count;
+ sc->count += count;
+ sc->memsize += S_SIZE(s);
+}
+
+static void
+size_sparse_region(const struct region *r, struct size_core *sc_sparse, struct size_core *sc_fileref)
+{
+ assert(0 != r->r_nsubregions);
+
+ unsigned long entry_total = sc_sparse->count + sc_fileref->count;
+ for (unsigned i = 0; i < r->r_nsubregions; i++) {
+ const struct subregion *s = r->r_subregions[i];
+ if (s->s_isuuidref)
+ size_fileref_subregion(s, sc_fileref);
+ else
+ size_sparse_subregion(s, sc_sparse);
+ }
+ if (OPTIONS_DEBUG(opt, 3)) {
+ /* caused by compression breaking a large region into chunks */
+ entry_total = (sc_fileref->count + sc_sparse->count) - entry_total;
+ if (entry_total > r->r_nsubregions)
+ printr(r, "range contains %u subregions requires %lu segment commands\n",
+ r->r_nsubregions, entry_total);
+ }
+}
+
+const struct regionop sparse_ops = {
+ print_memory_region,
+ write_sparse_region,
+ del_sparse_region,
+};
+
+static void
+size_vanilla_region(const struct region *r, struct size_core *sc)
+{
+ assert(0 == r->r_nsubregions);
+
+ const unsigned long count = count_memory_range(R_SIZE(r));
+ sc->headersize += sizeof_segment_command() * count;
+ sc->count += count;
+ sc->memsize += R_SIZE(r);
+
+ if (OPTIONS_DEBUG(opt, 3) && count != 1)
+ printr(r, "range with 1 region, but requires %lu segment commands\n", count);
+}
+
+const struct regionop vanilla_ops = {
+ print_memory_region,
+ write_vanilla_region,
+ del_vanilla_region,
+};
+
+walk_return_t
+region_size_memory(struct region *r, void *arg)
+{
+ struct size_segment_data *ssd = arg;
+
+ if (&zfod_ops == r->r_op)
+ size_zfod_region(r, &ssd->ssd_zfod);
+ else if (&fileref_ops == r->r_op)
+ size_fileref_region(r, &ssd->ssd_fileref);
+ else if (&sparse_ops == r->r_op)
+ size_sparse_region(r, &ssd->ssd_sparse, &ssd->ssd_fileref);
+ else if (&vanilla_ops == r->r_op)
+ size_vanilla_region(r, &ssd->ssd_vanilla);
+ else
+ errx(EX_SOFTWARE, "%s: bad op", __func__);
+
+ return WALK_CONTINUE;
+}
diff --git a/system_cmds/gcore.tproj/corefile.h b/system_cmds/gcore.tproj/corefile.h
new file mode 100644
index 0000000..2bdc43e
--- /dev/null
+++ b/system_cmds/gcore.tproj/corefile.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "loader_additions.h"
+#include "dyld_shared_cache.h"
+#include "region.h"
+
+#include <mach-o/loader.h>
+#include <mach/mach.h>
+#include <mach/mach_vm.h>
+#include <sys/types.h>
+
+#ifndef _COREFILE_H
+#define _COREFILE_H
+
+#if defined(__LP64__)
+typedef struct mach_header_64 native_mach_header_t;
+typedef struct segment_command_64 native_segment_command_t;
+#define NATIVE_MH_MAGIC MH_MAGIC_64
+#define NATIVE_LC_SEGMENT LC_SEGMENT_64
+#else
+typedef struct mach_header native_mach_header_t;
+typedef struct segment_command native_segment_command_t;
+#define NATIVE_MH_MAGIC MH_MAGIC
+#define NATIVE_LC_SEGMENT LC_SEGMENT
+#endif
+
+static __inline const struct load_command *next_lc(const struct load_command *lc) {
+ if (lc->cmdsize && (lc->cmdsize & 3) == 0)
+ return (const void *)((caddr_t)lc + lc->cmdsize);
+ return NULL;
+}
+
+extern native_segment_command_t *make_native_segment_command(void *, const struct vm_range *, const struct file_range *, vm_prot_t, vm_prot_t);
+
+extern native_mach_header_t *make_corefile_mach_header(void *);
+extern struct proto_coreinfo_command *make_coreinfo_command(native_mach_header_t *, void *, const uuid_t, uint64_t, uint64_t);
+
+static __inline void mach_header_inc_ncmds(native_mach_header_t *mh, uint32_t inc) {
+ mh->ncmds += inc;
+}
+
+static __inline void mach_header_inc_sizeofcmds(native_mach_header_t *mh, uint32_t inc) {
+ mh->sizeofcmds += inc;
+}
+
+struct size_core {
+ unsigned long count; /* number-of-objects */
+ size_t headersize; /* size in mach header */
+ mach_vm_offset_t memsize; /* size in memory */
+};
+
+struct size_segment_data {
+ struct size_core ssd_vanilla; /* full segments with data */
+ struct size_core ssd_sparse; /* sparse segments with data */
+ struct size_core ssd_fileref; /* full & sparse segments with uuid file references */
+ struct size_core ssd_zfod; /* full segments with zfod pages */
+};
+
+struct write_segment_data {
+ task_t wsd_task;
+ native_mach_header_t *wsd_mh;
+ void *wsd_lc;
+ int wsd_fd;
+ bool wsd_nocache;
+ off_t wsd_foffset;
+ off_t wsd_nwritten;
+};
+
+#endif /* _COREFILE_H */
diff --git a/system_cmds/gcore.tproj/dyld.c b/system_cmds/gcore.tproj/dyld.c
new file mode 100644
index 0000000..92aeac1
--- /dev/null
+++ b/system_cmds/gcore.tproj/dyld.c
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "options.h"
+#include "dyld.h"
+#include "utils.h"
+#include "corefile.h"
+#include "vm.h"
+
+#include <mach-o/loader.h>
+#include <mach-o/fat.h>
+#include <mach-o/dyld_process_info.h>
+
+#include <mach/mach.h>
+#include <mach/task.h>
+#include <mach/mach_vm.h>
+#include <mach/shared_region.h>
+#include <sys/param.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <libgen.h>
+#include <sys/stat.h>
+
+/*
+ * WARNING WARNING WARNING
+ *
+ * Do not trust any of the data from the target task.
+ *
+ * A broken program may have damaged it, or a malicious
+ * program may have deliberately constructed something to
+ * cause us harm.
+ */
+
+static const char warn_dyld_info[] = "dyld information is incomplete or damaged";
+
+dyld_process_info
+get_task_dyld_info(const task_t task)
+{
+ kern_return_t kret;
+ dyld_process_info dpi = _dyld_process_info_create(task, 0, &kret);
+ if (NULL == dpi) {
+ err_mach(kret, NULL, "_dlyd_process_info_create");
+ } else {
+ dyld_process_state_info stateInfo;
+
+ _dyld_process_info_get_state(dpi, &stateInfo);
+ switch (stateInfo.dyldState) {
+ case dyld_process_state_not_started:
+ warnx("%s: dyld state %d", warn_dyld_info, stateInfo.dyldState);
+ _dyld_process_info_release(dpi);
+ dpi = NULL;
+ break;
+ default:
+ break;
+ }
+ }
+ return dpi;
+}
+
+/*
+ * Get the shared cache UUID iff it's in use and is the system one
+ */
+bool
+get_sc_uuid(dyld_process_info dpi, uuid_t uu)
+{
+ dyld_process_cache_info cacheInfo;
+
+ _dyld_process_info_get_cache(dpi, &cacheInfo);
+ if (!cacheInfo.noCache && !cacheInfo.privateCache) {
+ uuid_copy(uu, cacheInfo.cacheUUID);
+ return true;
+ }
+ return false;
+}
+
+void
+free_task_dyld_info(dyld_process_info dpi)
+{
+ _dyld_process_info_release(dpi);
+}
+
+/*
+ * This routine collects both the Mach-O header and the commands
+ * "below" it, assuming they're in contiguous memory.
+ */
+static native_mach_header_t *
+copy_dyld_image_mh(task_t task, mach_vm_address_t targetmh, const char *path)
+{
+ vm_offset_t mhaddr = 0;
+ mach_msg_type_number_t mhlen = sizeof (native_mach_header_t);
+
+ for (int attempts = 0; attempts < 2; attempts++) {
+
+ const kern_return_t ret = mach_vm_read(task, targetmh, mhlen, &mhaddr, &mhlen);
+ if (KERN_SUCCESS != ret) {
+ err_mach(ret, NULL, "mach_vm_read() at 0x%llx for image %s\n", targetmh, path);
+ mhaddr = 0;
+ break;
+ }
+ const native_mach_header_t *mh = (void *)mhaddr;
+ if (mhlen < mh->sizeofcmds + sizeof (*mh)) {
+ const mach_msg_type_number_t newmhlen = sizeof (*mh) + mh->sizeofcmds;
+ mach_vm_deallocate(mach_task_self(), mhaddr, mhlen);
+ mhlen = newmhlen;
+ } else
+ break;
+ }
+
+ native_mach_header_t *result = NULL;
+
+ if (mhaddr) {
+ if (NULL != (result = malloc(mhlen)))
+ memcpy(result, (void *)mhaddr, mhlen);
+ mach_vm_deallocate(mach_task_self(), mhaddr, mhlen);
+ }
+ return result;
+}
+
+/*
+ * This table (list) describes libraries and the executable in the address space
+ */
+struct liblist {
+ STAILQ_ENTRY(liblist) ll_linkage;
+ unsigned long ll_namehash;
+ struct libent ll_entry;
+};
+static STAILQ_HEAD(, liblist) libhead = STAILQ_HEAD_INITIALIZER(libhead);
+
+static const struct libent *
+libent_lookup_bypathname_withhash(const char *nm, const unsigned long hash)
+{
+ struct liblist *ll;
+ STAILQ_FOREACH(ll, &libhead, ll_linkage) {
+ if (hash != ll->ll_namehash)
+ continue;
+ struct libent *le = &ll->ll_entry;
+ if (strcmp(nm, le->le_pathname) == 0)
+ return le;
+ }
+ return NULL;
+}
+
+const struct libent *
+libent_lookup_byuuid(const uuid_t uuid)
+{
+ struct liblist *ll;
+ STAILQ_FOREACH(ll, &libhead, ll_linkage) {
+ struct libent *le = &ll->ll_entry;
+ if (uuid_compare(uuid, le->le_uuid) == 0)
+ return le;
+ }
+ return NULL;
+}
+
+const struct libent *
+libent_lookup_first_bytype(uint32_t mhtype)
+{
+ struct liblist *ll;
+ STAILQ_FOREACH(ll, &libhead, ll_linkage) {
+ struct libent *le = &ll->ll_entry;
+ if (mhtype == le->le_mh->filetype)
+ return le;
+ }
+ return NULL;
+}
+
+const struct libent *
+libent_insert(const char *rawnm, const uuid_t uuid, uint64_t mhaddr, const native_mach_header_t *mh, const struct vm_range *vr, mach_vm_offset_t objoff)
+{
+ const struct libent *le = libent_lookup_byuuid(uuid);
+ if (NULL != le)
+ return le; // disallow multiple names for the same uuid
+
+ char *nm = realpath(rawnm, NULL);
+ if (NULL == nm)
+ nm = strdup(rawnm);
+ const unsigned long nmhash = simple_namehash(nm);
+ le = libent_lookup_bypathname_withhash(nm, nmhash);
+ if (NULL != le) {
+ free(nm);
+ return le;
+ }
+
+ if (OPTIONS_DEBUG(opt, 3)) {
+ uuid_string_t uustr;
+ uuid_unparse_lower(uuid, uustr);
+ printf("[adding <'%s', %s, 0x%llx, %p", nm, uustr, mhaddr, mh);
+ if (vr)
+ printf(" (%llx-%llx)", V_ADDR(vr), V_ENDADDR(vr));
+ printf(">]\n");
+ }
+ struct liblist *ll = malloc(sizeof (*ll));
+ ll->ll_namehash = nmhash;
+ ll->ll_entry.le_pathname = nm;
+ ll->ll_entry.le_filename = strrchr(ll->ll_entry.le_pathname, '/');
+ if (NULL == ll->ll_entry.le_filename)
+ ll->ll_entry.le_filename = ll->ll_entry.le_pathname;
+ else
+ ll->ll_entry.le_filename++;
+ uuid_copy(ll->ll_entry.le_uuid, uuid);
+ ll->ll_entry.le_mhaddr = mhaddr;
+ ll->ll_entry.le_mh = mh;
+ if (vr)
+ ll->ll_entry.le_vr = *vr;
+ else {
+ V_SETADDR(&ll->ll_entry.le_vr, MACH_VM_MAX_ADDRESS);
+ V_SETSIZE(&ll->ll_entry.le_vr, 0);
+ }
+ ll->ll_entry.le_objoff = objoff;
+ STAILQ_INSERT_HEAD(&libhead, ll, ll_linkage);
+
+ return &ll->ll_entry;
+}
+
+bool
+libent_build_nametable(task_t task, dyld_process_info dpi)
+{
+ __block bool valid = true;
+
+ _dyld_process_info_for_each_image(dpi, ^(uint64_t mhaddr, const uuid_t uuid, const char *path) {
+ if (valid) {
+ native_mach_header_t *mh = copy_dyld_image_mh(task, mhaddr, path);
+ if (mh) {
+ /*
+ * Validate the rest of the mach information in the header before attempting optimizations
+ */
+ const size_t mhlen = sizeof (*mh) + mh->sizeofcmds;
+ const struct load_command *lc = (const void *)(mh + 1);
+ struct vm_range vr = {
+ .addr = MACH_VM_MAX_ADDRESS,
+ .size = 0
+ };
+ mach_vm_offset_t objoff = MACH_VM_MAX_ADDRESS;
+
+ for (unsigned n = 0; n < mh->ncmds; n++) {
+ if (((uintptr_t)lc & 0x3) != 0 ||
+ (uintptr_t)lc < (uintptr_t)mh || (uintptr_t)lc > (uintptr_t)mh + mhlen) {
+ warnx("%s, %d", warn_dyld_info, __LINE__);
+ valid = false;
+ break;
+ }
+ switch (lc->cmd) {
+ case NATIVE_LC_SEGMENT: {
+ const native_segment_command_t *sc = (const void *)lc;
+
+ char scsegname[17];
+ strlcpy(scsegname, sc->segname, sizeof (scsegname));
+
+ if (0 == sc->vmaddr &&
+ strcmp(scsegname, SEG_PAGEZERO) == 0)
+ break;
+
+ /*
+ * -Depends- on finding a __TEXT segment first
+ * which implicitly maps the mach header too
+ */
+
+ if (MACH_VM_MAX_ADDRESS == objoff) {
+ if (strcmp(scsegname, SEG_TEXT) == 0) {
+ objoff = mhaddr - sc->vmaddr;
+ V_SETADDR(&vr, mhaddr);
+ V_SETSIZE(&vr, sc->vmsize);
+ } else {
+ printf("%s: expected %s segment, found %s\n", path, SEG_TEXT, scsegname);
+ valid = false;
+ break;
+ }
+ }
+
+ mach_vm_offset_t lo = sc->vmaddr + objoff;
+ mach_vm_offset_t hi = lo + sc->vmsize;
+
+ if (V_SIZE(&vr)) {
+ if (lo < V_ADDR(&vr)) {
+ mach_vm_offset_t newsize = V_SIZE(&vr) + (V_ADDR(&vr) - lo);
+ V_SETSIZE(&vr, newsize);
+ V_SETADDR(&vr, lo);
+ }
+ if (hi > V_ENDADDR(&vr)) {
+ V_SETSIZE(&vr, (hi - V_ADDR(&vr)));
+ }
+ } else {
+ V_SETADDR(&vr, lo);
+ V_SETSIZE(&vr, hi - lo);
+ }
+ assert(lo >= V_ADDR(&vr) && hi <= V_ENDADDR(&vr));
+ } break;
+#if defined(RDAR_28040018)
+ case LC_ID_DYLINKER:
+ if (MH_DYLINKER == mh->filetype) {
+ /* workaround: the API doesn't always return the right name */
+ const struct dylinker_command *dc = (const void *)lc;
+ path = dc->name.offset + (const char *)dc;
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ if (NULL == (lc = next_lc(lc)))
+ break;
+ }
+ if (valid)
+ (void) libent_insert(path, uuid, mhaddr, mh, &vr, objoff);
+ }
+ }
+ });
+ if (OPTIONS_DEBUG(opt, 3))
+ printf("nametable %sconstructed\n", valid ? "" : "NOT ");
+ return valid;
+}
diff --git a/system_cmds/gcore.tproj/dyld.h b/system_cmds/gcore.tproj/dyld.h
new file mode 100644
index 0000000..02ded2a
--- /dev/null
+++ b/system_cmds/gcore.tproj/dyld.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "options.h"
+#include "corefile.h"
+#include "utils.h"
+
+#include <mach-o/dyld_images.h>
+#include <mach-o/dyld_process_info.h>
+#include <uuid/uuid.h>
+
+#ifndef _DYLD_H
+#define _DYLD_H
+
+struct libent {
+ const char *le_filename; // (points into le_pathname!)
+ char *le_pathname;
+ uuid_t le_uuid;
+ uint64_t le_mhaddr; // address in target process
+ const native_mach_header_t *le_mh; // copy mapped into this address space
+ struct vm_range le_vr; // vmaddr, vmsize bounds in target process
+ mach_vm_offset_t le_objoff; // offset from le_mhaddr to first __TEXT seg
+};
+
+extern const struct libent *libent_lookup_byuuid(const uuid_t);
+extern const struct libent *libent_lookup_first_bytype(uint32_t);
+extern const struct libent *libent_insert(const char *, const uuid_t, uint64_t, const native_mach_header_t *, const struct vm_range *, mach_vm_offset_t);
+extern bool libent_build_nametable(task_t, dyld_process_info);
+
+extern dyld_process_info get_task_dyld_info(task_t);
+extern bool get_sc_uuid(dyld_process_info, uuid_t);
+extern void free_task_dyld_info(dyld_process_info);
+
+#endif /* _DYLD_H */
diff --git a/system_cmds/gcore.tproj/dyld_shared_cache.c b/system_cmds/gcore.tproj/dyld_shared_cache.c
new file mode 100644
index 0000000..0e21163
--- /dev/null
+++ b/system_cmds/gcore.tproj/dyld_shared_cache.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "options.h"
+#include "dyld_shared_cache.h"
+#include "utils.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fts.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <TargetConditionals.h>
+
+static const size_t dyld_cache_header_size = sizeof (struct copied_dyld_cache_header);
+
+/*
+ * The shared cache must both contain the magic ID and
+ * match the uuid we discovered via dyld's information.
+ * Assumes that the dyld_cache_header grows in a binary compatible fashion.
+ */
+bool
+get_uuid_from_shared_cache_mapping(const void *addr, size_t size, uuid_t out)
+{
+ const struct copied_dyld_cache_header *ch = addr;
+ if (size < sizeof (*ch))
+ return false;
+ static const char prefix[] = "dyld_v";
+ if (strncmp(ch->magic, prefix, strlen(prefix)) != 0)
+ return false;
+ uuid_copy(out, ch->uuid);
+ return true;
+}
+
+/*
+ * Look in the known places to see if we can find this one ..
+ */
+char *
+shared_cache_filename(const uuid_t uu)
+{
+ assert(!uuid_is_null(uu));
+ static char *sc_argv[] = {
+#if TARGET_OS_OSX
+ "/System/Library/dyld",
+#elif TARGET_OS_IPHONE
+ "/System/Library/Caches/com.apple.dyld",
+#else
+#error undefined
+#endif
+ NULL
+ };
+ char *nm = NULL;
+ FTS *fts = fts_open(sc_argv, FTS_NOCHDIR | FTS_LOGICAL | FTS_XDEV, NULL);
+ if (NULL != fts) {
+ FTSENT *fe;
+ while (NULL != (fe = fts_read(fts))) {
+ if ((fe->fts_info & FTS_F) == 0 ||
+ (fe->fts_info & FTS_ERR) != 0)
+ continue;
+
+ static const char prefix[] = "dyld_shared_cache_";
+ if (strncmp(fe->fts_name, prefix, strlen(prefix)) != 0)
+ continue;
+
+ if (fe->fts_statp->st_size < (off_t)dyld_cache_header_size)
+ continue;
+
+ int d = open(fe->fts_accpath, O_RDONLY);
+ if (-1 == d) {
+ if (OPTIONS_DEBUG(opt, 1))
+ printf("%s: cannot open - %s\n", fe->fts_accpath, strerror(errno));
+ continue;
+ }
+ void *addr = mmap(NULL, dyld_cache_header_size, PROT_READ, MAP_PRIVATE, d, 0);
+ close(d);
+ if ((void *)-1 == addr) {
+ if (OPTIONS_DEBUG(opt, 1))
+ printf("%s: cannot mmap - %s\n", fe->fts_accpath, strerror(errno));
+ continue;
+ }
+ uuid_t scuuid;
+ uuid_clear(scuuid);
+ if (get_uuid_from_shared_cache_mapping(addr, dyld_cache_header_size, scuuid)) {
+ if (uuid_compare(uu, scuuid) == 0)
+ nm = strdup(fe->fts_accpath);
+ else if (OPTIONS_DEBUG(opt, 3)) {
+ uuid_string_t scstr;
+ uuid_unparse_lower(scuuid, scstr);
+ printf("%s: shared cache mismatch (%s)\n", fe->fts_accpath, scstr);
+ }
+ }
+ munmap(addr, dyld_cache_header_size);
+ if (NULL != nm)
+ break;
+ }
+ }
+ if (fts)
+ fts_close(fts);
+ return nm;
+}
diff --git a/system_cmds/gcore.tproj/dyld_shared_cache.h b/system_cmds/gcore.tproj/dyld_shared_cache.h
new file mode 100644
index 0000000..1fe0fac
--- /dev/null
+++ b/system_cmds/gcore.tproj/dyld_shared_cache.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015 Apple Inc. All rights reserved.
+ */
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <uuid/uuid.h>
+#include <sys/types.h>
+
+#ifndef _DYLD_SHARED_CACHE_H
+#define _DYLD_SHARED_CACHE_H
+
+/*
+ * Guilty knowledge of dyld shared cache internals, used to verify shared cache
+ */
+struct copied_dyld_cache_header {
+ char magic[16]; // e.g. "dyld_v0 i386"
+ uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info
+ uint32_t mappingCount; // number of dyld_cache_mapping_info entries
+ uint32_t imagesOffset; // file offset to first dyld_cache_image_info
+ uint32_t imagesCount; // number of dyld_cache_image_info entries
+ uint64_t dyldBaseAddress; // base address of dyld when cache was built
+ uint64_t codeSignatureOffset; // file offset of code signature blob
+ uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file)
+ uint64_t slideInfoOffset; // file offset of kernel slid info
+ uint64_t slideInfoSize; // size of kernel slid info
+ uint64_t localSymbolsOffset; // file offset of where local symbols are stored
+ uint64_t localSymbolsSize; // size of local symbols information
+ uint8_t uuid[16]; // unique value for each shared cache file
+ uint64_t cacheType; // 1 for development, 0 for optimized
+};
+
+extern bool get_uuid_from_shared_cache_mapping(const void *, size_t, uuid_t);
+extern char *shared_cache_filename(const uuid_t);
+
+#endif /* _DYLD_SHARED_CACHE_H */
diff --git a/system_cmds/gcore.tproj/gcore-entitlements.plist b/system_cmds/gcore.tproj/gcore-entitlements.plist
new file mode 100644
index 0000000..39c14ef
--- /dev/null
+++ b/system_cmds/gcore.tproj/gcore-entitlements.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>com.apple.security.cs.debugger.root</key>
+ <true/>
+</dict>
+</plist>
diff --git a/system_cmds/gcore.tproj/gcore-internal.1 b/system_cmds/gcore.tproj/gcore-internal.1
new file mode 100644
index 0000000..923f3e9
--- /dev/null
+++ b/system_cmds/gcore.tproj/gcore-internal.1
@@ -0,0 +1,201 @@
+.Dd 9/29/16
+.Dt gcore-internal 1
+.Os Darwin
+.Sh NAME
+.Nm gcore
+.Nd get core images of running processes and corpses
+.Sh SYNOPSIS
+.Nm
+.Op Fl x
+.Op Fl F
+.Op Fl C
+.Op Fl Z Ar compopts
+.Op Fl t Ar threshold
+.Op Fl d
+.Ar args ...
+.Nm
+.Sy conv
+.Op Fl L Ar searchpath
+.Op Fl z
+.Op Fl s
+.Op Fl v
+.Op Fl d
+.Ar incore outcore
+.Nm
+.Sy map
+.Ar corefile
+.Nm
+.Sy fref
+.Ar corefile
+.Sh DESCRIPTION
+For an introduction to this command and its options, see
+.Xr gcore 1 .
+This page describes various experimental capabilities
+of the
+.Nm
+command intended, for the moment, for internal use only.
+.Pp
+The following set of additional flags are available:
+.Bl -tag -width Fl
+.It Fl x
+Create extended (compact) core files. With this flag,
+.Nm
+elides empty and unmapped regions from the dump, and uses
+metadata from the VM system and
+.Xr dyld 1
+to minimize the size of the dump, writing compressed versions of
+the active regions of the address space into the dump file.
+.Nm
+also records file references to the various files mapped into the
+address space, potentially including the shared cache, to
+avoid duplicating content already present on the filesystem.
+Taken together, these techniques can lead to very significant
+space savings for the core file, particularly for smaller programs.
+.It Fl F
+Normally when
+.Fl x
+is specified,
+.Nm
+makes conservative assumptions about which files should be
+incorporated into the dump as file references so that the
+full core file can be recreated later. This flag attempts to make
+.Em every
+mapped file into a file reference. While this can occasionally
+be useful for applications that map many files into their address space,
+it may be
+.Em extremely
+difficult to recreate the process image as a result.
+Use cautiously!
+.El
+.Pp
+The remaining options are more relevant to the
+.Nm
+maintainers:
+.Bl -tag -width Fl
+.It Fl C
+Forcibly generate a corpse for the process, even if the process is suspended.
+.It Fl Z Ar compopts
+Specify compression options e.g. algorithm and chunksize.
+.It Fl t Ar threshold
+Set the threshold at which I/O caching is disabled to
+.Ar threshold
+KiBytes.
+.It Fl d
+Enable debugging of the
+.Nm
+command.
+.El
+.Pp
+If the
+.Ar pid
+value is specified as 0,
+.Nm
+assumes it has been passed a corpse port by its parent;
+if so it will generate a core dump for that corpse. The
+.Fl c
+flag may not be used in this case, as the process context may no longer exist.
+.Pp
+The
+.Nm
+command supports several sub-commands that can be
+used with extended core files created using the
+.Fl x
+flag. These are:
+.Bl -tag -width frefs
+.\" -compact -offset indent
+.Pp
+.It Sy conv
+Copy and convert a core file to the "pre-coreinfo" format
+compatible with
+.Xr lldb(1) .
+This operation reads the input core file dereferencing any file
+references it contains by copying the content
+and decompressing any compressed data into the output core file.
+This conversion usually makes the core file substantially larger.
+.Pp
+By default, files to be dereferenced must be accessible on the
+local filesystem by the same relative paths as they were originally recorded
+when the dump was taken.
+Files that are Mach-O objects containing UUIDs are required to match
+the UUIDs recorded at the time the core dump was taken.
+Files are otherwise only checked for matching modification times, and
+thus can easily be "forged" using
+.Xr touch 1 .
+.Pp
+Several flags can be used with the conversion:
+.Pp
+.Bl -tag -width Fl
+.It Fl L Ar searchpath
+When processing file references,
+look for the pathnames in the directories specified in
+.Ar searchpath .
+These should be specified as a colon-separated
+list of base directories which will be prepended to each pathname in turn
+for each file reference.
+.It Fl z
+During conversion, if any mapped file
+identified by modification time
+cannot be located, substitute zeroed memory.
+.It Fl s
+When processing file references,
+try looking for the pathname through
+.Xr dsymForUUID 1
+before searching locally.
+.It Fl v
+Report progress on the conversion as it proceeds.
+.It Fl d
+Enable debugging of the
+.Sy conv
+subcommand.
+.El
+.It Sy map
+Print a representation of the address space contained in the core file.
+.Pp
+.It Sy frefs
+Print a list of files corresponding to the file references
+in the core file.
+Can be used to capture the set of files needed to bind the file references
+into the core file at a later time.
+.El
+.Sh BUGS
+.Pp
+When using the
+.Fl x
+flag,
+.Nm
+will likely incorporate a reference to the shared cache into
+.Ar corefile
+including the UUID of that cache.
+On some platforms, the cache is created when the release is built
+and since it resides on a read-only root filesystem it should
+generally be easy to retrieve.
+However on the desktop, the lifecycle of this cache is managed locally
+e.g. with
+.Xr update_dyld_shared_cache 1 .
+When this cache is recreated it is given a new UUID, the directory
+entry for the old cache is removed, and the same filename
+is used for the new cache.
+Thus when the last named copy of the shared cache is removed from the
+filesystem, extended core files that contain references to that cache
+can no longer be converted.
+.Pp
+When using the
+.Fl x
+flag,
+.Nm
+may be unable to locate the currently mapped shared cache in the filesystem.
+In this case
+.Nm
+does not generate any file references to the content of the
+shared cache; it just copies as much of the content
+as is needed from the memory image into the core file.
+This may lead to substantially larger core files than expected.
+.Pp
+It would be nice if
+.Xr lldb 1
+could examine extended core files directly i.e. without the conversion step.
+.Pp
+.Sh SEE ALSO
+.Xr dyld 1 ,
+.Xr update_dyld_shared_cache 1 ,
+.Xr dsymForUUID 1
diff --git a/system_cmds/gcore.tproj/gcore.1 b/system_cmds/gcore.tproj/gcore.1
new file mode 100644
index 0000000..48b360d
--- /dev/null
+++ b/system_cmds/gcore.tproj/gcore.1
@@ -0,0 +1,105 @@
+.Dd 2/10/16
+.Dt gcore 1
+.Os Darwin
+.Sh NAME
+.Nm gcore
+.Nd get core images of running processes
+.Sh SYNOPSIS
+.Nm
+.Op Fl s
+.Op Fl v
+.Op Fl b Ar size
+.Op Fl o Ar path | Fl c Ar pathformat
+.Ar pid
+.Sh DESCRIPTION
+The
+.Nm gcore
+program creates a core file image of the process specified by
+.Ar pid .
+The resulting core file can be used with a debugger, e.g.
+.Xr lldb(1) ,
+to examine the state of the process.
+.Pp
+The following options are available:
+.Bl -tag -width Fl
+.It Fl s
+Suspend the process while the core file is captured.
+.It Fl v
+Report progress on the dump as it proceeds.
+.It Fl b Ar size
+Limit the size of the core file to
+.Ar size
+MiBytes.
+.El
+.Pp
+The following options control the name of the core file:
+.Bl -tag -width flag
+.It Fl o Ar path
+Write the core file to
+.Ar path .
+.It Fl c Ar pathformat
+Write the core file to
+.Ar pathformat .
+The
+.Ar pathformat
+string is treated as a pathname that may contain various special
+characters which cause the interpolation of strings representing
+specific attributes of the process into the name.
+.Pp
+Each special character is introduced by the
+.Cm %
+character. The format characters and their meanings are:
+.Bl -tag -width Fl
+.It Cm N
+The name of the program being dumped, as reported by
+.Xr ps 1 .
+.It Cm U
+The uid of the process being dumped, converted to a string.
+.It Cm P
+The pid of the process being dumped, converted to a string.
+.It Cm T
+The time when the core file was taken, converted to ISO 8601 format.
+.It Cm %
+Output a percent character.
+.El
+.El
+.Pp
+The default file name used by
+.Nm gcore
+is
+.Ar %N-%P-%T .
+By default, the core file will be written to a directory whose
+name is determined from the
+.Ar kern.corefile
+MIB. This can be printed or modified using
+.Xr sysctl 8 .
+.Pp
+The directory where the core file is to be written must be
+accessible to the owner of the target process.
+.Pp
+.Nm gcore
+will not overwrite an existing file,
+nor will it create missing directories in the path.
+.Sh EXIT_STATUS
+.Ex -std
+.Pp
+.Sh FILES
+.Bl -tag -width "/cores/%N-%P-%T plus" -compact
+.It Pa /cores/%N-%P-%T
+default pathname for the corefile.
+.El
+.Sh BUGS
+With the
+.Fl b
+flag,
+.Nm gcore
+writes out as much data as it can up to the specified limit,
+even if that results in an incomplete core image.
+Such a partial core dump may confuse subsequent
+programs that attempt to parse the contents of such files.
+.Sh SEE ALSO
+.Xr lldb 1 ,
+.Xr core 5 ,
+.Xr Mach-O 5 ,
+.Xr sysctl 8 ,
+.Xr sudo 8 .
diff --git a/system_cmds/gcore.tproj/loader_additions.h b/system_cmds/gcore.tproj/loader_additions.h
new file mode 100644
index 0000000..16cf5b5
--- /dev/null
+++ b/system_cmds/gcore.tproj/loader_additions.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2015 Apple Inc. All rights reserved.
+ */
+
+#include <mach-o/loader.h>
+
+#ifndef _LOADER_ADDITIONS_H
+#define _LOADER_ADDITIONS_H
+
+/*
+ * Something like this should end up in <mach-o/loader.h>
+ */
+
+#define proto_LC_COREINFO 0x140 /* unofficial value!! */
+
+#define proto_CORETYPE_KERNEL 1
+#define proto_CORETYPE_USER 2
+#define proto_CORETYPE_IBOOT 3
+
+struct proto_coreinfo_command {
+ uint32_t cmd; /* LC_COREINFO */
+ uint32_t cmdsize; /* total size of this command */
+ uint32_t version; /* currently 1 */
+ uint16_t type; /* CORETYPE_KERNEL, CORETYPE_USER etc. */
+ uint16_t pageshift; /* log2 host pagesize */
+ /* content & interpretation depends on 'type' field */
+ uint64_t address; /* load address of "main binary" */
+ uint8_t uuid[16]; /* uuid of "main binary" */
+ uint64_t dyninfo; /* dynamic modules info */
+};
+
+#define proto_LC_FILEREF 0x141 /* unofficial value! */
+
+#define FREF_ID_SHIFT 0
+#define FREF_ID_MASK 0x7 /* up to 8 flavors */
+
+typedef enum {
+ kFREF_ID_NONE = 0, /* file has no available verifying ID */
+ kFREF_ID_UUID = 1, /* file has an associated UUID */
+ kFREF_ID_MTIMESPEC_LE = 2, /* file has a specific mtime */
+ /* one day: file has a computed hash? */
+} fref_type_t;
+
+#define FREF_ID_TYPE(f) ((fref_type_t)(((f) >> FREF_ID_SHIFT) & FREF_ID_MASK))
+#define FREF_MAKE_FLAGS(t) (((t) & FREF_ID_MASK) << FREF_ID_SHIFT)
+
+struct proto_fileref_command {
+ uint32_t cmd; /* LC_FILEREF */
+ uint32_t cmdsize;
+ union lc_str filename; /* filename these bits come from */
+ uint8_t id[16]; /* uuid, size or hash etc. */
+ uint64_t vmaddr; /* memory address of this segment */
+ uint64_t vmsize; /* memory size of this segment */
+ uint64_t fileoff; /* file offset of this segment */
+ uint64_t filesize; /* amount to map from the file */
+ vm_prot_t maxprot; /* maximum VM protection */
+ vm_prot_t prot; /* current VM protection */
+ uint32_t flags;
+ uint8_t share_mode; /* SM_COW etc. */
+ uint8_t purgable; /* VM_PURGABLE_NONVOLATILE etc. */
+ uint8_t tag; /* VM_MEMORY_MALLOC etc. */
+ uint8_t extp; /* external pager */
+};
+
+#define proto_LC_COREDATA 0x142 /* unofficial value! */
+
+/*
+ * These are flag bits for the segment_command 'flags' field.
+ */
+
+#define COMP_ALG_MASK 0x7
+/* carve out 3 bits for an enum i.e. allow for 7 flavors */
+#define COMP_ALG_SHIFT 4 /* (bottom 4 bits taken) */
+
+/* zero -> no compression */
+typedef enum {
+ kCOMP_NONE = 0,
+ kCOMP_LZ4 = 1, /* 0x100 */
+ kCOMP_ZLIB = 2, /* 0x205 */
+ kCOMP_LZMA = 3, /* 0x306 */
+ kCOMP_LZFSE = 4, /* 0x801 */
+} compression_flavor_t;
+
+#define COMP_ALG_TYPE(f) ((compression_flavor_t)((f) >> COMP_ALG_SHIFT) & COMP_ALG_MASK)
+#define COMP_MAKE_FLAGS(t) (((t) & COMP_ALG_MASK) << COMP_ALG_SHIFT)
+
+struct proto_coredata_command {
+ uint32_t cmd; /* LC_COREDATA */
+ uint32_t cmdsize;
+ uint64_t vmaddr; /* memory address of this segment */
+ uint64_t vmsize; /* memory size of this segment */
+ uint64_t fileoff; /* file offset of this segment */
+ uint64_t filesize; /* amount to map from the file */
+ vm_prot_t maxprot; /* maximum VM protection */
+ vm_prot_t prot; /* current VM protection */
+ uint32_t flags;
+ uint8_t share_mode; /* SM_COW etc. */
+ uint8_t purgable; /* VM_PURGABLE_NONVOLATILE etc. */
+ uint8_t tag; /* VM_MEMORY_MALLOC etc. */
+ uint8_t extp; /* external pager */
+};
+
+#endif /* _LOADER_ADDITIONS_H */
diff --git a/system_cmds/gcore.tproj/main.c b/system_cmds/gcore.tproj/main.c
new file mode 100644
index 0000000..abefa14
--- /dev/null
+++ b/system_cmds/gcore.tproj/main.c
@@ -0,0 +1,863 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "options.h"
+#include "utils.h"
+#include "corefile.h"
+#include "vanilla.h"
+#include "sparse.h"
+#include "convert.h"
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <libproc.h>
+
+#include <sys/kauth.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stddef.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 <libutil.h>
+
+#include <mach/mach.h>
+
+static char *
+kern_corefile(void)
+{
+ char *(^sysc_string)(const char *name) = ^(const char *name) {
+ char *p = NULL;
+ size_t len = 0;
+
+ if (-1 == sysctlbyname(name, NULL, &len, NULL, 0)) {
+ warnc(errno, "sysctl: %s", name);
+ } else if (0 != len) {
+ p = malloc(len);
+ if (-1 == sysctlbyname(name, p, &len, NULL, 0)) {
+ warnc(errno, "sysctl: %s", name);
+ free(p);
+ p = NULL;
+ }
+ }
+ return p;
+ };
+
+ char *s = sysc_string("kern.corefile");
+ if (NULL == s)
+ s = strdup("/cores/core.%P");
+ return s;
+}
+
+static const struct proc_bsdinfo *
+get_bsdinfo(pid_t pid)
+{
+ if (0 == pid)
+ return NULL;
+ struct proc_bsdinfo *pbi = calloc(1, sizeof (*pbi));
+ if (0 != proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, pbi, sizeof (*pbi)))
+ return pbi;
+ free(pbi);
+ return NULL;
+}
+
+static char *
+format_gcore_name(const char *fmt, pid_t pid, uid_t uid, const char *nm)
+{
+ __block size_t resid = MAXPATHLEN;
+ __block char *p = calloc(1, resid);
+ char *out = p;
+
+ int (^outchar)(int c) = ^(int c) {
+ if (resid > 1) {
+ *p++ = (char)c;
+ resid--;
+ return 1;
+ } else
+ return 0;
+ };
+
+ ptrdiff_t (^outstr)(const char *str) = ^(const char *str) {
+ const char *s = str;
+ while (*s && 0 != outchar(*s++))
+ ;
+ return s - str;
+ };
+
+ ptrdiff_t (^outint)(int value) = ^(int value) {
+ char id[11];
+ snprintf(id, sizeof (id), "%u", value);
+ return outstr(id);
+ };
+
+ ptrdiff_t (^outtstamp)(void) = ^(void) {
+ time_t now;
+ time(&now);
+ struct tm tm;
+ gmtime_r(&now, &tm);
+ char tstamp[50];
+ strftime(tstamp, sizeof (tstamp), "%Y%m%dT%H%M%SZ", &tm);
+ return outstr(tstamp);
+ };
+
+ int c;
+
+ for (int i = 0; resid > 0; i++)
+ switch (c = fmt[i]) {
+ default:
+ outchar(c);
+ break;
+ case '%':
+ i++;
+ switch (c = fmt[i]) {
+ case '%':
+ outchar(c);
+ break;
+ case 'P':
+ outint(pid);
+ break;
+ case 'U':
+ outint(uid);
+ break;
+ case 'N':
+ outstr(nm);
+ break;
+ case 'T':
+ outtstamp(); // ISO 8601 format
+ break;
+ default:
+ if (isprint(c))
+ err(EX_DATAERR, "unknown format char: %%%c", c);
+ else if (c != 0)
+ err(EX_DATAERR, "bad format char %%\\%03o", c);
+ else
+ err(EX_DATAERR, "bad format specifier");
+ }
+ break;
+ case 0:
+ outchar(c);
+ goto done;
+ }
+done:
+ return out;
+}
+
+static char *
+make_gcore_path(char **corefmtp, pid_t pid, uid_t uid, const char *nm)
+{
+ char *corefmt = *corefmtp;
+ if (NULL == corefmt) {
+ const char defcore[] = "%N-%P-%T";
+ if (NULL == (corefmt = kern_corefile()))
+ corefmt = strdup(defcore);
+ else {
+ // use the same directory as kern.corefile
+ char *p = strrchr(corefmt, '/');
+ if (NULL != p) {
+ *p = '\0';
+ size_t len = strlen(corefmt) + strlen(defcore) + 2;
+ char *buf = malloc(len);
+ snprintf(buf, len, "%s/%s", corefmt, defcore);
+ free(corefmt);
+ corefmt = buf;
+ }
+ if (OPTIONS_DEBUG(opt, 3))
+ printf("corefmt '%s'\n", corefmt);
+ }
+ }
+ char *path = format_gcore_name(corefmt, pid, uid, nm);
+ free(corefmt);
+ *corefmtp = NULL;
+ return path;
+}
+
+static bool proc_same_data_model(const struct proc_bsdinfo *pbi) {
+#if defined(__LP64__)
+ return (pbi->pbi_flags & PROC_FLAG_LP64) != 0;
+#else
+ return (pbi->pbi_flags & PROC_FLAG_LP64) == 0;
+#endif
+}
+
+static bool task_same_data_model(const task_flags_info_data_t *tfid) {
+#if defined(__LP64__)
+ return (tfid->flags & TF_LP64) != 0;
+#else
+ return (tfid->flags & TF_LP64) == 0;
+#endif
+}
+
+/*
+ * Change credentials for writing out the file
+ */
+static void
+change_credentials(gid_t uid, uid_t gid)
+{
+ if ((getgid() != gid && -1 == setgid(gid)) ||
+ (getuid() != uid && -1 == setuid(uid)))
+ errc(EX_NOPERM, errno, "insufficient privilege");
+ if (uid != getuid() || gid != getgid())
+ err(EX_OSERR, "wrong credentials");
+}
+
+static int
+openout(const char *corefname, char **coretname, struct stat *st)
+{
+ const int tfd = open(corefname, O_WRONLY);
+ if (-1 == tfd) {
+ if (ENOENT == errno) {
+ /*
+ * Arrange for a core file to appear "atomically": write the data
+ * to the file + ".tmp" suffix, then fchmod and rename it into
+ * place once the dump completes successfully.
+ */
+ const size_t nmlen = strlen(corefname) + 4 + 1;
+ char *tnm = malloc(nmlen);
+ snprintf(tnm, nmlen, "%s.tmp", corefname);
+ const int fd = open(tnm, O_WRONLY | O_CREAT | O_TRUNC, 0600);
+ if (-1 == fd || -1 == fstat(fd, st))
+ errc(EX_CANTCREAT, errno, "%s", tnm);
+ if (!S_ISREG(st->st_mode) || 1 != st->st_nlink)
+ errx(EX_CANTCREAT, "%s: invalid attributes", tnm);
+ *coretname = tnm;
+ return fd;
+ } else
+ errc(EX_CANTCREAT, errno, "%s", corefname);
+ } else if (-1 == fstat(tfd, st)) {
+ close(tfd);
+ errx(EX_CANTCREAT, "%s: fstat", corefname);
+ } else if (S_ISBLK(st->st_mode) || S_ISCHR(st->st_mode)) {
+ /*
+ * Write dump to a device, no rename!
+ */
+ *coretname = NULL;
+ return tfd;
+ } else {
+ close(tfd);
+ errc(EX_CANTCREAT, EEXIST, "%s", corefname);
+ }
+}
+
+static int
+closeout(int fd, int ecode, char *corefname, char *coretname, const struct stat *st)
+{
+ if (0 != ecode && !opt->preserve && S_ISREG(st->st_mode))
+ ftruncate(fd, 0); // limit large file clutter
+ if (0 == ecode && S_ISREG(st->st_mode))
+ fchmod(fd, 0400); // protect core files
+ if (-1 == close(fd)) {
+ warnc(errno, "%s: close", coretname ? coretname : corefname);
+ ecode = EX_OSERR;
+ }
+ if (NULL != coretname) {
+ if (0 == ecode && -1 == rename(coretname, corefname)) {
+ warnc(errno, "cannot rename %s to %s", coretname, corefname);
+ ecode = EX_NOPERM;
+ }
+ free(coretname);
+ }
+ if (corefname)
+ free(corefname);
+ return ecode;
+}
+
+const char *pgm;
+const struct options *opt;
+
+static const size_t oneK = 1024;
+static const size_t oneM = oneK * oneK;
+
+#define LARGEST_CHUNKSIZE INT32_MAX
+#define DEFAULT_COMPRESSION_CHUNKSIZE (16 * oneM)
+#define DEFAULT_NC_THRESHOLD (17 * oneK)
+
+static struct options options = {
+ .corpsify = 0,
+ .suspend = 0,
+ .preserve = 0,
+ .verbose = 0,
+#ifdef CONFIG_DEBUG
+ .debug = 0,
+#endif
+ .extended = 0,
+ .sizebound = 0,
+ .chunksize = 0,
+ .calgorithm = COMPRESSION_LZFSE,
+ .ncthresh = DEFAULT_NC_THRESHOLD,
+ .dsymforuuid = 0,
+};
+
+static int
+gcore_main(int argc, char *const *argv)
+{
+#define ZOPT_ALG (0)
+#define ZOPT_CHSIZE (ZOPT_ALG + 1)
+
+ static char *const zoptkeys[] = {
+ [ZOPT_ALG] = "algorithm",
+ [ZOPT_CHSIZE] = "chunksize",
+ NULL
+ };
+
+ err_set_exit_b(^(int eval) {
+ if (EX_USAGE == eval) {
+ fprintf(stderr,
+ "usage:\t%s [-s] [-v] [[-o file] | [-c pathfmt ]] [-b size] "
+#if DEBUG
+#ifdef CONFIG_DEBUG
+ "[-d] "
+#endif
+ "[-x] [-C] "
+ "[-Z compression-options] "
+ "[-t size] "
+ "[-F] "
+#endif
+ "pid\n", pgm);
+#if DEBUG
+ fprintf(stderr, "where compression-options:\n");
+ const char zvalfmt[] = "\t%s=%s\t\t%s\n";
+ fprintf(stderr, zvalfmt, zoptkeys[ZOPT_ALG], "alg",
+ "set compression algorithm");
+ fprintf(stderr, zvalfmt, zoptkeys[ZOPT_CHSIZE], "size",
+ "set compression chunksize, Mib");
+#endif
+ }
+ });
+
+ char *corefmt = NULL;
+ char *corefname = NULL;
+
+ int c;
+ char *sopts, *value;
+
+ while ((c = getopt(argc, argv, "vdsxCFZ:o:c:b:t:")) != -1) {
+ switch (c) {
+
+ /*
+ * documented options
+ */
+ case 's': /* FreeBSD compat: stop while gathering */
+ options.suspend = 1;
+ break;
+ case 'o': /* Linux (& SunOS) compat: basic name */
+ corefname = strdup(optarg);
+ break;
+ case 'c': /* FreeBSD compat: basic name */
+ /* (also allows pattern-based naming) */
+ corefmt = strdup(optarg);
+ break;
+
+ case 'b': /* bound the size of the core file */
+ if (NULL != optarg) {
+ off_t bsize = atoi(optarg) * oneM;
+ if (bsize > 0)
+ options.sizebound = bsize;
+ else
+ errx(EX_USAGE, "invalid bound");
+ } else
+ errx(EX_USAGE, "no bound specified");
+ break;
+ case 'v': /* verbose output */
+ options.verbose++;
+ break;
+
+ /*
+ * dev and debugging help
+ */
+#ifdef CONFIG_DEBUG
+ case 'd': /* debugging */
+ options.debug++;
+ options.verbose++;
+ options.preserve++;
+ break;
+#endif
+ /*
+ * Remaining options are experimental and/or
+ * affect the content of the core file
+ */
+ case 'x': /* write extended format (small) core files */
+ options.extended++;
+ options.chunksize = DEFAULT_COMPRESSION_CHUNKSIZE;
+ break;
+ case 'C': /* forcibly corpsify rather than suspend */
+ options.corpsify++;
+ break;
+ case 'Z': /* control compression options */
+ /*
+ * Only LZFSE and LZ4 seem practical.
+ * (Default to LZ4 compression when the
+ * process is suspended, LZFSE when corpsed?)
+ */
+ if (0 == options.extended)
+ errx(EX_USAGE, "illegal flag combination");
+ sopts = optarg;
+ while (*sopts) {
+ size_t chsize;
+
+ switch (getsubopt(&sopts, zoptkeys, &value)) {
+ case ZOPT_ALG: /* change the algorithm */
+ if (NULL == value)
+ errx(EX_USAGE, "missing algorithm for "
+ "%s suboption",
+ zoptkeys[ZOPT_ALG]);
+ if (strcmp(value, "lz4") == 0)
+ options.calgorithm = COMPRESSION_LZ4;
+ else if (strcmp(value, "zlib") == 0)
+ options.calgorithm = COMPRESSION_ZLIB;
+ else if (strcmp(value, "lzma") == 0)
+ options.calgorithm = COMPRESSION_LZMA;
+ else if (strcmp(value, "lzfse") == 0)
+ options.calgorithm = COMPRESSION_LZFSE;
+ else
+ errx(EX_USAGE, "unknown algorithm '%s'"
+ " for %s suboption",
+ value, zoptkeys[ZOPT_ALG]);
+ break;
+ case ZOPT_CHSIZE: /* set the chunksize */
+ if (NULL == value)
+ errx(EX_USAGE, "no value specified for "
+ "%s suboption",
+ zoptkeys[ZOPT_CHSIZE]);
+ if ((chsize = atoi(value)) < 1)
+ errx(EX_USAGE, "chunksize %lu too small", chsize);
+ if (chsize > (LARGEST_CHUNKSIZE / oneM))
+ errx(EX_USAGE, "chunksize %lu too large", chsize);
+ options.chunksize = chsize * oneM;
+ break;
+ default:
+ if (suboptarg)
+ errx(EX_USAGE, "illegal suboption '%s'",
+ suboptarg);
+ else
+ errx(EX_USAGE, "missing suboption");
+ }
+ }
+ break;
+ case 't': /* set the F_NOCACHE threshold */
+ if (NULL != optarg) {
+ size_t tsize = atoi(optarg) * oneK;
+ if (tsize > 0)
+ options.ncthresh = tsize;
+ else
+ errx(EX_USAGE, "invalid nc threshold");
+ } else
+ errx(EX_USAGE, "no threshold specified");
+ break;
+ case 'F': /* maximize filerefs */
+ options.allfilerefs++;
+ break;
+ default:
+ errx(EX_USAGE, "unknown flag");
+ }
+ }
+
+ if (optind == argc)
+ errx(EX_USAGE, "no pid specified");
+ if (optind < argc-1)
+ errx(EX_USAGE, "too many arguments");
+
+ opt = &options;
+ if (NULL != corefname && NULL != corefmt)
+ errx(EX_USAGE, "specify only one of -o and -c");
+ if (!opt->extended && opt->allfilerefs)
+ errx(EX_USAGE, "unknown flag");
+
+ setpageshift();
+
+ if (opt->ncthresh < ((vm_offset_t)1 << pageshift_host))
+ errx(EX_USAGE, "threshold %lu less than host pagesize", opt->ncthresh);
+
+ const pid_t apid = atoi(argv[optind]);
+ pid_t pid = apid;
+ mach_port_t corpse = MACH_PORT_NULL;
+ kern_return_t ret;
+
+ if (0 == apid) {
+ /* look for corpse - dead or alive */
+ mach_port_array_t parray = NULL;
+ mach_msg_type_number_t pcount = 0;
+ ret = mach_ports_lookup(mach_task_self(), &parray, &pcount);
+ if (KERN_SUCCESS == ret && pcount > 0) {
+ task_t tcorpse = parray[0];
+ mig_deallocate((vm_address_t)parray, pcount * sizeof (*parray));
+ pid_t tpid = 0;
+ ret = pid_for_task(tcorpse, &tpid);
+ if (KERN_SUCCESS == ret && tpid != getpid()) {
+ corpse = tcorpse;
+ pid = tpid;
+ }
+ }
+ }
+
+ if (pid < 1 || getpid() == pid)
+ errx(EX_DATAERR, "invalid pid: %d", pid);
+
+ if (0 == apid && MACH_PORT_NULL == corpse)
+ errx(EX_DATAERR, "missing or bad corpse from parent");
+
+ task_t task = TASK_NULL;
+ const struct proc_bsdinfo *pbi = NULL;
+ const int rc = kill(pid, 0);
+
+ if (rc == 0) {
+ /* process or corpse that may respond to signals */
+ pbi = get_bsdinfo(pid);
+ }
+
+ if (rc == 0 && pbi != NULL) {
+ /* process or corpse that responds to signals */
+
+ /* make our data model match the data model of the target */
+ if (-1 == reexec_to_match_lp64ness(pbi->pbi_flags & PROC_FLAG_LP64))
+ errc(1, errno, "cannot match data model of %d", pid);
+
+ if (!proc_same_data_model(pbi))
+ errx(EX_OSERR, "cannot match data model of %d", pid);
+
+ if (pbi->pbi_ruid != pbi->pbi_svuid ||
+ pbi->pbi_rgid != pbi->pbi_svgid)
+ errx(EX_NOPERM, "pid %d - not dumping a set-id process", pid);
+
+ if (NULL == corefname)
+ corefname = make_gcore_path(&corefmt, pbi->pbi_pid, pbi->pbi_uid, pbi->pbi_name[0] ? pbi->pbi_name : pbi->pbi_comm);
+
+ if (MACH_PORT_NULL == corpse) {
+ ret = task_for_pid(mach_task_self(), pid, &task);
+ if (KERN_SUCCESS != ret) {
+ if (KERN_FAILURE == ret)
+ errx(EX_NOPERM, "insufficient privilege");
+ else
+ errx(EX_NOPERM, "task_for_pid: %s", mach_error_string(ret));
+ }
+ }
+
+ /*
+ * Have either the corpse port or the task port so adopt the
+ * credentials of the target process, *before* opening the
+ * core file, and analyzing the address space.
+ *
+ * If we are unable to match the target credentials, bail out.
+ */
+ change_credentials(pbi->pbi_uid, pbi->pbi_gid);
+ } else {
+ if (MACH_PORT_NULL == corpse) {
+ if (rc == 0) {
+ errx(EX_OSERR, "cannot get process info for %d", pid);
+ }
+ switch (errno) {
+ case ESRCH:
+ errc(EX_DATAERR, errno, "no process with pid %d", pid);
+ default:
+ errc(EX_DATAERR, errno, "pid %d", pid);
+ }
+ }
+ /* a corpse with no live process backing it */
+
+ assert(0 == apid && TASK_NULL == task);
+
+ task_flags_info_data_t tfid;
+ mach_msg_type_number_t count = TASK_FLAGS_INFO_COUNT;
+ ret = task_info(corpse, TASK_FLAGS_INFO, (task_info_t)&tfid, &count);
+ if (KERN_SUCCESS != ret)
+ err_mach(ret, NULL, "task_info");
+ if (!task_same_data_model(&tfid))
+ errx(EX_OSERR, "data model mismatch for target corpse");
+
+ if (opt->suspend || opt->corpsify)
+ errx(EX_USAGE, "cannot use -s or -C option with a corpse");
+ if (NULL != corefmt)
+ errx(EX_USAGE, "cannot use -c with a corpse");
+ if (NULL == corefname)
+ corefname = make_gcore_path(&corefmt, pid, -2, "corpse");
+
+ /*
+ * Only have a corpse, thus no process credentials.
+ * Switch to nobody.
+ */
+ change_credentials(-2, -2);
+ }
+
+ struct stat cst;
+ char *coretname = NULL;
+ const int fd = openout(corefname, &coretname, &cst);
+
+ if (opt->verbose) {
+ printf("Dumping core ");
+ if (OPTIONS_DEBUG(opt, 1)) {
+ printf("(%s", opt->extended ? "extended" : "vanilla");
+ if (0 != opt->sizebound) {
+ hsize_str_t hstr;
+ printf(", <= %s", str_hsize(hstr, opt->sizebound));
+ }
+ printf(") ");
+ }
+ printf("for pid %d to %s\n", pid, corefname);
+ }
+
+ int ecode;
+
+ if (MACH_PORT_NULL == corpse) {
+ assert(TASK_NULL != task);
+
+ /*
+ * The "traditional" way to capture a consistent core dump is to
+ * suspend the process while examining it and writing it out.
+ * Yet suspending a large process for a long time can have
+ * unpleasant side-effects. Alternatively dumping from the live
+ * process can lead to an inconsistent state in the core file.
+ *
+ * Instead we can ask xnu to create a 'corpse' - the process is transiently
+ * suspended while a COW snapshot of the address space is constructed
+ * in the kernel and dump from that. This vastly reduces the suspend
+ * time, but it is more resource hungry and thus may fail.
+ *
+ * The -s flag (opt->suspend) causes a task_suspend/task_resume
+ * The -C flag (opt->corpse) causes a corpse be taken, exiting if that fails.
+ * Both flags can be specified, in which case corpse errors are ignored.
+ *
+ * With no flags, we imitate traditional behavior though more
+ * efficiently: we try to take a corpse-based dump, in the event that
+ * fails, dump the live process.
+ */
+
+ int trycorpse = 1; /* default: use corpses */
+ int badcorpse_is_fatal = 1; /* default: failure to create is an error */
+
+ if (!opt->suspend && !opt->corpsify) {
+ /* try a corpse dump, else dump the live process */
+ badcorpse_is_fatal = 0;
+ } else if (opt->suspend) {
+ trycorpse = opt->corpsify;
+ /* if suspended anyway, ignore corpse-creation errors */
+ badcorpse_is_fatal = 0;
+ }
+
+ if (opt->suspend)
+ task_suspend(task);
+
+ if (trycorpse) {
+ /*
+ * Create a corpse from the image before dumping it
+ */
+ ret = task_generate_corpse(task, &corpse);
+ switch (ret) {
+ case KERN_SUCCESS:
+ if (OPTIONS_DEBUG(opt, 1))
+ printf("Corpse generated on port %x, task %x\n",
+ corpse, task);
+ ecode = coredump(corpse, fd, pbi);
+ mach_port_deallocate(mach_task_self(), corpse);
+ break;
+ default:
+ if (badcorpse_is_fatal || opt->verbose) {
+ warnx("failed to snapshot pid %d: %s\n",
+ pid, mach_error_string(ret));
+ if (badcorpse_is_fatal) {
+ ecode = KERN_RESOURCE_SHORTAGE == ret ? EX_TEMPFAIL : EX_OSERR;
+ goto out;
+ }
+ }
+ ecode = coredump(task, fd, pbi);
+ break;
+ }
+ } else {
+ /*
+ * Examine the task directly
+ */
+ ecode = coredump(task, fd, pbi);
+ }
+
+ out:
+ if (opt->suspend)
+ task_resume(task);
+ } else {
+ /*
+ * Handed a corpse by our parent.
+ */
+ ecode = coredump(corpse, fd, pbi);
+ mach_port_deallocate(mach_task_self(), corpse);
+ }
+
+ ecode = closeout(fd, ecode, corefname, coretname, &cst);
+ if (ecode)
+ errx(ecode, "failed to dump core for pid %d", pid);
+ return 0;
+}
+
+#if defined(CONFIG_GCORE_FREF) || defined(CONFIG_GCORE_MAP) || defined(GCONFIG_GCORE_CONV)
+
+static int
+getcorefd(const char *infile)
+{
+ const int fd = open(infile, O_RDONLY | O_CLOEXEC);
+ if (-1 == fd)
+ errc(EX_DATAERR, errno, "cannot open %s", infile);
+
+ struct mach_header mh;
+ if (-1 == pread(fd, &mh, sizeof (mh), 0))
+ errc(EX_OSERR, errno, "cannot read mach header from %s", infile);
+
+ static const char cant_match_data_model[] = "cannot match the data model of %s";
+
+ if (-1 == reexec_to_match_lp64ness(MH_MAGIC_64 == mh.magic))
+ errc(1, errno, cant_match_data_model, infile);
+
+ if (NATIVE_MH_MAGIC != mh.magic)
+ errx(EX_OSERR, cant_match_data_model, infile);
+ if (MH_CORE != mh.filetype)
+ errx(EX_DATAERR, "%s is not a mach core file", infile);
+ return fd;
+}
+
+#endif
+
+#ifdef CONFIG_GCORE_FREF
+
+static int
+gcore_fref_main(int argc, char *argv[])
+{
+ err_set_exit_b(^(int eval) {
+ if (EX_USAGE == eval) {
+ fprintf(stderr, "usage:\t%s %s corefile\n", pgm, argv[1]);
+ }
+ });
+ if (2 == argc)
+ errx(EX_USAGE, "no input corefile");
+ if (argc > 3)
+ errx(EX_USAGE, "too many arguments");
+ opt = &options;
+ return gcore_fref(getcorefd(argv[2]));
+}
+
+#endif /* CONFIG_GCORE_FREF */
+
+#ifdef CONFIG_GCORE_MAP
+
+static int
+gcore_map_main(int argc, char *argv[])
+{
+ err_set_exit_b(^(int eval) {
+ if (EX_USAGE == eval) {
+ fprintf(stderr, "usage:\t%s %s corefile\n", pgm, argv[1]);
+ }
+ });
+ if (2 == argc)
+ errx(EX_USAGE, "no input corefile");
+ if (argc > 3)
+ errx(EX_USAGE, "too many arguments");
+ opt = &options;
+ return gcore_map(getcorefd(argv[2]));
+}
+
+#endif
+
+#ifdef CONFIG_GCORE_CONV
+
+static int
+gcore_conv_main(int argc, char *argv[])
+{
+ err_set_exit_b(^(int eval) {
+ if (EX_USAGE == eval)
+ fprintf(stderr,
+ "usage:\t%s %s [-v] [-L searchpath] [-z] [-s] incore outcore\n", pgm, argv[1]);
+ });
+
+ char *searchpath = NULL;
+ bool zf = false;
+
+ int c;
+ optind = 2;
+ while ((c = getopt(argc, argv, "dzvL:s")) != -1) {
+ switch (c) {
+ /*
+ * likely documented options
+ */
+ case 'L':
+ searchpath = strdup(optarg);
+ break;
+ case 'z':
+ zf = true;
+ break;
+ case 'v':
+ options.verbose++;
+ break;
+ case 's':
+ options.dsymforuuid++;
+ break;
+ /*
+ * dev and debugging help
+ */
+#ifdef CONFIG_DEBUG
+ case 'd':
+ options.debug++;
+ options.verbose++;
+ options.preserve++;
+ break;
+#endif
+ default:
+ errx(EX_USAGE, "unknown flag");
+ }
+ }
+ if (optind == argc)
+ errx(EX_USAGE, "no input corefile");
+ if (optind == argc - 1)
+ errx(EX_USAGE, "no output corefile");
+ if (optind < argc - 2)
+ errx(EX_USAGE, "too many arguments");
+
+ const char *incore = argv[optind];
+ char *corefname = strdup(argv[optind+1]);
+
+ opt = &options;
+
+ setpageshift();
+
+ if (opt->ncthresh < ((vm_offset_t)1 << pageshift_host))
+ errx(EX_USAGE, "threshold %lu less than host pagesize", opt->ncthresh);
+
+ const int infd = getcorefd(incore);
+ struct stat cst;
+ char *coretname = NULL;
+ const int fd = openout(corefname, &coretname, &cst);
+ int ecode = gcore_conv(infd, searchpath, zf, fd);
+ ecode = closeout(fd, ecode, corefname, coretname, &cst);
+ if (ecode)
+ errx(ecode, "failed to convert core file successfully");
+ return 0;
+}
+#endif
+
+int
+main(int argc, char *argv[])
+{
+ if (NULL == (pgm = strrchr(*argv, '/')))
+ pgm = *argv;
+ else
+ pgm++;
+#ifdef CONFIG_GCORE_FREF
+ if (argc > 1 && 0 == strcmp(argv[1], "fref")) {
+ return gcore_fref_main(argc, argv);
+ }
+#endif
+#ifdef CONFIG_GCORE_MAP
+ if (argc > 1 && 0 == strcmp(argv[1], "map")) {
+ return gcore_map_main(argc, argv);
+ }
+#endif
+#ifdef CONFIG_GCORE_CONV
+ if (argc > 1 && 0 == strcmp(argv[1], "conv")) {
+ return gcore_conv_main(argc, argv);
+ }
+#endif
+ return gcore_main(argc, argv);
+}
diff --git a/system_cmds/gcore.tproj/options.h b/system_cmds/gcore.tproj/options.h
new file mode 100644
index 0000000..71481fa
--- /dev/null
+++ b/system_cmds/gcore.tproj/options.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include <sys/types.h>
+#include <compression.h>
+
+#include <assert.h>
+
+#ifndef _OPTIONS_H
+#define _OPTIONS_H
+
+#if defined(__arm__) || defined(__arm64__)
+#define RDAR_23744374 1 /* 'true' while not fixed i.e. enable workarounds */
+#define RDAR_28040018 1 /* 'true' while not fixed i.e. enable workarounds */
+#endif
+
+//#define CONFIG_SUBMAP 1 /* include submaps (debugging output) */
+#define CONFIG_GCORE_MAP 1 /* support 'gcore map' */
+#define CONFIG_GCORE_CONV 1 /* support 'gcore conv' - new -> old core files */
+#define CONFIG_GCORE_FREF 1 /* support 'gcore fref' - referenced file list */
+#define CONFIG_DEBUG 1 /* support '-d' option */
+
+#ifdef NDEBUG
+#define poison(a, p, s) /* do nothing */
+#else
+#define poison(a, p, s) memset(a, p, s) /* scribble on dying memory */
+#endif
+
+struct options {
+ int corpsify; // make a corpse to dump from
+ int suspend; // suspend while dumping
+ int preserve; // preserve the core file, even if there are errors
+ int verbose; // be chatty
+#ifdef CONFIG_DEBUG
+ int debug; // internal debugging: options accumulate. noisy.
+#endif
+ int extended; // avoid writing out ro mapped files, compress regions
+ off_t sizebound; // maximum size of the dump
+ size_t chunksize; // max size of a compressed subregion
+ compression_algorithm calgorithm; // algorithm in use
+ size_t ncthresh; // F_NOCACHE enabled *above* this value
+ int allfilerefs; // if set, every mapped file on the root fs is a fileref
+ int dsymforuuid; // Try dsysForUUID to retrieve symbol-rich executable
+};
+
+extern const struct options *opt;
+
+/*
+ * == 0 - not verbose
+ * >= 1 - verbose plus chatty
+ * >= 2 - tabular summaries
+ * >= 3 - all
+ */
+
+#ifdef CONFIG_DEBUG
+#define OPTIONS_DEBUG(opt, lvl) ((opt)->debug && (opt)->debug >= (lvl))
+#else
+#define OPTIONS_DEBUG(opt, lvl) 0
+#endif
+
+#endif /* _OPTIONS_H */
diff --git a/system_cmds/gcore.tproj/region.h b/system_cmds/gcore.tproj/region.h
new file mode 100644
index 0000000..77853b2
--- /dev/null
+++ b/system_cmds/gcore.tproj/region.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <mach/mach.h>
+#include <uuid/uuid.h>
+
+#ifndef _REGION_H
+#define _REGION_H
+
+/*
+ * A range of virtual memory
+ */
+struct vm_range {
+ mach_vm_offset_t addr;
+ mach_vm_offset_t size;
+};
+
+#define _V_ADDR(g) ((g)->addr)
+#define _V_SIZE(g) ((g)->size)
+#define V_SETADDR(g, a) ((g)->addr = (a))
+#define V_SETSIZE(g, z) ((g)->size = (z))
+#define V_ENDADDR(g) (_V_ADDR(g) + _V_SIZE(g))
+
+static __inline const mach_vm_offset_t V_ADDR(const struct vm_range *vr) {
+ return _V_ADDR(vr);
+}
+static __inline const mach_vm_offset_t V_SIZE(const struct vm_range *vr) {
+ return _V_SIZE(vr);
+}
+static __inline const size_t V_SIZEOF(const struct vm_range *vr) {
+ assert((typeof (vr->size))(size_t)_V_SIZE(vr) == _V_SIZE(vr));
+ return (size_t)_V_SIZE(vr);
+}
+
+/*
+ * A range of offsets into a file
+ */
+struct file_range {
+ off_t off;
+ off_t size;
+};
+
+#define F_OFF(f) ((f)->off)
+#define F_SIZE(f) ((f)->size)
+
+struct regionop;
+struct subregion;
+
+struct region {
+ STAILQ_ENTRY(region) r_linkage;
+
+ struct vm_range r_range;
+
+#define R_RANGE(r) (&(r)->r_range)
+#define _R_ADDR(r) _V_ADDR(R_RANGE(r))
+#define _R_SIZE(r) _V_SIZE(R_RANGE(r))
+#define R_SIZEOF(r) V_SIZEOF(R_RANGE(r))
+#define R_SETADDR(r, a) V_SETADDR(R_RANGE(r), (a))
+#define R_SETSIZE(r, z) V_SETSIZE(R_RANGE(r), (z))
+#define R_ENDADDR(r) (_R_ADDR(r) + _R_SIZE(r))
+
+ vm_region_submap_info_data_64_t r_info;
+ vm_page_info_basic_data_t r_pageinfo;
+
+ int r_purgable;
+
+#ifdef CONFIG_SUBMAP
+ int r_depth;
+#endif
+ boolean_t r_insharedregion, r_inzfodregion, r_incommregion;
+
+ /*
+ * This field may be non-NULL if the region is a read-only part
+ * of a mapped file (i.e. the shared cache) and thus
+ * doesn't need to be copied.
+ */
+ struct {
+ const struct libent *fr_libent;
+ const char *fr_pathname;
+ off_t fr_offset;
+ } *r_fileref;
+
+ /*
+ * These (optional) fields are filled in after we parse the information
+ * about the dylibs we've mapped, as provided by dyld.
+ */
+ struct subregion **r_subregions;
+ unsigned r_nsubregions;
+
+ const struct regionop *r_op;
+};
+
+static __inline const mach_vm_offset_t R_ADDR(const struct region *r) {
+ return _R_ADDR(r);
+}
+static __inline const mach_vm_offset_t R_SIZE(const struct region *r) {
+ return _R_SIZE(r);
+}
+
+/*
+ * Describes the disposition of the region after a walker returns
+ */
+typedef enum {
+ WALK_CONTINUE, // press on ..
+ WALK_DELETE_REGION, // discard this region, then continue
+ WALK_TERMINATE, // early termination, no error
+ WALK_ERROR, // early termination, error
+} walk_return_t;
+
+struct size_core;
+struct write_segment_data;
+
+typedef walk_return_t walk_region_cbfn_t(struct region *, void *);
+
+struct regionop {
+ void (*rop_print)(const struct region *);
+ walk_return_t (*rop_write)(const struct region *, struct write_segment_data *);
+ void (*rop_delete)(struct region *);
+};
+
+#define ROP_PRINT(r) (((r)->r_op->rop_print)(r))
+#define ROP_WRITE(r, w) (((r)->r_op->rop_write)(r, w))
+#define ROP_DELETE(r) (((r)->r_op->rop_delete)(r))
+
+extern const struct regionop vanilla_ops, sparse_ops, zfod_ops;
+extern const struct regionop fileref_ops;
+
+struct regionhead;
+
+#endif /* _REGION_H */
diff --git a/system_cmds/gcore.tproj/sparse.c b/system_cmds/gcore.tproj/sparse.c
new file mode 100644
index 0000000..c62b9f3
--- /dev/null
+++ b/system_cmds/gcore.tproj/sparse.c
@@ -0,0 +1,497 @@
+/*
+ * 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 "sparse.h"
+#include "vanilla.h"
+#include "corefile.h"
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/stat.h>
+#include <sys/mman.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 <mach/mach.h>
+
+static struct subregion *
+new_subregion(
+ const mach_vm_offset_t vmaddr,
+ const mach_vm_offset_t vmsize,
+ const native_segment_command_t *sc,
+ const struct libent *le)
+{
+ struct subregion *s = malloc(sizeof (*s));
+
+ assert(vmaddr != 0 && vmsize != 0);
+ assert(vmaddr < vmaddr + vmsize);
+ s->s_segcmd = *sc;
+
+ S_SETADDR(s, vmaddr);
+ S_SETSIZE(s, vmsize);
+
+ s->s_libent = le;
+ s->s_isuuidref = false;
+ return s;
+}
+
+static void
+del_subregion(struct subregion *s)
+{
+ poison(s, 0xfacefac1, sizeof (*s));
+ free(s);
+}
+
+static walk_return_t
+clean_subregions(struct region *r)
+{
+ if (r->r_nsubregions) {
+ assert(r->r_subregions);
+ for (unsigned i = 0; i < r->r_nsubregions; i++)
+ del_subregion(r->r_subregions[i]);
+ poison(r->r_subregions, 0xfac1fac1, sizeof (r->r_subregions[0]) * r->r_nsubregions);
+ free(r->r_subregions);
+ r->r_nsubregions = 0;
+ r->r_subregions = NULL;
+ } else {
+ assert(NULL == r->r_subregions);
+ }
+ return WALK_CONTINUE;
+}
+
+void
+del_sparse_region(struct region *r)
+{
+ clean_subregions(r);
+ poison(r, 0xcafecaff, sizeof (*r));
+ free(r);
+}
+
+#define NULLsc ((native_segment_command_t *)0)
+
+static bool
+issamesubregiontype(const struct subregion *s0, const struct subregion *s1) {
+ return 0 == strncmp(S_MACHO_TYPE(s0), S_MACHO_TYPE(s1), sizeof (NULLsc->segname));
+}
+
+bool
+issubregiontype(const struct subregion *s, const char *sctype) {
+ return 0 == strncmp(S_MACHO_TYPE(s), sctype, sizeof (NULLsc->segname));
+}
+
+static void
+elide_subregion(struct region *r, unsigned ind)
+{
+ del_subregion(r->r_subregions[ind]);
+ for (unsigned j = ind; j < r->r_nsubregions - 1; j++)
+ r->r_subregions[j] = r->r_subregions[j+1];
+ assert(r->r_nsubregions != 0);
+ r->r_subregions[--r->r_nsubregions] = NULL;
+}
+
+struct subregionlist {
+ STAILQ_ENTRY(subregionlist) srl_linkage;
+ struct subregion *srl_s;
+};
+typedef STAILQ_HEAD(, subregionlist) subregionlisthead_t;
+
+static walk_return_t
+add_subregions_for_libent(
+ subregionlisthead_t *srlh,
+ const struct region *r,
+ const native_mach_header_t *mh,
+ const mach_vm_offset_t __unused mh_taddr, // address in target
+ const struct libent *le)
+{
+ const struct load_command *lc = (const void *)(mh + 1);
+ mach_vm_offset_t objoff = le->le_objoff;
+ for (unsigned n = 0; n < mh->ncmds; n++) {
+
+ const native_segment_command_t *sc;
+
+ switch (lc->cmd) {
+ case NATIVE_LC_SEGMENT:
+ sc = (const void *)lc;
+
+ if (0 == sc->vmaddr && strcmp(sc->segname, SEG_PAGEZERO) == 0)
+ break;
+ mach_vm_offset_t lo = sc->vmaddr + objoff;
+ mach_vm_offset_t hi = lo + sc->vmsize;
+
+ /* Eliminate non-overlapping sections first */
+
+ if (R_ENDADDR(r) - 1 < lo)
+ break;
+ if (hi - 1 < R_ADDR(r))
+ break;
+
+ /*
+ * Some part of this segment is in the region.
+ * Trim the edges in the case where we span regions.
+ */
+ if (lo < R_ADDR(r))
+ lo = R_ADDR(r);
+ if (hi > R_ENDADDR(r))
+ hi = R_ENDADDR(r);
+
+ struct subregionlist *srl = calloc(1, sizeof (*srl));
+ struct subregion *s = new_subregion(lo, hi - lo, sc, le);
+ assert(sc->fileoff >= 0);
+ srl->srl_s = s;
+ STAILQ_INSERT_HEAD(srlh, srl, srl_linkage);
+
+ if (OPTIONS_DEBUG(opt, 2)) {
+ hsize_str_t hstr;
+ printr(r, "subregion %llx-%llx %7s %12s\t%s [%s off %lu for %lu nsects %u flags %x]\n",
+ S_ADDR(s), S_ENDADDR(s),
+ str_hsize(hstr, S_SIZE(s)),
+ sc->segname,
+ S_FILENAME(s),
+ str_prot(sc->initprot),
+ (unsigned long)sc->fileoff,
+ (unsigned long)sc->filesize,
+ sc->nsects, sc->flags);
+ }
+ break;
+ default:
+ break;
+ }
+ if (lc->cmdsize)
+ lc = (const void *)((caddr_t)lc + lc->cmdsize);
+ else
+ break;
+ }
+ return WALK_CONTINUE;
+}
+
+/*
+ * Because we aggregate information from multiple sources, there may
+ * be duplicate subregions. Eliminate them here.
+ *
+ * Note that the each library in the shared cache points
+ * separately at a single, unified (large!) __LINKEDIT section; these
+ * get removed here too.
+ *
+ * Assumes the subregion array is sorted by address!
+ */
+static void
+eliminate_duplicate_subregions(struct region *r)
+{
+ unsigned i = 1;
+ while (i < r->r_nsubregions) {
+ struct subregion *s0 = r->r_subregions[i-1];
+ struct subregion *s1 = r->r_subregions[i];
+
+ if (S_ADDR(s0) != S_ADDR(s1) || S_SIZE(s0) != S_SIZE(s1)) {
+ i++;
+ continue;
+ }
+ if (memcmp(&s0->s_segcmd, &s1->s_segcmd, sizeof (s0->s_segcmd)) != 0) {
+ i++;
+ continue;
+ }
+ if (OPTIONS_DEBUG(opt, 3))
+ printr(r, "eliding duplicate %s subregion (%llx-%llx) file %s\n",
+ S_MACHO_TYPE(s1), S_ADDR(s1), S_ENDADDR(s1), S_FILENAME(s1));
+ /* If the duplicate subregions aren't mapping the same file (?), forget the name */
+ if (s0->s_libent != s1->s_libent)
+ s0->s_libent = s1->s_libent = NULL;
+ elide_subregion(r, i);
+ }
+}
+
+/*
+ * See if any of the dyld information we have can better describe this
+ * region of the target address space.
+ */
+walk_return_t
+decorate_memory_region(struct region *r, void *arg)
+{
+ if (r->r_inzfodregion || r->r_incommregion)
+ return WALK_CONTINUE;
+
+ const dyld_process_info dpi = arg;
+
+ __block walk_return_t retval = WALK_CONTINUE;
+ __block subregionlisthead_t srlhead = STAILQ_HEAD_INITIALIZER(srlhead);
+
+ _dyld_process_info_for_each_image(dpi, ^(uint64_t __unused mhaddr, const uuid_t uuid, __unused const char *path) {
+ if (WALK_CONTINUE == retval) {
+ const struct libent *le = libent_lookup_byuuid(uuid);
+ assert(le->le_mhaddr == mhaddr);
+ bool shouldskip = false;
+ if (V_SIZE(&le->le_vr))
+ shouldskip = (R_ENDADDR(r) < V_ADDR(&le->le_vr) ||
+ R_ADDR(r) > V_ENDADDR(&le->le_vr));
+ if (!shouldskip)
+ retval = add_subregions_for_libent(&srlhead, r, le->le_mh, le->le_mhaddr, le);
+ }
+ });
+ if (WALK_CONTINUE != retval)
+ goto done;
+
+ /*
+ * Take the unsorted list of subregions, if any,
+ * and hang a sorted array of ranges on the region structure.
+ */
+ if (!STAILQ_EMPTY(&srlhead)) {
+ struct subregionlist *srl;
+ STAILQ_FOREACH(srl, &srlhead, srl_linkage) {
+ r->r_nsubregions++;
+ }
+ assert(r->r_nsubregions);
+
+ r->r_subregions = calloc(r->r_nsubregions, sizeof (void *));
+ unsigned i = 0;
+ STAILQ_FOREACH(srl, &srlhead, srl_linkage) {
+ r->r_subregions[i++] = srl->srl_s;
+ }
+ qsort_b(r->r_subregions, r->r_nsubregions, sizeof (void *),
+ ^(const void *a, const void *b) {
+ const struct subregion *lhs = *(struct subregion **)a;
+ const struct subregion *rhs = *(struct subregion **)b;
+ if (S_ADDR(lhs) > S_ADDR(rhs))
+ return 1;
+ if (S_ADDR(lhs) < S_ADDR(rhs))
+ return -1;
+ return 0;
+ });
+
+ eliminate_duplicate_subregions(r);
+
+ if (r->r_info.external_pager) {
+ /*
+ * Only very specific segment types get to be filerefs
+ */
+ for (i = 0; i < r->r_nsubregions; i++) {
+ struct subregion *s = r->r_subregions[i];
+ /*
+ * Anything marked writable is trivially disqualified; we're
+ * going to copy it anyway.
+ */
+ if (s->s_segcmd.initprot & VM_PROT_WRITE)
+ continue;
+
+ /* __TEXT and __LINKEDIT are our real targets */
+ if (!issubregiontype(s, SEG_TEXT) && !issubregiontype(s, SEG_LINKEDIT) && !issubregiontype(s, "__UNICODE")) {
+ if (OPTIONS_DEBUG(opt, 3)) {
+ hsize_str_t hstr;
+ printvr(S_RANGE(s), "skipping read-only %s segment %s\n", S_MACHO_TYPE(s), str_hsize(hstr, S_SIZE(s)));
+ }
+ continue;
+ }
+ if (r->r_insharedregion) {
+ /*
+ * Part of the shared region: things get more complicated.
+ */
+ if (r->r_fileref) {
+ /*
+ * There's a file reference here for the whole region.
+ * For __TEXT subregions, we could, in principle (though
+ * see below) generate references to the individual
+ * dylibs that dyld reports in the region. If the
+ * debugger could then use the __LINKEDIT info in the
+ * file, then we'd be done. But as long as the dump
+ * includes __LINKEDIT sections, we're going to
+ * end up generating a file reference to the combined
+ * __LINKEDIT section in the shared cache anyway, so
+ * we might as well do that for the __TEXT regions as
+ * well.
+ */
+ s->s_libent = r->r_fileref->fr_libent;
+ s->s_isuuidref = true;
+ } else {
+ /*
+ * If we get here, it's likely that the shared cache
+ * name can't be found e.g. update_dyld_shared_cache(1).
+ * For __TEXT subregions, we could generate refs to
+ * the individual dylibs, but note that the mach header
+ * and segment commands in memory are still pointing
+ * into the shared cache so any act of reconstruction
+ * is fiendishly complex. So copy it.
+ */
+ assert(!s->s_isuuidref);
+ }
+ } else {
+ /* Just a regular dylib? */
+ if (s->s_libent)
+ s->s_isuuidref = true;
+ }
+ }
+ }
+ }
+ assert(WALK_CONTINUE == retval);
+
+done:
+ if (!STAILQ_EMPTY(&srlhead)) {
+ struct subregionlist *srl, *trl;
+ STAILQ_FOREACH_SAFE(srl, &srlhead, srl_linkage, trl) {
+ free(srl);
+ }
+ }
+ return retval;
+}
+
+/*
+ * Strip region of all decoration
+ *
+ * Invoked (on every region!) after an error during the initial
+ * 'decoration' phase to discard potentially incomplete information.
+ */
+walk_return_t
+undecorate_memory_region(struct region *r, __unused void *arg)
+{
+ assert(&sparse_ops != r->r_op);
+ return r->r_nsubregions ? clean_subregions(r) : WALK_CONTINUE;
+}
+
+/*
+ * This optimization occurs -after- the vanilla_region_optimizations(),
+ * and -after- we've tagged zfod and first-pass fileref's.
+ */
+walk_return_t
+sparse_region_optimization(struct region *r, __unused void *arg)
+{
+ assert(&sparse_ops != r->r_op);
+
+ if (r->r_inzfodregion) {
+ /*
+ * Pure zfod region: almost certainly a more compact
+ * representation - keep it that way.
+ */
+ if (OPTIONS_DEBUG(opt, 3))
+ printr(r, "retaining zfod region\n");
+ assert(&zfod_ops == r->r_op);
+ return clean_subregions(r);
+ }
+
+ if (r->r_insharedregion && 0 == r->r_nsubregions) {
+ /*
+ * A segment in the shared region needs to be
+ * identified with an LC_SEGMENT that dyld claims,
+ * otherwise (we assert) it's not useful to the dump.
+ */
+ if (OPTIONS_DEBUG(opt, 2)) {
+ hsize_str_t hstr;
+ printr(r, "not referenced in dyld info => "
+ "eliding %s range in shared region\n",
+ str_hsize(hstr, R_SIZE(r)));
+ }
+ if (0 == r->r_info.pages_dirtied && 0 == r->r_info.pages_swapped_out)
+ return WALK_DELETE_REGION;
+ if (OPTIONS_DEBUG(opt, 2)) {
+ hsize_str_t hstr;
+ printr(r, "dirty pages, but not referenced in dyld info => "
+ "NOT eliding %s range in shared region\n",
+ str_hsize(hstr, R_SIZE(r)));
+ }
+ }
+
+ if (r->r_fileref) {
+ /*
+ * Already have a fileref for the whole region: already
+ * a more compact representation - keep it that way.
+ */
+ if (OPTIONS_DEBUG(opt, 3))
+ printr(r, "retaining fileref region\n");
+ assert(&fileref_ops == r->r_op);
+ return clean_subregions(r);
+ }
+
+ if (r->r_nsubregions > 1) {
+ /*
+ * Merge adjacent or identical subregions that have no file reference
+ * (Reducing the number of subregions reduces header overhead and
+ * improves compressability)
+ */
+ unsigned i = 1;
+ while (i < r->r_nsubregions) {
+ struct subregion *s0 = r->r_subregions[i-1];
+ struct subregion *s1 = r->r_subregions[i];
+
+ if (s0->s_isuuidref) {
+ i++;
+ continue; /* => destined to be a fileref */
+ }
+ if (!issamesubregiontype(s0, s1)) {
+ i++;
+ continue; /* merge-able subregions must have same "type" */
+ }
+
+ if (S_ENDADDR(s0) == S_ADDR(s1)) {
+ /* directly adjacent subregions */
+ if (OPTIONS_DEBUG(opt, 2))
+ printr(r, "merging subregions (%llx-%llx + %llx-%llx) -- adjacent\n",
+ S_ADDR(s0), S_ENDADDR(s0), S_ADDR(s1), S_ENDADDR(s1));
+ S_SETSIZE(s0, S_ENDADDR(s1) - S_ADDR(s0));
+ elide_subregion(r, i);
+ continue;
+ }
+
+ const mach_vm_size_t pfn[2] = {
+ S_ADDR(s0) >> pageshift_host,
+ S_ADDR(s1) >> pageshift_host
+ };
+ const mach_vm_size_t endpfn[2] = {
+ (S_ENDADDR(s0) - 1) >> pageshift_host,
+ (S_ENDADDR(s1) - 1) >> pageshift_host
+ };
+
+ if (pfn[0] == pfn[1] && pfn[0] == endpfn[0] && pfn[0] == endpfn[1]) {
+ /* two small subregions share a host page */
+ if (OPTIONS_DEBUG(opt, 2))
+ printr(r, "merging subregions (%llx-%llx + %llx-%llx) -- same page\n",
+ S_ADDR(s0), S_ENDADDR(s0), S_ADDR(s1), S_ENDADDR(s1));
+ S_SETSIZE(s0, S_ENDADDR(s1) - S_ADDR(s0));
+ elide_subregion(r, i);
+ continue;
+ }
+
+ if (pfn[1] == 1 + endpfn[0]) {
+ /* subregions are pagewise-adjacent: bigger chunks to compress */
+ if (OPTIONS_DEBUG(opt, 2))
+ printr(r, "merging subregions (%llx-%llx + %llx-%llx) -- adjacent pages\n",
+ S_ADDR(s0), S_ENDADDR(s0), S_ADDR(s1), S_ENDADDR(s1));
+ S_SETSIZE(s0, S_ENDADDR(s1) - S_ADDR(s0));
+ elide_subregion(r, i);
+ continue;
+ }
+
+ i++; /* this isn't the subregion we're looking for */
+ }
+ }
+
+ if (1 == r->r_nsubregions) {
+ struct subregion *s = r->r_subregions[0];
+ if (!s->s_isuuidref &&
+ R_ADDR(r) == S_ADDR(s) && R_ENDADDR(r) == S_ENDADDR(s)) {
+ if (OPTIONS_DEBUG(opt, 3))
+ printr(r, "subregion (%llx-%llx) reverts to region\n",
+ S_ADDR(s), S_ENDADDR(s));
+ return clean_subregions(r);
+ }
+ }
+
+ if (r->r_nsubregions)
+ r->r_op = &sparse_ops;
+
+ return WALK_CONTINUE;
+}
diff --git a/system_cmds/gcore.tproj/sparse.h b/system_cmds/gcore.tproj/sparse.h
new file mode 100644
index 0000000..cf920ff
--- /dev/null
+++ b/system_cmds/gcore.tproj/sparse.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "region.h"
+#include "dyld.h"
+
+#ifndef _SPARSE_H
+#define _SPARSE_H
+
+struct subregion {
+ struct vm_range s_range;
+ native_segment_command_t s_segcmd;
+ const struct libent *s_libent;
+ bool s_isuuidref;
+};
+
+#define S_RANGE(s) (&(s)->s_range)
+
+static __inline void S_SETADDR(struct subregion *s, mach_vm_offset_t a) {
+ V_SETADDR(S_RANGE(s), a);
+}
+
+static __inline void S_SETSIZE(struct subregion *s, mach_vm_offset_t sz) {
+ V_SETSIZE(S_RANGE(s), sz);
+}
+
+static __inline const mach_vm_offset_t S_ADDR(const struct subregion *s) {
+ return V_ADDR(S_RANGE(s));
+}
+
+static __inline const mach_vm_offset_t S_SIZE(const struct subregion *s) {
+ return V_SIZE(S_RANGE(s));
+}
+
+static __inline const mach_vm_offset_t S_ENDADDR(const struct subregion *s) {
+ return S_ADDR(s) + S_SIZE(s);
+}
+
+static __inline const char *S_MACHO_TYPE(const struct subregion *s) {
+ return s->s_segcmd.segname;
+}
+
+static __inline off_t S_MACHO_FILEOFF(const struct subregion *s) {
+ return s->s_segcmd.fileoff;
+}
+
+static __inline off_t S_MACHO_FILESIZE(const struct subregion *s) {
+ return s->s_segcmd.filesize;
+}
+
+static __inline const struct libent *S_LIBENT(const struct subregion *s) {
+ return s->s_libent;
+}
+
+static __inline const char *S_PATHNAME(const struct subregion *s) {
+ const struct libent *le = S_LIBENT(s);
+ return le ? le->le_pathname : "(unknown)";
+}
+
+static __inline const char *S_FILENAME(const struct subregion *s) {
+ const struct libent *le = S_LIBENT(s);
+ return le ? le->le_filename : S_PATHNAME(s);
+}
+
+extern bool issubregiontype(const struct subregion *, const char *);
+
+extern walk_region_cbfn_t decorate_memory_region;
+extern walk_region_cbfn_t undecorate_memory_region;
+extern walk_region_cbfn_t sparse_region_optimization;
+
+#endif /* _SPARSE_H */
diff --git a/system_cmds/gcore.tproj/threads.c b/system_cmds/gcore.tproj/threads.c
new file mode 100644
index 0000000..b1b3d6f
--- /dev/null
+++ b/system_cmds/gcore.tproj/threads.c
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2015 Apple Inc. All rights reserved.
+ */
+
+#include "options.h"
+#include "utils.h"
+#include "threads.h"
+#include "corefile.h"
+
+#include <sys/types.h>
+#include <mach/mach.h>
+#include <mach/task.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stddef.h>
+
+typedef struct {
+ int flavor;
+ mach_msg_type_number_t count;
+} threadflavor_t;
+
+static threadflavor_t thread_flavor[] = {
+#if defined(__i386__) || defined(__x86_64__)
+ { x86_THREAD_STATE, x86_THREAD_STATE_COUNT },
+ { x86_FLOAT_STATE, x86_FLOAT_STATE_COUNT },
+ { x86_EXCEPTION_STATE, x86_EXCEPTION_STATE_COUNT },
+#elif defined(__arm__)
+ { ARM_THREAD_STATE, ARM_THREAD_STATE_COUNT },
+ { ARM_VFP_STATE, ARM_VFP_STATE_COUNT },
+ { ARM_EXCEPTION_STATE, ARM_EXCEPTION_STATE_COUNT },
+#elif defined(__arm64__)
+ { ARM_THREAD_STATE64, ARM_THREAD_STATE64_COUNT },
+ /* ARM64_TODO: NEON? */
+ { ARM_EXCEPTION_STATE64, ARM_EXCEPTION_STATE64_COUNT },
+#else
+#error architecture not supported
+#endif
+};
+
+static const int nthread_flavors = sizeof (thread_flavor) / sizeof (thread_flavor[0]);
+
+size_t
+sizeof_LC_THREAD()
+{
+ size_t cmdsize = sizeof (struct thread_command);
+ for (int i = 0; i < nthread_flavors; i++) {
+ cmdsize += sizeof (thread_flavor[i]) +
+ thread_flavor[i].count * sizeof (int);
+ }
+ return cmdsize;
+}
+
+void
+dump_thread_state(native_mach_header_t *mh, struct thread_command *tc, mach_port_t thread)
+{
+ tc->cmd = LC_THREAD;
+ tc->cmdsize = (uint32_t) sizeof_LC_THREAD();
+
+ uint32_t *wbuf = (void *)(tc + 1);
+
+ for (int f = 0; f < nthread_flavors; f++) {
+
+ memcpy(wbuf, &thread_flavor[f], sizeof (thread_flavor[f]));
+ wbuf += sizeof (thread_flavor[f]) / sizeof (*wbuf);
+
+ const kern_return_t kr = thread_get_state(thread, thread_flavor[f].flavor, (thread_state_t)wbuf, &thread_flavor[f].count);
+ if (KERN_SUCCESS != kr) {
+ err_mach(kr, NULL, "getting flavor %d of thread",
+ thread_flavor[f].flavor);
+ bzero(wbuf, thread_flavor[f].count * sizeof (int));
+ }
+
+ wbuf += thread_flavor[f].count;
+ }
+ assert((ptrdiff_t)tc->cmdsize == ((caddr_t)wbuf - (caddr_t)tc));
+
+ mach_header_inc_ncmds(mh, 1);
+ mach_header_inc_sizeofcmds(mh, tc->cmdsize);
+}
diff --git a/system_cmds/gcore.tproj/threads.h b/system_cmds/gcore.tproj/threads.h
new file mode 100644
index 0000000..c5605ce
--- /dev/null
+++ b/system_cmds/gcore.tproj/threads.h
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2015 Apple Inc. All rights reserved.
+ */
+
+#include "corefile.h"
+#include <mach/task.h>
+
+#ifndef _THREADS_H
+#define _THREADS_H
+
+extern size_t sizeof_LC_THREAD(void);
+extern void dump_thread_state(native_mach_header_t *, struct thread_command *, mach_port_t);
+
+#endif /* _THREADS_H */
diff --git a/system_cmds/gcore.tproj/utils.c b/system_cmds/gcore.tproj/utils.c
new file mode 100644
index 0000000..f0edcf8
--- /dev/null
+++ b/system_cmds/gcore.tproj/utils.c
@@ -0,0 +1,421 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "options.h"
+#include "utils.h"
+#include "region.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <libutil.h>
+#include <errno.h>
+
+void
+err_mach(kern_return_t kr, const struct region *r, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ if (0 != kr)
+ printf("%s: ", pgm);
+ if (NULL != r)
+ printf("%016llx-%016llx ", R_ADDR(r), R_ENDADDR(r));
+ vprintf(fmt, ap);
+ va_end(ap);
+
+ if (0 != kr) {
+ printf(": failed: %s (0x%x)", mach_error_string(kr), kr);
+ switch (err_get_system(kr)) {
+ case err_get_system(err_mach_ipc):
+ /* 0x10000000 == (4 << 26) */
+ printf(" => fatal\n");
+ exit(127);
+ default:
+ putchar('\n');
+ break;
+ }
+ } else
+ putchar('\n');
+}
+
+static void
+vprintvr(const struct vm_range *vr, const char *restrict fmt, va_list ap)
+{
+ if (NULL != vr)
+ printf("%016llx-%016llx ", V_ADDR(vr), V_ENDADDR(vr));
+ vprintf(fmt, ap);
+}
+
+void
+printvr(const struct vm_range *vr, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vprintvr(vr, fmt, ap);
+ va_end(ap);
+}
+
+void
+printr(const struct region *r, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vprintvr(R_RANGE(r), fmt, ap);
+ va_end(ap);
+}
+
+/*
+ * Print power-of-1024 sizes in human-readable form
+ */
+const char *
+str_hsize(hsize_str_t hstr, uint64_t size)
+{
+ humanize_number(hstr, sizeof (hsize_str_t) - 1, size, "",
+ HN_AUTOSCALE, HN_B | HN_NOSPACE | HN_DECIMAL | HN_IEC_PREFIXES);
+ return hstr;
+}
+
+/*
+ * Print VM protections in human-readable form
+ */
+const char *
+str_prot(const vm_prot_t prot)
+{
+ static const char *pstr[] = {
+ [0] = "---",
+ [VM_PROT_READ] = "r--",
+ [VM_PROT_WRITE] = "-w-",
+ [VM_PROT_READ|VM_PROT_WRITE] = "rw-",
+ [VM_PROT_EXECUTE] = "--x",
+ [VM_PROT_READ|VM_PROT_EXECUTE] = "r-x",
+ [VM_PROT_WRITE|VM_PROT_EXECUTE] = "-wx",
+ [VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE] = "rwx"
+ };
+ return pstr[prot & 7];
+}
+
+// c.f. VMUVMRegion.m
+
+const char *
+str_shared(int sm)
+{
+ static const char *sstr[] = {
+ [0] = " ",
+ [SM_COW] = "sm=cow",
+ [SM_PRIVATE] = "sm=prv",
+ [SM_EMPTY] = "sm=nul",
+ [SM_SHARED] = "sm=ali",
+ [SM_TRUESHARED] = "sm=shm",
+ [SM_PRIVATE_ALIASED] = "sm=zer",
+ [SM_SHARED_ALIASED] = "sm=s/a",
+ [SM_LARGE_PAGE] = "sm=lpg",
+ };
+ if ((unsigned)sm < sizeof (sstr) / sizeof (sstr[0]))
+ return sstr[sm];
+ return "sm=???";
+}
+
+const char *
+str_purgable(int pu, int sm)
+{
+ if (SM_EMPTY == sm)
+ return " ";
+ static const char *pstr[] = {
+ [VM_PURGABLE_NONVOLATILE] = "p=n",
+ [VM_PURGABLE_VOLATILE] = "p=v",
+ [VM_PURGABLE_EMPTY] = "p=e",
+ [VM_PURGABLE_DENY] = " ",
+ };
+ if ((unsigned)pu < sizeof (pstr) / sizeof (pstr[0]))
+ return pstr[pu];
+ return "p=?";
+}
+
+/*
+ * c.f. VMURegionTypeDescriptionForTagShareProtAndPager.
+ */
+const char *
+str_tag(tag_str_t tstr, int tag, int share_mode, vm_prot_t curprot, int external_pager)
+{
+ const char *rtype;
+
+ switch (tag) {
+ case 0:
+ if (external_pager)
+ rtype = "mapped file";
+ else if (SM_TRUESHARED == share_mode)
+ rtype = "shared memory";
+ else
+ rtype = "VM_allocate";
+ break;
+ case VM_MEMORY_MALLOC:
+ if (VM_PROT_NONE == curprot)
+ rtype = "MALLOC guard page";
+ else if (SM_EMPTY == share_mode)
+ rtype = "MALLOC";
+ else
+ rtype = "MALLOC metadata";
+ break;
+ case VM_MEMORY_STACK:
+ if (VM_PROT_NONE == curprot)
+ rtype = "Stack guard";
+ else
+ rtype = "Stack";
+ break;
+#if defined(CONFIG_DEBUG) || defined(CONFIG_GCORE_MAP)
+ case VM_MEMORY_MALLOC_SMALL:
+ rtype = "MALLOC_SMALL";
+ break;
+ case VM_MEMORY_MALLOC_LARGE:
+ rtype = "MALLOC_LARGE";
+ break;
+ case VM_MEMORY_MALLOC_HUGE:
+ rtype = "MALLOC_HUGE";
+ break;
+ case VM_MEMORY_SBRK:
+ rtype = "SBRK";
+ break;
+ case VM_MEMORY_REALLOC:
+ rtype = "MALLOC_REALLOC";
+ break;
+ case VM_MEMORY_MALLOC_TINY:
+ rtype = "MALLOC_TINY";
+ break;
+ case VM_MEMORY_MALLOC_LARGE_REUSABLE:
+ rtype = "MALLOC_LARGE_REUSABLE";
+ break;
+ case VM_MEMORY_MALLOC_LARGE_REUSED:
+ rtype = "MALLOC_LARGE";
+ break;
+ case VM_MEMORY_ANALYSIS_TOOL:
+ rtype = "Performance tool data";
+ break;
+ case VM_MEMORY_MALLOC_NANO:
+ rtype = "MALLOC_NANO";
+ break;
+ case VM_MEMORY_MACH_MSG:
+ rtype = "Mach message";
+ break;
+ case VM_MEMORY_IOKIT:
+ rtype = "IOKit";
+ break;
+ case VM_MEMORY_GUARD:
+ rtype = "Guard";
+ break;
+ case VM_MEMORY_SHARED_PMAP:
+ rtype = "shared pmap";
+ break;
+ case VM_MEMORY_DYLIB:
+ rtype = "dylib";
+ break;
+ case VM_MEMORY_OBJC_DISPATCHERS:
+ rtype = "ObjC dispatching code";
+ break;
+ case VM_MEMORY_UNSHARED_PMAP:
+ rtype = "unshared pmap";
+ break;
+ case VM_MEMORY_APPKIT:
+ rtype = "AppKit";
+ break;
+ case VM_MEMORY_FOUNDATION:
+ rtype = "Foundation";
+ break;
+ case VM_MEMORY_COREGRAPHICS:
+ rtype = "CoreGraphics";
+ break;
+ case VM_MEMORY_CORESERVICES:
+ rtype = "CoreServices";
+ break;
+ case VM_MEMORY_JAVA:
+ rtype = "Java";
+ break;
+ case VM_MEMORY_COREDATA:
+ rtype = "CoreData";
+ break;
+ case VM_MEMORY_COREDATA_OBJECTIDS:
+ rtype = "CoreData Object IDs";
+ break;
+ case VM_MEMORY_ATS:
+ rtype = "ATS (font support)";
+ break;
+ case VM_MEMORY_LAYERKIT:
+ rtype = "CoreAnimation";
+ break;
+ case VM_MEMORY_CGIMAGE:
+ rtype = "CG image";
+ break;
+ case VM_MEMORY_TCMALLOC:
+ rtype = "WebKit Malloc";
+ break;
+ case VM_MEMORY_COREGRAPHICS_DATA:
+ rtype = "CG raster data";
+ break;
+ case VM_MEMORY_COREGRAPHICS_SHARED:
+ rtype = "CG shared images";
+ break;
+ case VM_MEMORY_COREGRAPHICS_FRAMEBUFFERS:
+ rtype = "CG framebuffers";
+ break;
+ case VM_MEMORY_COREGRAPHICS_BACKINGSTORES:
+ rtype = "CG backingstores";
+ break;
+ case VM_MEMORY_DYLD:
+ rtype = "dyld private memory";
+ break;
+ case VM_MEMORY_DYLD_MALLOC:
+ rtype = "dyld malloc memory";
+ break;
+ case VM_MEMORY_SQLITE:
+ rtype = "SQlite page cache";
+ break;
+ case VM_MEMORY_JAVASCRIPT_CORE:
+ rtype = "WebAssembly memory";
+ break;
+ case VM_MEMORY_JAVASCRIPT_JIT_EXECUTABLE_ALLOCATOR:
+ rtype = "JS JIT generated code";
+ break;
+ case VM_MEMORY_JAVASCRIPT_JIT_REGISTER_FILE:
+ rtype = "JS VM register file";
+ break;
+ case VM_MEMORY_GLSL:
+ rtype = "OpenGL GLSL";
+ break;
+ case VM_MEMORY_OPENCL:
+ rtype = "OpenCL";
+ break;
+ case VM_MEMORY_COREIMAGE:
+ rtype = "CoreImage";
+ break;
+ case VM_MEMORY_WEBCORE_PURGEABLE_BUFFERS:
+ rtype = "WebCore purgable data";
+ break;
+ case VM_MEMORY_IMAGEIO:
+ rtype = "Image IO";
+ break;
+ case VM_MEMORY_COREPROFILE:
+ rtype = "CoreProfile";
+ break;
+ case VM_MEMORY_ASSETSD:
+ rtype = "Assets Library";
+ break;
+ case VM_MEMORY_OS_ALLOC_ONCE:
+ rtype = "OS Alloc Once";
+ break;
+ case VM_MEMORY_LIBDISPATCH:
+ rtype = "Dispatch continuations";
+ break;
+ case VM_MEMORY_ACCELERATE:
+ rtype = "Accelerate framework";
+ break;
+ case VM_MEMORY_COREUI:
+ rtype = "CoreUI image data";
+ break;
+ case VM_MEMORY_COREUIFILE:
+ rtype = "CoreUI image file";
+ break;
+ case VM_MEMORY_GENEALOGY:
+ rtype = "Activity Tracing";
+ break;
+ case VM_MEMORY_RAWCAMERA:
+ rtype = "RawCamera";
+ break;
+ case VM_MEMORY_CORPSEINFO:
+ rtype = "Process Corpse Info";
+ break;
+ case VM_MEMORY_ASL:
+ rtype = "Apple System Log";
+ break;
+ case VM_MEMORY_SWIFT_RUNTIME:
+ rtype = "Swift runtime";
+ break;
+ case VM_MEMORY_SWIFT_METADATA:
+ rtype = "Swift metadata";
+ break;
+ case VM_MEMORY_DHMM:
+ rtype = "DHMM";
+ break;
+ case VM_MEMORY_SCENEKIT:
+ rtype = "SceneKit";
+ break;
+ case VM_MEMORY_SKYWALK:
+ rtype = "Skywalk Networking";
+ break;
+#endif
+ default:
+ rtype = NULL;
+ break;
+ }
+ if (rtype)
+ snprintf(tstr, sizeof (tag_str_t), "%s", rtype);
+ else
+ snprintf(tstr, sizeof (tag_str_t), "tag #%d", tag);
+ return tstr;
+}
+
+const char *
+str_tagr(tag_str_t tstr, const struct region *r) {
+ return str_tag(tstr, r->r_info.user_tag, r->r_info.share_mode, r->r_info.protection, r->r_info.external_pager);
+}
+
+/*
+ * Put two strings together separated by a '+' sign
+ * If the string gets too long, then add an ellipsis and
+ * stop concatenating it.
+ */
+char *
+strconcat(const char *s0, const char *s1, size_t maxlen)
+{
+ const char ellipsis[] = "...";
+ const char junction[] = ", ";
+ const size_t s0len = strlen(s0);
+ size_t nmlen = s0len + strlen(s1) + strlen(junction) + 1;
+ if (maxlen > strlen(ellipsis) && nmlen > maxlen) {
+ if (strcmp(s0 + s0len - strlen(ellipsis), ellipsis) == 0)
+ return strdup(s0);
+ s1 = ellipsis;
+ nmlen = s0len + strlen(s1) + strlen(junction) + 1;
+ }
+ char *p = malloc(nmlen);
+ if (p) {
+ strlcpy(p, s0, nmlen);
+ strlcat(p, junction, nmlen);
+ strlcat(p, s1, nmlen);
+ }
+ return p;
+}
+
+unsigned long
+simple_namehash(const char *nm)
+{
+ unsigned long result = 5381;
+ int c;
+ while (0 != (c = *nm++))
+ result = (result * 33) ^ c;
+ return result; /* modified djb2 */
+}
+
+int
+bounded_pwrite(int fd, const void *addr, size_t size, off_t off, bool *nocache, ssize_t *nwrittenp)
+{
+ if (opt->sizebound && off + (off_t)size > opt->sizebound)
+ return EFBIG;
+
+ bool oldnocache = *nocache;
+ if (size >= opt->ncthresh && !oldnocache)
+ *nocache = 0 == fcntl(fd, F_NOCACHE, 1);
+ else if (size < opt->ncthresh && oldnocache)
+ *nocache = 0 != fcntl(fd, F_NOCACHE, 0);
+ if (OPTIONS_DEBUG(opt, 3) && oldnocache ^ *nocache)
+ printf("F_NOCACHE now %sabled on fd %d\n", *nocache ? "en" : "dis", fd);
+
+ const ssize_t nwritten = pwrite(fd, addr, size, off);
+ if (-1 == nwritten)
+ return errno;
+ if (nwrittenp)
+ *nwrittenp = nwritten;
+ return 0;
+}
diff --git a/system_cmds/gcore.tproj/utils.h b/system_cmds/gcore.tproj/utils.h
new file mode 100644
index 0000000..37eda58
--- /dev/null
+++ b/system_cmds/gcore.tproj/utils.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <uuid/uuid.h>
+#include <mach/mach_types.h>
+#include <sysexits.h>
+#include <err.h>
+#include <fcntl.h>
+
+#ifndef _UTILS_H
+#define _UTILS_H
+
+extern const char *pgm;
+
+struct vm_range;
+struct region;
+
+extern void err_mach(kern_return_t, const struct region *, const char *, ...) __printflike(3, 4);
+extern void printvr(const struct vm_range *, const char *, ...) __printflike(2, 3);
+extern void printr(const struct region *, const char *, ...) __printflike(2, 3);
+
+typedef char hsize_str_t[7]; /* e.g. 1008Mib */
+
+extern const char *str_hsize(hsize_str_t hstr, uint64_t);
+extern const char *str_prot(vm_prot_t);
+extern const char *str_shared(int);
+extern const char *str_purgable(int, int);
+
+typedef char tag_str_t[24];
+
+extern const char *str_tag(tag_str_t, int, int, vm_prot_t, int);
+extern const char *str_tagr(tag_str_t, const struct region *);
+
+extern char *strconcat(const char *, const char *, size_t);
+extern unsigned long simple_namehash(const char *);
+extern int bounded_pwrite(int, const void *, size_t, off_t, bool *, ssize_t *);
+
+#endif /* _UTILS_H */
diff --git a/system_cmds/gcore.tproj/vanilla.c b/system_cmds/gcore.tproj/vanilla.c
new file mode 100644
index 0000000..2253bff
--- /dev/null
+++ b/system_cmds/gcore.tproj/vanilla.c
@@ -0,0 +1,911 @@
+/*
+ * 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;
+}
diff --git a/system_cmds/gcore.tproj/vanilla.h b/system_cmds/gcore.tproj/vanilla.h
new file mode 100644
index 0000000..721c653
--- /dev/null
+++ b/system_cmds/gcore.tproj/vanilla.h
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "vm.h"
+
+#ifndef _VANILLA_H
+#define _VANILLA_H
+
+struct proc_bsdinfo;
+
+extern void validate_core_header(const native_mach_header_t *, off_t);
+extern int coredump(task_t, int, const struct proc_bsdinfo *);
+extern int coredump_write(task_t, int, struct regionhead *, const uuid_t, mach_vm_offset_t, mach_vm_offset_t);
+extern struct regionhead *coredump_prepare(task_t, uuid_t);
+
+#endif /* _VANILLA_H */
diff --git a/system_cmds/gcore.tproj/vm.c b/system_cmds/gcore.tproj/vm.c
new file mode 100644
index 0000000..22b0efe
--- /dev/null
+++ b/system_cmds/gcore.tproj/vm.c
@@ -0,0 +1,493 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include "options.h"
+#include "vm.h"
+#include "utils.h"
+#include "region.h"
+#include "sparse.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <sys/queue.h>
+
+/*
+ * There should be better APIs to describe the shared region
+ * For now, some hackery.
+ */
+
+#include <mach/shared_region.h>
+
+static __inline boolean_t
+in_shared_region(mach_vm_address_t addr)
+{
+ const mach_vm_address_t base = SHARED_REGION_BASE;
+ const mach_vm_address_t size = SHARED_REGION_SIZE;
+ return addr >= base && addr < (base + size);
+}
+
+/*
+ * On both x64 and arm, there's a globallly-shared
+ * read-only page at _COMM_PAGE_START_ADDRESS
+ * which low-level library routines reference.
+ *
+ * On x64, somewhere randomly chosen between _COMM_PAGE_TEXT_ADDRESS
+ * and the top of the user address space, there's the
+ * pre-emption-free-zone read-execute page.
+ */
+
+#include <System/machine/cpu_capabilities.h>
+
+static __inline boolean_t
+in_comm_region(const mach_vm_address_t addr, const vm_region_submap_info_data_64_t *info)
+{
+ return addr >= _COMM_PAGE_START_ADDRESS &&
+ SM_TRUESHARED == info->share_mode &&
+ VM_INHERIT_SHARE == info->inheritance &&
+ !info->external_pager && (info->max_protection & VM_PROT_WRITE) == 0;
+}
+
+static __inline boolean_t
+in_zfod_region(const vm_region_submap_info_data_64_t *info)
+{
+ return info->share_mode == SM_EMPTY && !info->is_submap &&
+ 0 == info->object_id && !info->external_pager &&
+ 0 == info->pages_dirtied + info->pages_resident + info->pages_swapped_out;
+}
+
+static struct region *
+new_region(mach_vm_offset_t vmaddr, mach_vm_size_t vmsize, const vm_region_submap_info_data_64_t *infop)
+{
+ struct region *r = calloc(1, sizeof (*r));
+ assert(vmaddr != 0 && vmsize != 0);
+ R_SETADDR(r, vmaddr);
+ R_SETSIZE(r, vmsize);
+ r->r_info = *infop;
+ r->r_purgable = VM_PURGABLE_DENY;
+ r->r_insharedregion = in_shared_region(vmaddr);
+ r->r_incommregion = in_comm_region(vmaddr, &r->r_info);
+ r->r_inzfodregion = in_zfod_region(&r->r_info);
+
+ if (r->r_inzfodregion)
+ r->r_op = &zfod_ops;
+ else
+ r->r_op = &vanilla_ops;
+ return r;
+}
+
+void
+del_fileref_region(struct region *r)
+{
+ assert(&fileref_ops == r->r_op);
+ /* r->r_fileref->fr_libent is a reference into the name table */
+ poison(r->r_fileref, 0xdeadbee9, sizeof (*r->r_fileref));
+ free(r->r_fileref);
+ poison(r, 0xdeadbeeb, sizeof (*r));
+ free(r);
+}
+
+void
+del_zfod_region(struct region *r)
+{
+ assert(&zfod_ops == r->r_op);
+ assert(r->r_inzfodregion && 0 == r->r_nsubregions);
+ assert(NULL == r->r_fileref);
+ poison(r, 0xdeadbeed, sizeof (*r));
+ free(r);
+}
+
+void
+del_vanilla_region(struct region *r)
+{
+ assert(&vanilla_ops == r->r_op);
+ assert(!r->r_inzfodregion && 0 == r->r_nsubregions);
+ assert(NULL == r->r_fileref);
+ poison(r, 0xdeadbeef, sizeof (*r));
+ free(r);
+}
+
+/*
+ * "does any part of this address range match the tag?"
+ */
+int
+is_tagged(task_t task, mach_vm_offset_t addr, mach_vm_offset_t size, unsigned tag)
+{
+ mach_vm_offset_t vm_addr = addr;
+ mach_vm_offset_t vm_size = 0;
+ natural_t depth = 0;
+ size_t pgsize = (1u << pageshift_host);
+
+ do {
+ mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
+ vm_region_submap_info_data_64_t info;
+
+ kern_return_t ret = mach_vm_region_recurse(task, &vm_addr, &vm_size, &depth, (vm_region_recurse_info_t)&info, &count);
+
+ if (KERN_FAILURE == ret) {
+ err_mach(ret, NULL, "error inspecting task at %llx", vm_addr);
+ return -1;
+ } else if (KERN_INVALID_ADDRESS == ret) {
+ err_mach(ret, NULL, "invalid address at %llx", vm_addr);
+ return -1;
+ } else if (KERN_SUCCESS != ret) {
+ err_mach(ret, NULL, "error inspecting task at %llx", vm_addr);
+ return -1;
+ }
+ if (info.is_submap) {
+ depth++;
+ continue;
+ }
+ if (info.user_tag == tag)
+ return 1;
+ if (vm_addr + vm_size > addr + size)
+ return 0;
+ vm_addr += pgsize;
+ } while (1);
+}
+
+STAILQ_HEAD(regionhead, region);
+
+/*
+ * XXX Need something like mach_vm_shared_region_recurse()
+ * to properly identify the shared region address ranges as
+ * we go.
+ */
+
+static int
+walk_regions(task_t task, struct regionhead *rhead)
+{
+ mach_vm_offset_t vm_addr = MACH_VM_MIN_ADDRESS;
+ natural_t depth = 0;
+
+ if (OPTIONS_DEBUG(opt, 3)) {
+ printf("Building raw region list\n");
+ print_memory_region_header();
+ }
+ while (1) {
+ vm_region_submap_info_data_64_t info;
+ mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;
+ mach_vm_size_t vm_size;
+
+ kern_return_t ret = mach_vm_region_recurse(task, &vm_addr, &vm_size, &depth, (vm_region_recurse_info_t)&info, &count);
+
+ if (KERN_FAILURE == ret) {
+ err_mach(ret, NULL, "error inspecting task at %llx", vm_addr);
+ goto bad;
+ } else if (KERN_INVALID_ADDRESS == ret) {
+ break; /* loop termination */
+ } else if (KERN_SUCCESS != ret) {
+ err_mach(ret, NULL, "error inspecting task at %llx", vm_addr);
+ goto bad;
+ }
+
+ if (OPTIONS_DEBUG(opt, 3)) {
+ struct region *d = new_region(vm_addr, vm_size, &info);
+ ROP_PRINT(d);
+ ROP_DELETE(d);
+ }
+
+ if (info.is_submap) {
+#ifdef CONFIG_SUBMAP
+ /* We also want to see submaps -- for debugging purposes. */
+ struct region *r = new_region(vm_addr, vm_size, &info);
+ r->r_depth = depth;
+ STAILQ_INSERT_TAIL(rhead, r, r_linkage);
+#endif
+ depth++;
+ continue;
+ }
+
+ if (VM_MEMORY_IOKIT == info.user_tag) {
+ vm_addr += vm_size;
+ continue; // ignore immediately: IO memory has side-effects
+ }
+
+ struct region *r = new_region(vm_addr, vm_size, &info);
+#ifdef CONFIG_SUBMAP
+ r->r_depth = depth;
+#endif
+ /* grab the page info of the first page in the mapping */
+
+ mach_msg_type_number_t pageinfoCount = VM_PAGE_INFO_BASIC_COUNT;
+ ret = mach_vm_page_info(task, R_ADDR(r), VM_PAGE_INFO_BASIC, (vm_page_info_t)&r->r_pageinfo, &pageinfoCount);
+ if (KERN_SUCCESS != ret)
+ err_mach(ret, r, "getting pageinfo at %llx", R_ADDR(r));
+
+ /* record the purgability */
+
+ ret = mach_vm_purgable_control(task, vm_addr, VM_PURGABLE_GET_STATE, &r->r_purgable);
+ if (KERN_SUCCESS != ret)
+ r->r_purgable = VM_PURGABLE_DENY;
+
+ STAILQ_INSERT_TAIL(rhead, r, r_linkage);
+
+ vm_addr += vm_size;
+ }
+
+ return 0;
+bad:
+ return EX_OSERR;
+}
+
+void
+del_region_list(struct regionhead *rhead)
+{
+ struct region *r, *t;
+
+ STAILQ_FOREACH_SAFE(r, rhead, r_linkage, t) {
+ STAILQ_REMOVE(rhead, r, region, r_linkage);
+ ROP_DELETE(r);
+ }
+ free(rhead);
+}
+
+struct regionhead *
+build_region_list(task_t task)
+{
+ struct regionhead *rhead = malloc(sizeof (*rhead));
+ STAILQ_INIT(rhead);
+ if (0 != walk_regions(task, rhead)) {
+ del_region_list(rhead);
+ return NULL;
+ }
+ return rhead;
+}
+
+int
+walk_region_list(struct regionhead *rhead, walk_region_cbfn_t cbfn, void *arg)
+{
+ struct region *r, *t;
+
+ STAILQ_FOREACH_SAFE(r, rhead, r_linkage, t) {
+ switch (cbfn(r, arg)) {
+ case WALK_CONTINUE:
+ break;
+ case WALK_DELETE_REGION:
+ STAILQ_REMOVE(rhead, r, region, r_linkage);
+ ROP_DELETE(r);
+ break;
+ case WALK_TERMINATE:
+ goto done;
+ case WALK_ERROR:
+ return -1;
+ }
+ }
+done:
+ return 0;
+}
+
+int pageshift_host;
+int pageshift_app;
+
+void
+setpageshift(void)
+{
+ if (0 == pageshift_host) {
+ vm_size_t hps = 0;
+ kern_return_t ret = host_page_size(MACH_PORT_NULL, &hps);
+ if (KERN_SUCCESS != ret || hps == 0)
+ err_mach(ret, NULL, "host page size");
+ int pshift = 0;
+ while (((vm_offset_t)1 << pshift) != hps)
+ pshift++;
+ pageshift_host = pshift;
+ }
+ if (OPTIONS_DEBUG(opt, 3))
+ printf("host page size: %lu\n", 1ul << pageshift_host);
+
+ if (0 == pageshift_app) {
+ size_t psz = getpagesize();
+ int pshift = 0;
+ while ((1ul << pshift) != psz)
+ pshift++;
+ pageshift_app = pshift;
+ }
+ if (OPTIONS_DEBUG(opt, 3) && pageshift_app != pageshift_host)
+ printf("app page size: %lu\n", 1ul << pageshift_app);
+}
+
+void
+print_memory_region_header(void)
+{
+ printf("%-33s %c %-7s %-7s %8s %16s ",
+ "Address Range", 'S', "Size", "Cur/Max", "Obj32", "FirstPgObjectID");
+ printf("%9s %-3s %-11s %5s ",
+ "Offset", "Tag", "Mode", "Refc");
+#ifdef CONFIG_SUBMAP
+ printf("%5s ", "Depth");
+#endif
+ printf("%5s %5s %5s %3s ",
+ "Res", "SNP", "Dirty", "Pgr");
+ printf("\n");
+}
+
+static __inline char
+region_type(const struct region *r)
+{
+ if (r->r_fileref)
+ return 'f';
+ if (r->r_inzfodregion)
+ return 'z';
+ if (r->r_incommregion)
+ return 'c';
+ if (r->r_insharedregion)
+ return 's';
+ return ' ';
+}
+
+void
+print_memory_region(const struct region *r)
+{
+ hsize_str_t hstr;
+ tag_str_t tstr;
+
+ printf("%016llx-%016llx %c %-7s %s/%s %8x %16llx ",
+ R_ADDR(r), R_ENDADDR(r), region_type(r),
+ str_hsize(hstr, R_SIZE(r)),
+ str_prot(r->r_info.protection),
+ str_prot(r->r_info.max_protection),
+ r->r_info.object_id, r->r_pageinfo.object_id
+ );
+
+ printf("%9lld %3d %-11s %5u ",
+ r->r_info.external_pager ?
+ r->r_pageinfo.offset : r->r_info.offset,
+ r->r_info.user_tag,
+ str_shared(r->r_info.share_mode),
+ r->r_info.ref_count
+ );
+#ifdef CONFIG_SUBMAP
+ printf("%5u ", r->r_depth);
+#endif
+
+ if (!r->r_info.is_submap) {
+ printf("%5u %5u %5u %3s ",
+ r->r_info.pages_resident,
+ r->r_info.pages_shared_now_private,
+ r->r_info.pages_dirtied,
+ r->r_info.external_pager ? "ext" : "");
+ if (r->r_fileref)
+ printf("\n %s at %lld ",
+ r->r_fileref->fr_pathname,
+ r->r_fileref->fr_offset);
+ else
+ printf("%s", str_tagr(tstr, r));
+ printf("\n");
+ if (r->r_nsubregions) {
+ printf(" %-33s %7s %12s\t%s\n",
+ "Address Range", "Size", "Type(s)", "Filename(s)");
+ for (unsigned i = 0; i < r->r_nsubregions; i++) {
+ struct subregion *s = r->r_subregions[i];
+ printf(" %016llx-%016llx %7s %12s\t%s\n",
+ S_ADDR(s), S_ENDADDR(s),
+ str_hsize(hstr, S_SIZE(s)),
+ S_MACHO_TYPE(s),
+ S_FILENAME(s));
+ }
+ }
+ } else {
+ printf("%5s %5s %5s %3s %s\n", "", "", "", "", str_tagr(tstr, r));
+ }
+}
+
+walk_return_t
+region_print_memory(struct region *r, __unused void *arg)
+{
+ ROP_PRINT(r);
+ return WALK_CONTINUE;
+}
+
+void
+print_one_memory_region(const struct region *r)
+{
+ print_memory_region_header();
+ ROP_PRINT(r);
+}
+
+#ifdef RDAR_23744374
+/*
+ * The reported size of a mapping to a file object gleaned from
+ * mach_vm_region_recurse() can exceed the underlying size of the file.
+ * If we attempt to write out the full reported size, we find that we
+ * error (EFAULT) or if we compress it, we die with the SIGBUS.
+ *
+ * See rdar://23744374
+ *
+ * Figure out what the "non-faulting" size of the object is to
+ * *host* page size resolution.
+ */
+bool
+is_actual_size(const task_t task, const struct region *r, mach_vm_size_t *hostvmsize)
+{
+ if (!r->r_info.external_pager ||
+ (r->r_info.max_protection & VM_PROT_READ) == VM_PROT_NONE)
+ return true;
+
+ const size_t pagesize_host = 1ul << pageshift_host;
+ const unsigned filepages = r->r_info.pages_resident +
+ r->r_info.pages_swapped_out;
+
+ if (pagesize_host * filepages == R_SIZE(r))
+ return true;
+
+ /*
+ * Verify that the last couple of host-pagesize pages
+ * of a file backed mapping are actually pageable in the
+ * underlying object by walking backwards from the end
+ * of the application-pagesize mapping.
+ */
+ *hostvmsize = R_SIZE(r);
+
+ const long npagemax = 1ul << (pageshift_app - pageshift_host);
+ for (long npage = 0; npage < npagemax; npage++) {
+
+ const mach_vm_address_t taddress =
+ R_ENDADDR(r) - pagesize_host * (npage + 1);
+ if (taddress < R_ADDR(r) || taddress >= R_ENDADDR(r))
+ break;
+
+ mach_msg_type_number_t pCount = VM_PAGE_INFO_BASIC_COUNT;
+ vm_page_info_basic_data_t pInfo;
+
+ kern_return_t ret = mach_vm_page_info(task, taddress, VM_PAGE_INFO_BASIC, (vm_page_info_t)&pInfo, &pCount);
+ if (KERN_SUCCESS != ret) {
+ err_mach(ret, NULL, "getting pageinfo at %llx", taddress);
+ break; /* bail */
+ }
+
+ /*
+ * If this page has been in memory before, assume it can
+ * be brought back again
+ */
+ if (pInfo.disposition & (VM_PAGE_QUERY_PAGE_PRESENT | VM_PAGE_QUERY_PAGE_REF | VM_PAGE_QUERY_PAGE_DIRTY | VM_PAGE_QUERY_PAGE_PAGED_OUT))
+ continue;
+
+ /*
+ * Force the page to be fetched to see if it faults
+ */
+ mach_vm_size_t tsize = 1ul << pageshift_host;
+ void *tmp = valloc((size_t)tsize);
+ const mach_vm_address_t vtmp = (mach_vm_address_t)tmp;
+
+ switch (ret = mach_vm_read_overwrite(task,
+ taddress, tsize, vtmp, &tsize)) {
+ case KERN_INVALID_ADDRESS:
+ *hostvmsize = taddress - R_ADDR(r);
+ break;
+ case KERN_SUCCESS:
+ break;
+ default:
+ err_mach(ret, NULL, "mach_vm_overwrite()");
+ break;
+ }
+ free(tmp);
+ }
+ return R_SIZE(r) == *hostvmsize;
+}
+#endif
diff --git a/system_cmds/gcore.tproj/vm.h b/system_cmds/gcore.tproj/vm.h
new file mode 100644
index 0000000..b912782
--- /dev/null
+++ b/system_cmds/gcore.tproj/vm.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2016 Apple Inc. All rights reserved.
+ */
+
+#include <mach/mach.h>
+#include <mach/mach_port.h>
+#include <mach/task.h>
+#include <mach/mach_vm.h>
+#include <stdbool.h>
+
+#include "corefile.h"
+#include "region.h"
+
+#ifndef _VM_H
+#define _VM_H
+
+extern void setpageshift(void);
+extern int pageshift_host;
+extern int pageshift_app;
+
+struct region;
+struct regionhead;
+
+extern void del_fileref_region(struct region *);
+extern void del_zfod_region(struct region *);
+extern void del_sparse_region(struct region *);
+extern void del_vanilla_region(struct region *);
+
+extern struct regionhead *build_region_list(task_t);
+extern int walk_region_list(struct regionhead *, walk_region_cbfn_t, void *);
+extern void del_region_list(struct regionhead *);
+
+extern void print_memory_region_header(void);
+extern void print_memory_region(const struct region *);
+extern void print_one_memory_region(const struct region *);
+
+extern walk_region_cbfn_t region_print_memory;
+extern walk_region_cbfn_t region_write_memory;
+extern walk_region_cbfn_t region_size_memory;
+
+extern int is_tagged(task_t, mach_vm_offset_t, mach_vm_offset_t, unsigned);
+
+#ifdef RDAR_23744374
+extern bool is_actual_size(const task_t, const struct region *, mach_vm_size_t *);
+#endif
+
+#endif /* _VM_H */