From 887d5eedc67e1d6b5933393070e765bc7739453c Mon Sep 17 00:00:00 2001 From: Apple Date: Tue, 26 Sep 2017 16:28:56 +0000 Subject: [PATCH] system_cmds-790.tar.gz --- .../com.apple.dynamic_pager.plist | 2 - dynamic_pager.tproj/dynamic_pager.c | 28 +- dynamic_pager.tproj/entitlements.plist | 8 + dynamic_pager.tproj/generate_plist.sh | 2 +- fs_usage.tproj/fs_usage.c | 102 +- gcore.tproj/convert.c | 749 +++++++++ gcore.tproj/convert.h | 24 + gcore.tproj/corefile.c | 562 +++++-- gcore.tproj/corefile.h | 19 +- gcore.tproj/dyld.c | 113 +- gcore.tproj/dyld.h | 8 +- gcore.tproj/dyld_shared_cache.c | 6 +- gcore.tproj/gcore-internal.1 | 194 +++ gcore.tproj/loader_additions.h | 92 +- gcore.tproj/main.c | 865 ++++++---- gcore.tproj/options.h | 40 +- gcore.tproj/region.h | 63 +- gcore.tproj/sparse.c | 284 ++-- gcore.tproj/sparse.h | 15 +- gcore.tproj/threads.c | 3 + gcore.tproj/utils.c | 344 +++- gcore.tproj/utils.h | 20 +- gcore.tproj/vanilla.c | 553 +++++-- gcore.tproj/vanilla.h | 5 +- gcore.tproj/vm.c | 115 +- gcore.tproj/vm.h | 5 +- getty.tproj/generate_plist.sh | 2 +- hostinfo.tproj/hostinfo.c | 21 +- iosim.tproj/iosim.c | 2 +- lskq.tproj/common.h | 33 +- lskq.tproj/lskq.1 | 10 + lskq.tproj/lskq.c | 341 +++- lsmp.tproj/common.h | 41 +- lsmp.tproj/json.h | 119 ++ lsmp.tproj/lsmp.1 | 4 + lsmp.tproj/lsmp.c | 59 +- lsmp.tproj/port_details.c | 704 +++++--- lsmp.tproj/task_details.c | 53 +- ltop.tproj/ltop.c | 13 +- mkfile.tproj/mkfile.8 | 2 +- mslutil/mslutil.1 | 46 + mslutil/mslutil.c | 94 ++ nvram.tproj/nvram.c | 20 + stackshot.tproj/stackshot.c | 233 +++ sysctl.tproj/sysctl.c | 3 + system_cmds.xcodeproj/project.pbxproj | 396 ++++- taskpolicy.tproj/taskpolicy.8 | 3 + taskpolicy.tproj/taskpolicy.c | 17 +- trace.tproj/trace.c | 2 +- zic.tproj/README | 16 +- zic.tproj/build_zichost.sh | 1 + zic.tproj/generate_zoneinfo.sh | 8 +- zic.tproj/ialloc.c | 46 +- zic.tproj/install_zoneinfo.sh | 14 +- zic.tproj/private.h | 141 +- zic.tproj/scheck.c | 33 +- zic.tproj/tzfile.h | 192 +++ zic.tproj/zic.8 | 112 +- zic.tproj/zic.c | 1448 ++++++++++++----- zprint.tproj/zprint.c | 169 +- 60 files changed, 6645 insertions(+), 1974 deletions(-) create mode 100644 dynamic_pager.tproj/entitlements.plist create mode 100644 gcore.tproj/convert.c create mode 100644 gcore.tproj/convert.h create mode 100644 gcore.tproj/gcore-internal.1 create mode 100644 lsmp.tproj/json.h create mode 100644 mslutil/mslutil.1 create mode 100644 mslutil/mslutil.c create mode 100644 stackshot.tproj/stackshot.c create mode 100644 zic.tproj/tzfile.h diff --git a/dynamic_pager.tproj/com.apple.dynamic_pager.plist b/dynamic_pager.tproj/com.apple.dynamic_pager.plist index bf95905..4214037 100644 --- a/dynamic_pager.tproj/com.apple.dynamic_pager.plist +++ b/dynamic_pager.tproj/com.apple.dynamic_pager.plist @@ -16,8 +16,6 @@ ProgramArguments /sbin/dynamic_pager - -F - /private/var/vm/swapfile diff --git a/dynamic_pager.tproj/dynamic_pager.c b/dynamic_pager.tproj/dynamic_pager.c index 9c0f435..deb9379 100644 --- a/dynamic_pager.tproj/dynamic_pager.c +++ b/dynamic_pager.tproj/dynamic_pager.c @@ -43,16 +43,14 @@ clean_swap_directory(const char *path) int main(int argc, char **argv) { - char default_filename[] = "/private/var/vm/swapfile"; int ch; - int err=0; static char tmp[1024]; struct statfs sfs; char *q; char fileroot[512]; seteuid(getuid()); - strcpy(fileroot, default_filename); + fileroot[0] = '\0'; while ((ch = getopt(argc, argv, "F:")) != EOF) { switch((char)ch) { @@ -68,6 +66,25 @@ main(int argc, char **argv) } } + /* + * set vm.swapfileprefix if a fileroot was passed from the command + * line, otherwise get the value from the kernel + */ + if (fileroot[0] != '\0') { + if (sysctlbyname("vm.swapfileprefix", NULL, 0, fileroot, sizeof(fileroot)) == -1) { + perror("Failed to set swapfile name prefix"); + } + } else { + size_t fileroot_len = sizeof(fileroot); + if (sysctlbyname("vm.swapfileprefix", fileroot, &fileroot_len, NULL, 0) == -1) { + perror("Failed to get swapfile name prefix"); + /* + * can't continue without a fileroot + */ + return (0); + } + } + /* * get rid of the filename at the end of the swap file specification * we only want the portion of the pathname that should already exist @@ -93,10 +110,5 @@ main(int argc, char **argv) chown(tmp, 0, 0); - err = sysctlbyname("vm.swapfileprefix", NULL, 0, fileroot, sizeof(fileroot)); - if (err) { - (void)fprintf(stderr, "Failed to set swapfile name prefix with error: %d\n", err); - } - return (0); } diff --git a/dynamic_pager.tproj/entitlements.plist b/dynamic_pager.tproj/entitlements.plist new file mode 100644 index 0000000..2e0c44d --- /dev/null +++ b/dynamic_pager.tproj/entitlements.plist @@ -0,0 +1,8 @@ + + + + + com.apple.rootless.volume.VM + + + diff --git a/dynamic_pager.tproj/generate_plist.sh b/dynamic_pager.tproj/generate_plist.sh index 288f045..290a550 100644 --- a/dynamic_pager.tproj/generate_plist.sh +++ b/dynamic_pager.tproj/generate_plist.sh @@ -4,7 +4,7 @@ set -x cp "${SCRIPT_INPUT_FILE_0}" "${SCRIPT_OUTPUT_FILE_0}" case "$PLATFORM_NAME" in -iphone*|appletv*|watch*) +iphone*|appletv*|watch*|bridge*) /usr/libexec/PlistBuddy -c "Add :LaunchOnlyOnce bool true" "${SCRIPT_OUTPUT_FILE_0}" ;; macosx) diff --git a/fs_usage.tproj/fs_usage.c b/fs_usage.tproj/fs_usage.c index 5289421..f7f9bfc 100644 --- a/fs_usage.tproj/fs_usage.c +++ b/fs_usage.tproj/fs_usage.c @@ -40,7 +40,8 @@ #include #include -#include +#include +#include #include #include @@ -126,6 +127,7 @@ void extend_syscall(uintptr_t thread, int type, ktrace_event_t event); /* printing routines */ bool check_filter_mode(pid_t pid, th_info_t ti, unsigned long type, int error, int retval, char *sc_name); void format_print(th_info_t ti, char *sc_name, ktrace_event_t event, unsigned long type, int format, uint64_t now, uint64_t stime, int waited, const char *pathname, struct diskio *dio); +int print_open(ktrace_event_t event, uintptr_t flags); /* metadata info hash routines */ void meta_add_name(uint64_t blockno, const char *pathname); @@ -717,7 +719,7 @@ main(int argc, char *argv[]) char ch; int rv; bool exclude_pids = false; - double time_limit = 0.0; + uint64_t time_limit_ns = 0; get_screenwidth(); @@ -760,8 +762,12 @@ main(int argc, char *argv[]) break; case 't': - time_limit = atof(optarg); - + time_limit_ns = (uint64_t)(NSEC_PER_SEC * atof(optarg)); + if (time_limit_ns == 0) { + fprintf(stderr, "ERROR: could not set time limit to %s\n", + optarg); + exit(1); + } break; case 'R': @@ -789,16 +795,15 @@ main(int argc, char *argv[]) argc -= optind; argv += optind; - if (time_limit > 0.0) { + if (time_limit_ns > 0) { if (RAW_flag) { fprintf(stderr, "NOTE: time limit ignored when a raw file is specified\n"); } else { - stop_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); - dispatch_source_set_timer(stop_timer, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * time_limit), DISPATCH_TIME_FOREVER, 0); - dispatch_source_set_event_handler(stop_timer, ^{ + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, time_limit_ns), + dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), + ^{ ktrace_end(s, 0); }); - dispatch_resume(stop_timer); } } @@ -902,7 +907,7 @@ main(int argc, char *argv[]) if (!wideflag) get_screenwidth(); }); - dispatch_resume(sigwinch_source); + dispatch_activate(sigwinch_source); init_shared_cache_mapping(); @@ -923,6 +928,8 @@ main(int argc, char *argv[]) ktrace_set_default_event_names_enabled(KTRACE_FEATURE_DISABLED); ktrace_set_execnames_enabled(s, KTRACE_FEATURE_LAZY); ktrace_set_vnode_paths_enabled(s, true); + /* no need to symbolicate addresses */ + ktrace_set_uuid_map_enabled(s, KTRACE_FEATURE_DISABLED); rv = ktrace_start(s, dispatch_get_main_queue()); @@ -1551,6 +1558,40 @@ check_filter_mode(pid_t pid, th_info_t ti, unsigned long type, int error, int re return ret; } +int +print_open(ktrace_event_t event, uintptr_t flags) +{ + char mode[] = "______"; + + if (flags & O_RDWR) { + mode[0] = 'R'; + mode[1] = 'W'; + } else if (flags & O_WRONLY) { + mode[1] = 'W'; + } else { + mode[0] = 'R'; + } + + if (flags & O_CREAT) { + mode[2] = 'C'; + } + if (flags & O_APPEND) { + mode[3] = 'A'; + } + if (flags & O_TRUNC) { + mode[4] = 'T'; + } + if (flags & O_EXCL) { + mode[5] = 'E'; + } + + if (event->arg1) { + return printf(" [%3d] (%s) ", (int)event->arg1, mode); + } else { + return printf(" F=%-3d (%s) ", (int)event->arg2, mode); + } +} + /* * called from: * @@ -2355,46 +2396,15 @@ format_print(th_info_t ti, char *sc_name, ktrace_event_t event, break; } - case FMT_OPENAT: case FMT_OPEN: - { - /* - * open - */ - char mode[7]; - - memset(mode, '_', 6); - mode[6] = '\0'; - - if (ti->arg2 & O_RDWR) { - mode[0] = 'R'; - mode[1] = 'W'; - } else if (ti->arg2 & O_WRONLY) { - mode[1] = 'W'; - } else { - mode[0] = 'R'; - } - - if (ti->arg2 & O_CREAT) - mode[2] = 'C'; - - if (ti->arg2 & O_APPEND) - mode[3] = 'A'; - - if (ti->arg2 & O_TRUNC) - mode[4] = 'T'; - - if (ti->arg2 & O_EXCL) - mode[5] = 'E'; - - if (event->arg1) - clen += printf(" [%3d] (%s) ", (int)event->arg1, mode); - else - clen += printf(" F=%-3d (%s) ", (int)event->arg2, mode); + clen += print_open(event, ti->arg2); + nopadding = 1; + break; + case FMT_OPENAT: + clen += print_open(event, ti->arg3); nopadding = 1; break; - } case FMT_SOCKET: { diff --git a/gcore.tproj/convert.c b/gcore.tproj/convert.c new file mode 100644 index 0000000..33dad94 --- /dev/null +++ b/gcore.tproj/convert.c @@ -0,0 +1,749 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} + +/* + * 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"); + } + + const size_t pathsize = path ? strlen(path) : 0; + int 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/gcore.tproj/convert.h b/gcore.tproj/convert.h new file mode 100644 index 0000000..03854b8 --- /dev/null +++ b/gcore.tproj/convert.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016 Apple Inc. All rights reserved. + */ + +#include "options.h" + +#include + +#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/gcore.tproj/corefile.c b/gcore.tproj/corefile.c index addcda4..8242b6e 100644 --- a/gcore.tproj/corefile.c +++ b/gcore.tproj/corefile.c @@ -18,6 +18,7 @@ #include #include #include +#include native_mach_header_t * make_corefile_mach_header(void *data) @@ -50,6 +51,7 @@ make_coreinfo_command(native_mach_header_t *mh, void *data, const uuid_t aoutid, 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; @@ -58,30 +60,63 @@ make_coreinfo_command(native_mach_header_t *mh, void *data, const uuid_t aoutid, return cc; } -static native_segment_command_t * -make_native_segment_command(void *data, mach_vm_offset_t vmaddr, mach_vm_size_t vmsize, off_t fileoff, size_t filesize, unsigned maxprot, unsigned initprot, unsigned comptype) +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(vmsize); -#if defined(__LP64__) - sc->vmaddr = vmaddr; - sc->vmsize = vmsize; - sc->fileoff = fileoff; -#else - sc->vmaddr = (uintptr_t)vmaddr; - sc->vmsize = (size_t)vmsize; - sc->fileoff = (long)fileoff; -#endif - sc->filesize = filesize; + 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 = proto_SG_COMP_MAKE_FLAGS(comptype); + 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 */ @@ -119,53 +154,81 @@ size_fileref_subregion(const struct subregion *s, struct size_core *sc) sc->memsize += S_SIZE(s); } -#ifdef CONFIG_REFSC 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_libent->le_pathname); + size_t cmdsize = cmdsize_fileref_command(r->r_fileref->fr_pathname); sc->headersize += cmdsize; sc->count++; sc->memsize += R_SIZE(r); } -#endif static struct proto_fileref_command * -make_fileref_command(void *data, const struct libent *le, mach_vm_offset_t vmaddr, mach_vm_size_t vmsize, off_t fileoff, off_t filesize, unsigned maxprot, unsigned initprot) +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 *fr = data; + struct proto_fileref_command *fc = data; size_t len; - fr->cmd = proto_LC_FILEREF; - fr->cmdsize = sizeof (*fr); - if (0 != (len = strlen(le->le_pathname))) { + 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 */ - fr->filename.offset = sizeof (*fr); - void *s = fr + 1; - strlcpy(s, le->le_pathname, ++len); // NUL-terminated for mmap sanity - fr->cmdsize += roundup(len, sizeof (long)); - assert(cmdsize_fileref_command(le->le_pathname) == fr->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); } - uuid_copy(fr->uuid, le->le_uuid); - - fr->vmaddr = vmaddr; - assert(vmsize); - fr->vmsize = vmsize; - assert(fileoff >= 0); - fr->fileoff = fileoff; - fr->filesize = filesize; - - assert(maxprot & VM_PROT_READ); - fr->maxprot = maxprot; - fr->initprot = initprot; - return fr; + /* + * 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; } /* @@ -176,23 +239,26 @@ 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 (opt->debug && !issubregiontype(s, SEG_TEXT) && !issubregiontype(s, SEG_LINKEDIT)) + 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 proto_fileref_command *fc = make_fileref_command(wsd->wsd_lc, le, S_ADDR(s), S_SIZE(s), S_MACHO_FILEOFF(s), S_SIZE(s), r->r_info.max_protection, r->r_info.protection); + 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 (opt->debug > 1) { + 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; } -#ifdef CONFIG_REFSC - /* * 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 @@ -206,12 +272,18 @@ write_fileref_region(const struct region *r, struct write_segment_data *wsd) 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 struct proto_fileref_command *fc = make_fileref_command(wsd->wsd_lc, le, R_ADDR(r), R_SIZE(r), r->r_fileref->fr_offset, (size_t)R_SIZE(r), r->r_info.max_protection, r->r_info.protection); + 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 (opt->debug > 1) { + if (OPTIONS_DEBUG(opt, 3)) { hsize_str_t hstr; - printr(r, "ref '%s' %s (vm %llx-%llx, file offset %lld for %s)\n", le->le_filename, "(type?)", (uint64_t)fc->vmaddr, (uint64_t)fc->vmaddr + fc->vmsize, (int64_t)fc->fileoff, str_hsize(hstr, fc->filesize)); + 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; } @@ -222,7 +294,6 @@ const struct regionop fileref_ops = { del_fileref_region, }; -#endif /* CONFIG_REFSC */ #pragma mark -- ZFOD segments written only to the header -- @@ -231,7 +302,7 @@ size_zfod_region(const struct region *r, struct size_core *sc) { assert(0 == r->r_nsubregions); assert(r->r_inzfodregion); - sc->headersize += sizeof (native_segment_command_t); + sc->headersize += sizeof_segment_command(); sc->count++; sc->memsize += R_SIZE(r); } @@ -242,8 +313,12 @@ 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 void *sc = make_native_segment_command(wsd->wsd_lc, R_ADDR(r), R_SIZE(r), wsd->wsd_foffset, 0, r->r_info.max_protection, r->r_info.protection, 0); - commit_load_command(wsd, sc); + 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; } @@ -256,35 +331,25 @@ const struct regionop zfod_ops = { #pragma mark -- Regions containing data -- static walk_return_t -pwrite_memory(struct write_segment_data *wsd, const void *addr, size_t size, mach_vm_offset_t memaddr, size_t memsize) +pwrite_memory(struct write_segment_data *wsd, const void *addr, size_t size, const struct vm_range *vr) { assert(size); - int error = 0; - ssize_t nwritten = 0; - - if (opt->sizebound > 0 && - wsd->wsd_foffset + (off_t)size > opt->sizebound) { - error = EFBIG; - } else { - nwritten = pwrite(wsd->wsd_fd, addr, size, wsd->wsd_foffset); - if (nwritten < 0) - error = errno; - } + ssize_t nwritten; + const int error = bounded_pwrite(wsd->wsd_fd, addr, size, wsd->wsd_foffset, &wsd->wsd_nocache, &nwritten); - if (error || opt->debug > 1) { + if (error || OPTIONS_DEBUG(opt, 3)) { hsize_str_t hsz; - printf("%llx-%llx writing %ld bytes at offset %lld -> ", - memaddr, memaddr+memsize, size, wsd->wsd_foffset); + 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 != memsize) + else if (size != V_SIZE(vr)) printf("(%s in memory) ", - str_hsize(hsz, memsize)); + str_hsize(hsz, V_SIZE(vr))); } printf("\n"); } @@ -318,16 +383,16 @@ segment_compflags(compression_algorithm ca, unsigned *algnum) { switch (ca) { case COMPRESSION_LZ4: - *algnum = proto_SG_COMP_LZ4; + *algnum = kCOMP_LZ4; break; case COMPRESSION_ZLIB: - *algnum = proto_SG_COMP_ZLIB; + *algnum = kCOMP_ZLIB; break; case COMPRESSION_LZMA: - *algnum = proto_SG_COMP_LZMA; + *algnum = kCOMP_LZMA; break; case COMPRESSION_LZFSE: - *algnum = proto_SG_COMP_LZFSE; + *algnum = kCOMP_LZFSE; break; default: err(EX_SOFTWARE, "unsupported compression algorithm %x", ca); @@ -335,6 +400,190 @@ segment_compflags(compression_algorithm ca, unsigned *algnum) 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) { @@ -344,10 +593,6 @@ write_memory_range(struct write_segment_data *wsd, const struct region *r, mach_ walk_return_t step = WALK_CONTINUE; do { - unsigned algorithm = 0; - void *dstbuf = NULL; - size_t filesize; - vmsize = resid; /* @@ -360,93 +605,72 @@ write_memory_range(struct write_segment_data *wsd, const struct region *r, mach_ vmsize = opt->chunksize; assert(vmsize <= INT32_MAX); - mach_vm_offset_t data; - mach_vm_offset_t data_count; - - kern_return_t kr; - const void *srcaddr; - - if (r->r_incommregion) { - /* - * For commpage access, we just copy from our own address space. - */ - data = 0; - data_count = vmsize; - kr = mach_vm_allocate(mach_task_self(), &data, data_count, VM_FLAGS_ANYWHERE); - if (KERN_SUCCESS != kr || data == 0) { - err_mach(kr, r, "subregion %llx-%llx, mach_vm_allocate()", vmaddr, vmaddr + vmsize); - if (opt->debug) { - print_memory_region_header(); - ROP_PRINT(r); - } - break; - } - if (opt->debug) - printr(r, "subregion %llx-%llx, copying from self\n", vmaddr, vmaddr+vmsize); - srcaddr = (const void *)memcpy((void *)data, (void *)vmaddr, vmsize); - } else { - /* - * Most segments with data are mapped here - */ - vm_offset_t data32 = 0; - mach_msg_type_number_t data32_count; - kr = mach_vm_read(wsd->wsd_task, vmaddr, vmsize, &data32, &data32_count); - if (KERN_SUCCESS != kr || data32 == 0 || data32_count < vmsize) { - err_mach(kr, r, "subregion %llx-%llx, mach_vm_read()", vmaddr, vmaddr + vmsize); - if (opt->debug) { - print_memory_region_header(); - ROP_PRINT(r); - } - break; - } - data = data32; - data_count = data32_count; - mach_vm_behavior_set(mach_task_self(), data, data_count, VM_BEHAVIOR_SEQUENTIAL); - srcaddr = (const void *)data; - } + const struct vm_range vr = { + .addr = vmaddr, + .size = vmsize, + }; + struct vm_range d, *dp = &d; - assert(vmsize); + 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); - if (opt->compress) { - dstbuf = malloc((size_t)vmsize); - if (dstbuf) { + mach_vm_behavior_set(mach_task_self(), V_ADDR(dp), V_SIZE(dp), VM_BEHAVIOR_SEQUENTIAL); - filesize = compression_encode_buffer(dstbuf, (size_t)vmsize, srcaddr, (size_t)vmsize, NULL, opt->calgorithm); + void *dstbuf = NULL; + unsigned algorithm = 0; + size_t filesize; - if (filesize > 0 && filesize < vmsize) { - srcaddr = dstbuf; - if (segment_compflags(opt->calgorithm, &algorithm) != 0) { - free(dstbuf); - mach_vm_deallocate(mach_task_self(), data, data_count); - return WALK_ERROR; - } - } else { - free(dstbuf); - dstbuf = NULL; - filesize = (size_t)vmsize; - } - } else - filesize = (size_t)vmsize; - } else - filesize = (size_t)vmsize; + 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); - native_segment_command_t *sc = make_native_segment_command(wsd->wsd_lc, vmaddr, vmsize, wsd->wsd_foffset, filesize, r->r_info.max_protection, r->r_info.protection, algorithm); - - assert((sc->flags == 0) ^ (sc->filesize < sc->vmsize)); - - step = pwrite_memory(wsd, srcaddr, sc->filesize, vmaddr, sc->vmsize); - if (dstbuf) - free(dstbuf); - mach_vm_deallocate(mach_task_self(), data, data_count); + 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, (const void *)sc); - resid -= vmsize; - vmaddr += vmsize; - } while (resid); + if (WALK_ERROR == step) + break; + commit_load_command(wsd, wsd->wsd_lc); + resid -= vmsize; + vmaddr += vmsize; + } while (resid); return step; } @@ -464,7 +688,7 @@ getvmsize_host(const task_t task, const struct region *r) if (pageshift_host != pageshift_app) { is_actual_size(task, r, &vmsize_host); - if (opt->debug && R_SIZE(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; @@ -482,9 +706,7 @@ write_sparse_region(const struct region *r, struct write_segment_data *wsd) { assert(r->r_nsubregions); assert(!r->r_inzfodregion); -#ifdef CONFIG_REFSC assert(NULL == r->r_fileref); -#endif const mach_vm_size_t vmsize_host = getvmsize_host(wsd->wsd_task, r); walk_return_t step = WALK_CONTINUE; @@ -492,7 +714,7 @@ write_sparse_region(const struct region *r, struct write_segment_data *wsd) for (unsigned i = 0; i < r->r_nsubregions; i++) { const struct subregion *s = r->r_subregions[i]; - if (s->s_isfileref) + if (s->s_isuuidref) step = write_fileref_subregion(r, s, wsd); else { /* Write this one out as real data */ @@ -500,7 +722,7 @@ write_sparse_region(const struct region *r, struct write_segment_data *wsd) 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 (opt->debug) + if (OPTIONS_DEBUG(opt, 3)) printr(r, "(subregion size tweak: was %llx, is %llx)\n", S_SIZE(s), vmsize); } @@ -518,9 +740,7 @@ write_vanilla_region(const struct region *r, struct write_segment_data *wsd) { assert(0 == r->r_nsubregions); assert(!r->r_inzfodregion); -#ifdef CONFIG_REFSC assert(NULL == r->r_fileref); -#endif const mach_vm_size_t vmsize_host = getvmsize_host(wsd->wsd_task, r); return write_memory_range(wsd, r, R_ADDR(r), vmsize_host); @@ -542,7 +762,7 @@ static unsigned long count_memory_range(mach_vm_offset_t vmsize) { unsigned long count; - if (opt->compress && opt->chunksize > 0) { + if (opt->chunksize) { count = (size_t)vmsize / opt->chunksize; if (vmsize != (mach_vm_offset_t)count * opt->chunksize) count++; @@ -559,7 +779,7 @@ 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 (native_segment_command_t) * count; + sc->headersize += sizeof_segment_command() * count; sc->count += count; sc->memsize += S_SIZE(s); } @@ -572,12 +792,12 @@ size_sparse_region(const struct region *r, struct size_core *sc_sparse, struct s 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_isfileref) + if (s->s_isuuidref) size_fileref_subregion(s, sc_fileref); else size_sparse_subregion(s, sc_sparse); } - if (opt->debug) { + 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) @@ -598,11 +818,11 @@ 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 (native_segment_command_t) * count; + sc->headersize += sizeof_segment_command() * count; sc->count += count; sc->memsize += R_SIZE(r); - if (opt->debug && count > 1) + if (OPTIONS_DEBUG(opt, 3) && count != 1) printr(r, "range with 1 region, but requires %lu segment commands\n", count); } @@ -619,10 +839,8 @@ region_size_memory(struct region *r, void *arg) if (&zfod_ops == r->r_op) size_zfod_region(r, &ssd->ssd_zfod); -#ifdef CONFIG_REFSC else if (&fileref_ops == r->r_op) size_fileref_region(r, &ssd->ssd_fileref); -#endif else if (&sparse_ops == r->r_op) size_sparse_region(r, &ssd->ssd_sparse, &ssd->ssd_fileref); else if (&vanilla_ops == r->r_op) diff --git a/gcore.tproj/corefile.h b/gcore.tproj/corefile.h index d455d7e..2bdc43e 100644 --- a/gcore.tproj/corefile.h +++ b/gcore.tproj/corefile.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Apple Inc. All rights reserved. + * Copyright (c) 2016 Apple Inc. All rights reserved. */ #include "loader_additions.h" @@ -26,16 +26,22 @@ typedef struct segment_command native_segment_command_t; #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) { +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) { +static __inline void mach_header_inc_sizeofcmds(native_mach_header_t *mh, uint32_t inc) { mh->sizeofcmds += inc; } @@ -48,7 +54,7 @@ struct size_core { 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 file references */ + struct size_core ssd_fileref; /* full & sparse segments with uuid file references */ struct size_core ssd_zfod; /* full segments with zfod pages */ }; @@ -57,6 +63,7 @@ struct write_segment_data { native_mach_header_t *wsd_mh; void *wsd_lc; int wsd_fd; + bool wsd_nocache; off_t wsd_foffset; off_t wsd_nwritten; }; diff --git a/gcore.tproj/dyld.c b/gcore.tproj/dyld.c index c8235b5..0ff3958 100644 --- a/gcore.tproj/dyld.c +++ b/gcore.tproj/dyld.c @@ -129,16 +129,6 @@ struct liblist { }; static STAILQ_HEAD(, liblist) libhead = STAILQ_HEAD_INITIALIZER(libhead); -static unsigned long -namehash(const char *nm) -{ - unsigned long result = 5381; - int c; - while (0 != (c = *nm++)) - result = (result * 33) ^ c; - return result; /* modified djb2 */ -} - static const struct libent * libent_lookup_bypathname_withhash(const char *nm, const unsigned long hash) { @@ -178,25 +168,33 @@ libent_lookup_first_bytype(uint32_t mhtype) } const struct libent * -libent_insert(const char *nm, const uuid_t uuid, uint64_t mhaddr, const native_mach_header_t *mh) +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 - unsigned long nmhash = namehash(nm); + 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) + if (NULL != le) { + free(nm); return le; + } - if (opt->debug > 3) { + if (OPTIONS_DEBUG(opt, 3)) { uuid_string_t uustr; uuid_unparse_lower(uuid, uustr); - printf("[adding <'%s', %s, 0x%llx, %p>]\n", nm, uustr, mhaddr, mh); + 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 = strdup(nm); + 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; @@ -205,7 +203,13 @@ libent_insert(const char *nm, const uuid_t uuid, uint64_t mhaddr, const native_m 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; @@ -216,7 +220,7 @@ 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) { + _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) { @@ -225,6 +229,11 @@ libent_build_nametable(task_t task, dyld_process_info dpi) */ 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 || @@ -233,17 +242,73 @@ libent_build_nametable(task_t task, dyld_process_info dpi) valid = false; break; } - if (lc->cmdsize) - lc = (const void *)((caddr_t)lc + lc->cmdsize); - else + 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); + if (valid) + (void) libent_insert(path, uuid, mhaddr, mh, &vr, objoff); } } }); - if (opt->debug) + if (OPTIONS_DEBUG(opt, 3)) printf("nametable %sconstructed\n", valid ? "" : "NOT "); return valid; } diff --git a/gcore.tproj/dyld.h b/gcore.tproj/dyld.h index cac1409..02ded2a 100644 --- a/gcore.tproj/dyld.h +++ b/gcore.tproj/dyld.h @@ -4,6 +4,7 @@ #include "options.h" #include "corefile.h" +#include "utils.h" #include #include @@ -17,17 +18,18 @@ struct libent { char *le_pathname; uuid_t le_uuid; uint64_t le_mhaddr; // address in target process - const native_mach_header_t *le_mh; // cached copy in this address space + 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 *); +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/gcore.tproj/dyld_shared_cache.c b/gcore.tproj/dyld_shared_cache.c index 91aefa5..92d0042 100644 --- a/gcore.tproj/dyld_shared_cache.c +++ b/gcore.tproj/dyld_shared_cache.c @@ -73,14 +73,14 @@ shared_cache_filename(const uuid_t uu) int d = open(fe->fts_accpath, O_RDONLY); if (-1 == d) { - if (opt->debug) + 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 (opt->debug) + if (OPTIONS_DEBUG(opt, 1)) printf("%s: cannot mmap - %s\n", fe->fts_accpath, strerror(errno)); continue; } @@ -89,7 +89,7 @@ shared_cache_filename(const uuid_t uu) 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 (opt->debug) { + 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); diff --git a/gcore.tproj/gcore-internal.1 b/gcore.tproj/gcore-internal.1 new file mode 100644 index 0000000..665e2f5 --- /dev/null +++ b/gcore.tproj/gcore-internal.1 @@ -0,0 +1,194 @@ +.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 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 +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 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 diff --git a/gcore.tproj/loader_additions.h b/gcore.tproj/loader_additions.h index 47e3054..16cf5b5 100644 --- a/gcore.tproj/loader_additions.h +++ b/gcore.tproj/loader_additions.h @@ -10,56 +10,94 @@ /* * Something like this should end up in */ -#define proto_LC_COREINFO 0x40 /* unofficial value!! */ + +#define proto_LC_COREINFO 0x140 /* unofficial value!! */ #define proto_CORETYPE_KERNEL 1 -#define proto_CORETYPE_USER 2 +#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 */ - /* - * 'type' determines the content of the corefile; interpretation - * of the address and uuid fields are specific to the type. - */ - uint32_t type; /* CORETYPE_KERNEL, CORETYPE_USER etc. */ + 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 proto_SG_COMP_ALG_MASK 0x7 +#define COMP_ALG_MASK 0x7 /* carve out 3 bits for an enum i.e. allow for 7 flavors */ -#define proto_SG_COMP_ALG_SHIFT 4 /* (bottom 4 bits taken) */ +#define COMP_ALG_SHIFT 4 /* (bottom 4 bits taken) */ /* zero -> no compression */ -#define proto_SG_COMP_LZ4 1 /* 0x100 */ -#define proto_SG_COMP_ZLIB 2 /* 0x205 */ -#define proto_SG_COMP_LZMA 3 /* 0x306 */ -#define proto_SG_COMP_LZFSE 4 /* 0x801 */ +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 proto_SG_COMP_ALG_TYPE(flags) (((flags) >> proto_SG_COMP_ALG_SHIFT) & proto_SG_COMP_ALG_MASK) -#define proto_SG_COMP_MAKE_FLAGS(type) (((type) & proto_SG_COMP_ALG_MASK) << proto_SG_COMP_ALG_SHIFT) +#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) -#define proto_LC_FILEREF 0x41 /* unofficial value! */ - -struct proto_fileref_command { - uint32_t cmd; /* LC_FILEREF */ +struct proto_coredata_command { + uint32_t cmd; /* LC_COREDATA */ uint32_t cmdsize; - union lc_str filename; /* filename these bits come from */ - uint8_t uuid[16]; /* uuid if known */ - 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 initprot; /* initial VM protection */ + 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/gcore.tproj/main.c b/gcore.tproj/main.c index d434468..4c2c3aa 100644 --- a/gcore.tproj/main.c +++ b/gcore.tproj/main.c @@ -7,6 +7,7 @@ #include "corefile.h" #include "vanilla.h" #include "sparse.h" +#include "convert.h" #include #include @@ -71,7 +72,7 @@ get_bsdinfo(pid_t pid) } static char * -format_gcore_name(const char *fmt, const struct proc_bsdinfo *pbi) +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); @@ -93,7 +94,7 @@ format_gcore_name(const char *fmt, const struct proc_bsdinfo *pbi) return s - str; }; - ptrdiff_t (^outint)(int value)= ^(int value) { + ptrdiff_t (^outint)(int value) = ^(int value) { char id[11]; snprintf(id, sizeof (id), "%u", value); return outstr(id); @@ -123,14 +124,13 @@ format_gcore_name(const char *fmt, const struct proc_bsdinfo *pbi) outchar(c); break; case 'P': - outint(pbi->pbi_pid); + outint(pid); break; case 'U': - outint(pbi->pbi_uid); + outint(uid); break; case 'N': - outstr(pbi->pbi_name[0] ? - pbi->pbi_name : pbi->pbi_comm); + outstr(nm); break; case 'T': outtstamp(); // ISO 8601 format @@ -152,17 +152,153 @@ 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; -int -main(int argc, char *const *argv) -{ - if (NULL == (pgm = strrchr(*argv, '/'))) - pgm = *argv; - else - pgm++; +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, +}; + +static int +gcore_main(int argc, char *const *argv) +{ #define ZOPT_ALG (0) #define ZOPT_CHSIZE (ZOPT_ALG + 1) @@ -175,13 +311,15 @@ main(int argc, char *const *argv) err_set_exit_b(^(int eval) { if (EX_USAGE == eval) { fprintf(stderr, - "usage:\n\t%s [-s] [-v] [[-o file] | [-c pathfmt ]] [-b size] " + "usage:\t%s [-s] [-v] [[-o file] | [-c pathfmt ]] [-b size] " #if DEBUG - "[-d] [-n] [-i] [-p] [-S] [-z] [-C] " - "[-Z compression-options] " -#ifdef CONFIG_REFSC - "[-R] " +#ifdef CONFIG_DEBUG + "[-d] " #endif + "[-x] [-C] " + "[-Z compression-options] " + "[-t size] " + "[-F] " #endif "pid\n", pgm); #if DEBUG @@ -197,33 +335,11 @@ main(int argc, char *const *argv) char *corefmt = NULL; char *corefname = NULL; - const size_t oneM = 1024 * 1024; - -#define LARGEST_CHUNKSIZE INT32_MAX -#define DEFAULT_COMPRESSION_CHUNKSIZE (16 * oneM) - - struct options options = { - .corpse = 0, - .suspend = 0, - .preserve = 0, - .verbose = 0, - .debug = 0, - .dryrun = 0, - .sparse = 0, - .sizebound = 0, - .coreinfo = 0, -#ifdef OPTIONS_REFSC - .scfileref = 0, -#endif - .compress = 0, - .chunksize = LARGEST_CHUNKSIZE, - .calgorithm = COMPRESSION_LZFSE, - }; int c; char *sopts, *value; - while ((c = getopt(argc, argv, "inmvdszpCSRZ:o:c:b:")) != -1) { + while ((c = getopt(argc, argv, "vdsxCFZ:o:c:b:t:")) != -1) { switch (c) { /* @@ -257,44 +373,23 @@ main(int argc, char *const *argv) /* * dev and debugging help */ - case 'n': /* write the core file to /dev/null */ - options.dryrun++; - break; +#ifdef CONFIG_DEBUG case 'd': /* debugging */ options.debug++; options.verbose++; options.preserve++; break; - case 'p': /* preserve partial core file (even if errors) */ - options.preserve++; - break; - +#endif /* * Remaining options are experimental and/or * affect the content of the core file */ - case 'i': /* include LC_COREINFO data */ - options.coreinfo++; + case 'x': /* write extended format (small) core files */ + options.extended++; + options.chunksize = DEFAULT_COMPRESSION_CHUNKSIZE; break; - case 'C': /* corpsify rather than suspend */ - options.corpse++; - break; -#ifdef CONFIG_REFSC - case 'R': /* include the shared cache by reference */ - options.scfileref++; - options.coreinfo++; - break; -#endif - case 'S': /* use dyld info to control the content */ - options.sparse++; - options.coreinfo++; - break; - case 'z': /* create compressed LC_SEGMENT* segments */ - if (0 == options.compress) { - options.compress++; - options.chunksize = DEFAULT_COMPRESSION_CHUNKSIZE; - } - options.coreinfo++; + case 'C': /* forcibly corpsify rather than suspend */ + options.corpsify++; break; case 'Z': /* control compression options */ /* @@ -302,10 +397,8 @@ main(int argc, char *const *argv) * (Default to LZ4 compression when the * process is suspended, LZFSE when corpsed?) */ - if (0 == options.compress) { - options.compress++; - options.chunksize = DEFAULT_COMPRESSION_CHUNKSIZE; - } + if (0 == options.extended) + errx(EX_USAGE, "illegal flag combination"); sopts = optarg; while (*sopts) { size_t chsize; @@ -317,22 +410,17 @@ main(int argc, char *const *argv) "%s suboption", zoptkeys[ZOPT_ALG]); if (strcmp(value, "lz4") == 0) - options.calgorithm = - COMPRESSION_LZ4; + options.calgorithm = COMPRESSION_LZ4; else if (strcmp(value, "zlib") == 0) - options.calgorithm = - COMPRESSION_ZLIB; + options.calgorithm = COMPRESSION_ZLIB; else if (strcmp(value, "lzma") == 0) - options.calgorithm = - COMPRESSION_LZMA; + options.calgorithm = COMPRESSION_LZMA; else if (strcmp(value, "lzfse") == 0) - options.calgorithm = - COMPRESSION_LZFSE; + options.calgorithm = COMPRESSION_LZFSE; else errx(EX_USAGE, "unknown algorithm '%s'" " for %s suboption", - value, - zoptkeys[ZOPT_ALG]); + value, zoptkeys[ZOPT_ALG]); break; case ZOPT_CHSIZE: /* set the chunksize */ if (NULL == value) @@ -353,245 +441,414 @@ main(int argc, char *const *argv) 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"); - opt = &options; + 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; + + if (-1 != kill(pid, 0)) { + /* process or corpse that responds to signals */ + pbi = get_bsdinfo(pid); + if (NULL == pbi) + errx(EX_OSERR, "cannot get process info for %d", pid); + + /* 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) { + 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 ((opt->dryrun ? 1 : 0) + - (NULL != corefname ? 1 : 0) + - (NULL != corefmt ? 1 : 0) > 1) - errx(EX_USAGE, "specify only one of -n, -o and -c"); + 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; +} - setpageshift(); +#if defined(CONFIG_GCORE_FREF) || defined(CONFIG_GCORE_MAP) || defined(GCONFIG_GCORE_CONV) - const pid_t pid = atoi(argv[optind]); - if (pid < 1 || getpid() == pid) - errx(EX_DATAERR, "invalid pid: %d", pid); - if (-1 == kill(pid, 0)) { - switch (errno) { - case ESRCH: - errc(EX_DATAERR, errno, "no process with pid %d", pid); - default: - errc(EX_DATAERR, errno, "pid %d", pid); - } - } +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); - const struct proc_bsdinfo *pbi = get_bsdinfo(pid); - if (NULL == pbi) - errx(EX_OSERR, "cannot get bsdinfo about %d", pid); + struct mach_header mh; + if (-1 == pread(fd, &mh, sizeof (mh), 0)) + errc(EX_OSERR, errno, "cannot read mach header from %s", infile); - /* - * 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); + 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; +} -#if defined(__LP64__) - if ((pbi->pbi_flags & PROC_FLAG_LP64) == 0) -#else - if ((pbi->pbi_flags & PROC_FLAG_LP64) != 0) -#endif - errx(EX_OSERR, "cannot match data model of %d", pid); - - /* - * These are experimental options for the moment. - * These will likely change. - * Some may become defaults, some may be removed altogether. - */ - if (opt->sparse || -#ifdef CONFIG_REFSC - opt->scfileref || #endif - opt->compress || - opt->corpse || - opt->coreinfo) - warnx("experimental option(s) used, " - "resulting corefile may be unusable."); - - 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) { - 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 (opt->debug) - printf("corefmt '%s'\n", corefmt); - } - } - corefname = format_gcore_name(corefmt, pbi); - free(corefmt); - } - task_t task; - kern_return_t 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)); - } +#ifdef CONFIG_GCORE_FREF - /* - * Now that we have the task port, we 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. - */ - if (getgid() != pbi->pbi_gid && - setgid(pbi->pbi_gid) == -1) - errc(EX_NOPERM, errno, "insufficient privilege"); - - if (getuid() != pbi->pbi_uid && - setuid(pbi->pbi_uid) == -1) - errc(EX_NOPERM, errno, "insufficient privilege"); - - int fd; - - if (opt->dryrun) { - free(corefname); - corefname = strdup("/dev/null"); - fd = open(corefname, O_RDWR); - } else { - fd = open(corefname, O_RDWR | O_CREAT | O_EXCL, 0400); - - struct stat st; - - if (-1 == fd || -1 == fstat(fd, &st)) - errc(EX_CANTCREAT, errno, "%s", corefname); - if ((st.st_mode & S_IFMT) != S_IFREG || 1 != st.st_nlink) { - close(fd); - errx(EX_CANTCREAT, "%s: invalid file", corefname); - } - } +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])); +} - if (opt->verbose) { - printf("Dumping core "); - if (opt->debug) { - printf("(%s%s%s", - opt->sparse ? "sparse" : "normal", - opt->compress ? ", compressed" : "", -#ifdef CONFIG_REFSC - opt->scfileref ? ", scfilerefs" : -#endif - ""); - 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; +#endif /* CONFIG_GCORE_FREF */ - /* - * The traditional way to capture a consistent core dump is to - * suspend the process while processing 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 a corpse is an error */ - - if (!opt->suspend && !opt->corpse) { - /* try a corpse dump, else dump the live process */ - badcorpse_is_fatal = 0; - } else if (opt->suspend) { - trycorpse = opt->corpse; - /* if suspended anyway, ignore corpse-creation errors */ - badcorpse_is_fatal = 0; - } +#ifdef CONFIG_GCORE_MAP - if (opt->suspend) - task_suspend(task); - - if (trycorpse) { - /* - * Create a corpse from the image before dumping it - */ - mach_port_t corpse = MACH_PORT_NULL; - ret = task_generate_corpse(task, &corpse); - switch (ret) { - case KERN_SUCCESS: - if (opt->debug) - printf("corpse generated on port %x, task %x\n", - corpse, task); - ecode = coredump(corpse, fd); - mach_port_deallocate(mach_task_self(), corpse); - break; - default: - if (badcorpse_is_fatal || opt->debug) { - 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); - break; - } - } else { - /* - * Examine the task directly - */ - ecode = coredump(task, fd); - } +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])); +} -out: - if (opt->suspend) - task_resume(task); - - if (0 != ecode && !opt->preserve && !opt->dryrun) { - /* - * try not to leave a half-written mess occupying - * blocks on the filesystem - */ - ftruncate(fd, 0); - unlink(corefname); - } - if (-1 == close(fd)) - ecode = EX_OSERR; - if (ecode) - errx(ecode, "failed to dump core for pid %d", pid); - free(corefname); +#endif - return 0; +#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] incore outcore\n", pgm, argv[1]); + }); + + char *searchpath = NULL; + bool zf = false; + + int c; + optind = 2; + while ((c = getopt(argc, argv, "dzvL:")) != -1) { + switch (c) { + /* + * likely documented options + */ + case 'L': + searchpath = strdup(optarg); + break; + case 'z': + zf = true; + break; + case 'v': + options.verbose++; + 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/gcore.tproj/options.h b/gcore.tproj/options.h index 051be4a..57e7768 100644 --- a/gcore.tproj/options.h +++ b/gcore.tproj/options.h @@ -12,11 +12,14 @@ #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_REFSC 1 /* create shared cache reference segment (-R) */ -//#define CONFIG_PURGABLE 1 /* record purgability */ -//#define CONFIG_SUBMAP 1 /* include submaps (debugging) */ +//#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 */ @@ -25,23 +28,34 @@ #endif struct options { - int corpse; // dump from a corpse + 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 - int debug; // internal debugging: options accumulate. very noisy. - int dryrun; // do all the work, but throw the dump away - int sparse; // use dyld's data about dylibs to reduce the size of the dump - off_t sizebound; // maximum size of the dump - int coreinfo; // create a (currently experimental) 'coreinfo' section -#ifdef CONFIG_REFSC - int scfileref; // create "reference" segments that point at the shared cache +#ifdef CONFIG_DEBUG + int debug; // internal debugging: options accumulate. noisy. #endif - int compress; // compress the dump - size_t chunksize; // max size of the compressed segment + 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 }; 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/gcore.tproj/region.h b/gcore.tproj/region.h index a4f0afb..77853b2 100644 --- a/gcore.tproj/region.h +++ b/gcore.tproj/region.h @@ -10,36 +10,68 @@ #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; - mach_vm_offset_t r_address; - mach_vm_offset_t r_size; + struct vm_range r_range; -#define _R_ADDR(r) ((r)->r_address) -#define _R_SIZE(r) ((r)->r_size) -#define R_SETADDR(r, a) ((r)->r_address = (a)) -#define R_SETSIZE(r, z) ((r)->r_size = (z)) +#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; -#ifdef CONFIG_PURGABLE int r_purgable; -#endif + #ifdef CONFIG_SUBMAP int r_depth; #endif - boolean_t - r_insharedregion, - r_inzfodregion, - r_incommregion; + boolean_t r_insharedregion, r_inzfodregion, r_incommregion; -#ifdef CONFIG_REFSC /* * 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 @@ -47,9 +79,9 @@ struct region { */ struct { const struct libent *fr_libent; + const char *fr_pathname; off_t fr_offset; } *r_fileref; -#endif /* * These (optional) fields are filled in after we parse the information @@ -64,7 +96,6 @@ struct region { 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); } @@ -95,9 +126,7 @@ struct regionop { #define ROP_DELETE(r) (((r)->r_op->rop_delete)(r)) extern const struct regionop vanilla_ops, sparse_ops, zfod_ops; -#ifdef CONFIG_REFSC extern const struct regionop fileref_ops; -#endif struct regionhead; diff --git a/gcore.tproj/sparse.c b/gcore.tproj/sparse.c index 26d4dc7..ad5d917 100644 --- a/gcore.tproj/sparse.c +++ b/gcore.tproj/sparse.c @@ -49,7 +49,7 @@ new_subregion( S_SETSIZE(s, vmsize); s->s_libent = le; - s->s_isfileref = false; + s->s_isuuidref = false; return s; } @@ -63,11 +63,17 @@ del_subregion(struct subregion *s) static walk_return_t clean_subregions(struct region *r) { - 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; + 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; } @@ -112,12 +118,11 @@ add_subregions_for_libent( subregionlisthead_t *srlh, const struct region *r, const native_mach_header_t *mh, - const mach_vm_offset_t mh_taddr, + 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 scoffset = MACH_VM_MAX_ADDRESS; - + mach_vm_offset_t objoff = le->le_objoff; for (unsigned n = 0; n < mh->ncmds; n++) { const native_segment_command_t *sc; @@ -126,59 +131,41 @@ add_subregions_for_libent( case NATIVE_LC_SEGMENT: sc = (const void *)lc; - char scsegname[17]; - strlcpy(scsegname, sc->segname, sizeof (scsegname)); - - if (0 == sc->vmaddr && - strcmp(scsegname, SEG_PAGEZERO) == 0) + if (0 == sc->vmaddr && strcmp(sc->segname, SEG_PAGEZERO) == 0) break; - - /* -Depends- on finding a __TEXT segment first! */ - - if (MACH_VM_MAX_ADDRESS == scoffset) { - if (strcmp(scsegname, SEG_TEXT) == 0) - scoffset = mh_taddr - sc->vmaddr; - else { - /* - * Treat as error - don't want a partial description - * to cause something to be omitted from the dump. - */ - printr(r, "expected %s segment, found %s segment\n", SEG_TEXT, scsegname); - return WALK_ERROR; - } - } + 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 < sc->vmaddr + scoffset) + if (R_ENDADDR(r) - 1 < lo) break; - if (sc->vmaddr + scoffset + sc->vmsize - 1 < R_ADDR(r)) + 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. */ - mach_vm_offset_t loaddr = sc->vmaddr + scoffset; - mach_vm_offset_t hiaddr = loaddr + sc->vmsize; - if (loaddr < R_ADDR(r)) - loaddr = R_ADDR(r); - if (hiaddr > R_ENDADDR(r)) - hiaddr = R_ENDADDR(r); + 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(loaddr, hiaddr - loaddr, sc, le); + 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 (opt->debug > 3) { + if (OPTIONS_DEBUG(opt, 2)) { hsize_str_t hstr; - printr(r, "subregion %llx-%llx %7s %12s\t%s [%x/%x off %zd for %zd nsects %u flags %x]\n", + printr(r, "subregion %llx-%llx %7s %12s\t%s [%s off %zd for %zd nsects %u flags %x]\n", S_ADDR(s), S_ENDADDR(s), str_hsize(hstr, S_SIZE(s)), - scsegname, + sc->segname, S_FILENAME(s), - sc->initprot, sc->maxprot, + str_prot(sc->initprot), sc->fileoff, sc->filesize, sc->nsects, sc->flags); } @@ -220,7 +207,7 @@ eliminate_duplicate_subregions(struct region *r) i++; continue; } - if (opt->debug) + 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 */ @@ -237,24 +224,24 @@ eliminate_duplicate_subregions(struct region *r) 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 mhaddr, const uuid_t uuid, __unused const char *path) { + _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); - /* - * Core dumps conventionally contain the whole executable, but we're trying - * to elide everything that can't be found in a file elsewhere. - */ -#if 0 - if (MH_EXECUTE == le->le_mh->filetype) - return; // cause the whole a.out to be emitted -#endif - retval = add_subregions_for_libent(&srlhead, r, le->le_mh, le->le_mhaddr, le); + 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) @@ -289,48 +276,67 @@ decorate_memory_region(struct region *r, void *arg) eliminate_duplicate_subregions(r); - const struct libent *lesc = NULL; /* libent ref for shared cache */ - if (r->r_insharedregion) { - uuid_t uusc; - if (get_sc_uuid(dpi, uusc)) { - lesc = libent_lookup_byuuid(uusc); - assert(NULL == lesc->le_mh && 0 == lesc->le_mhaddr); - } - } - - /* - * 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 writable is trivially disqualified - */ - if (s->s_segcmd.initprot & VM_PROT_WRITE) - continue; - /* - * As long as there's a filename, __TEXT and __LINKEDIT - * end up as a file reference. - * - * __LINKEDIT is more complicated: the segment commands point - * at a unified segment in the shared cache mapping. - * Ditto for __UNICODE(?) - */ - if (issubregiontype(s, SEG_TEXT)) { - /* fall through */; - } else if (issubregiontype(s, SEG_LINKEDIT)) { - if (r->r_insharedregion) - s->s_libent = lesc; - } else if (issubregiontype(s, "__UNICODE")) { - if (r->r_insharedregion) - s->s_libent = lesc; - } else - continue; - - if (s->s_libent) - s->s_isfileref = true; - } - } + 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: @@ -347,8 +353,7 @@ done: * Strip region of all decoration * * Invoked (on every region!) after an error during the initial - * 'decoration' phase to discard to discard potentially incomplete - * information. + * 'decoration' phase to discard potentially incomplete information. */ walk_return_t undecorate_memory_region(struct region *r, __unused void *arg) @@ -371,36 +376,44 @@ sparse_region_optimization(struct region *r, __unused void *arg) * 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); } -#ifdef CONFIG_REFSC - if (r->r_fileref) { - /* - * Already have a fileref for the whole region: almost - * certainly a more compact representation - keep - * it that way. - */ - assert(&fileref_ops == r->r_op); - return clean_subregions(r); - } -#endif - - 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 (opt->debug) { - hsize_str_t hstr; - printr(r, "not referenced in dyld info => " - "eliding %s range in shared region\n", - str_hsize(hstr, R_SIZE(r))); - } - return WALK_DELETE_REGION; - } + 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) { /* @@ -413,7 +426,7 @@ sparse_region_optimization(struct region *r, __unused void *arg) struct subregion *s0 = r->r_subregions[i-1]; struct subregion *s1 = r->r_subregions[i]; - if (s0->s_isfileref) { + if (s0->s_isuuidref) { i++; continue; /* => destined to be a fileref */ } @@ -424,11 +437,9 @@ sparse_region_optimization(struct region *r, __unused void *arg) if (S_ENDADDR(s0) == S_ADDR(s1)) { /* directly adjacent subregions */ -#if 1 - if (opt->debug) + 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)); -#endif S_SETSIZE(s0, S_ENDADDR(s1) - S_ADDR(s0)); elide_subregion(r, i); continue; @@ -445,11 +456,9 @@ sparse_region_optimization(struct region *r, __unused void *arg) if (pfn[0] == pfn[1] && pfn[0] == endpfn[0] && pfn[0] == endpfn[1]) { /* two small subregions share a host page */ -#if 1 - if (opt->debug) + 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)); -#endif S_SETSIZE(s0, S_ENDADDR(s1) - S_ADDR(s0)); elide_subregion(r, i); continue; @@ -457,11 +466,9 @@ sparse_region_optimization(struct region *r, __unused void *arg) if (pfn[1] == 1 + endpfn[0]) { /* subregions are pagewise-adjacent: bigger chunks to compress */ -#if 1 - if (opt->debug) + 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)); -#endif S_SETSIZE(s0, S_ENDADDR(s1) - S_ADDR(s0)); elide_subregion(r, i); continue; @@ -471,6 +478,17 @@ sparse_region_optimization(struct region *r, __unused void *arg) } } + 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; diff --git a/gcore.tproj/sparse.h b/gcore.tproj/sparse.h index 1880bc5..cf920ff 100644 --- a/gcore.tproj/sparse.h +++ b/gcore.tproj/sparse.h @@ -9,27 +9,28 @@ #define _SPARSE_H struct subregion { - mach_vm_offset_t s_address; - mach_vm_offset_t s_size; + struct vm_range s_range; native_segment_command_t s_segcmd; const struct libent *s_libent; - bool s_isfileref; + bool s_isuuidref; }; +#define S_RANGE(s) (&(s)->s_range) + static __inline void S_SETADDR(struct subregion *s, mach_vm_offset_t a) { - s->s_address = a; + V_SETADDR(S_RANGE(s), a); } static __inline void S_SETSIZE(struct subregion *s, mach_vm_offset_t sz) { - s->s_size = sz; + V_SETSIZE(S_RANGE(s), sz); } static __inline const mach_vm_offset_t S_ADDR(const struct subregion *s) { - return s->s_address; + return V_ADDR(S_RANGE(s)); } static __inline const mach_vm_offset_t S_SIZE(const struct subregion *s) { - return s->s_size; + return V_SIZE(S_RANGE(s)); } static __inline const mach_vm_offset_t S_ENDADDR(const struct subregion *s) { diff --git a/gcore.tproj/threads.c b/gcore.tproj/threads.c index 9903803..b1b3d6f 100644 --- a/gcore.tproj/threads.c +++ b/gcore.tproj/threads.c @@ -14,6 +14,7 @@ #include #include #include +#include typedef struct { int flavor; @@ -73,6 +74,8 @@ dump_thread_state(native_mach_header_t *mh, struct thread_command *tc, mach_port 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/gcore.tproj/utils.c b/gcore.tproj/utils.c index 2c90967..f0edcf8 100644 --- a/gcore.tproj/utils.c +++ b/gcore.tproj/utils.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Apple Inc. All rights reserved. + * Copyright (c) 2016 Apple Inc. All rights reserved. */ #include "options.h" @@ -13,6 +13,7 @@ #include #include #include +#include void err_mach(kern_return_t kr, const struct region *r, const char *fmt, ...) @@ -22,12 +23,12 @@ err_mach(kern_return_t kr, const struct region *r, const char *fmt, ...) if (0 != kr) printf("%s: ", pgm); if (NULL != r) - printf("%llx-%llx ", R_ADDR(r), R_ENDADDR(r)); + printf("%016llx-%016llx ", R_ADDR(r), R_ENDADDR(r)); vprintf(fmt, ap); va_end(ap); if (0 != kr) { - printf(": %s (%x)", mach_error_string(kr), 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) */ @@ -41,14 +42,29 @@ err_mach(kern_return_t kr, const struct region *r, const char *fmt, ...) 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); - if (NULL != r) - printf("%llx-%llx ", R_ADDR(r), R_ENDADDR(r)); - vfprintf(stdout, fmt, ap); + vprintvr(R_RANGE(r), fmt, ap); va_end(ap); } @@ -63,9 +79,291 @@ str_hsize(hsize_str_t hstr, uint64_t size) 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 elipsis and + * If the string gets too long, then add an ellipsis and * stop concatenating it. */ char * @@ -89,3 +387,35 @@ strconcat(const char *s0, const char *s1, size_t maxlen) } 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/gcore.tproj/utils.h b/gcore.tproj/utils.h index 890f837..37eda58 100644 --- a/gcore.tproj/utils.h +++ b/gcore.tproj/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Apple Inc. All rights reserved. + * Copyright (c) 2016 Apple Inc. All rights reserved. */ #include @@ -9,20 +9,34 @@ #include #include #include +#include #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 *r, const char *fmt, ...) __printflike(3, 4); -extern void printr(const struct region *r, const char *fmt, ...) __printflike(2, 3); +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/gcore.tproj/vanilla.c b/gcore.tproj/vanilla.c index 61f9c09..2253bff 100644 --- a/gcore.tproj/vanilla.c +++ b/gcore.tproj/vanilla.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -32,8 +33,13 @@ #include -walk_return_t -vanilla_region_optimization(struct region *r, __unused void *arg) +/* + * (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)); @@ -41,7 +47,7 @@ vanilla_region_optimization(struct region *r, __unused void *arg) * Elide unreadable regions */ if ((r->r_info.max_protection & VM_PROT_READ) != VM_PROT_READ) { - if (opt->debug) + if (OPTIONS_DEBUG(opt, 2)) printr(r, "eliding unreadable region\n"); return WALK_DELETE_REGION; } @@ -50,7 +56,7 @@ vanilla_region_optimization(struct region *r, __unused void *arg) * Elide submaps (here for debugging purposes?) */ if (r->r_info.is_submap) { - if (opt->debug) + if (OPTIONS_DEBUG(opt)) printr(r, "eliding submap\n"); return WALK_DELETE_REGION; } @@ -61,26 +67,47 @@ vanilla_region_optimization(struct region *r, __unused void *arg) 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 (opt->debug) { + if (OPTIONS_DEBUG(opt, 2)) { hsize_str_t hstr; - printr(r, "eliding %s - guard\n", - str_hsize(hstr, R_SIZE(r))); + 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.) */ -static void +void validate_core_header(const native_mach_header_t *mh, off_t corefilesize) { - if (opt->debug) - printf("Core file: mh %p ncmds %u sizeofcmds %u\n", - mh, mh->ncmds, mh->sizeofcmds); + 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++) { @@ -90,43 +117,41 @@ validate_core_header(const native_mach_header_t *mh, off_t corefilesize) lc, mh, (uintptr_t)mh + mh->sizeofcmds); abort(); } - if (opt->debug) - printf("lc %p cmd %u cmdsize %u ", lc, lc->cmd, lc->cmdsize); - - const native_segment_command_t *sc; - const struct proto_coreinfo_command *cic; - const struct proto_fileref_command *frc; - const struct thread_command *tc; + 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: - sc = (const void *)lc; - if (opt->debug) { - printf("%8s: mem %llx-%llx file %lld-%lld %x/%x flags %x\n", + 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, - sc->initprot, sc->maxprot, sc->flags); + 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(); } - if ((off_t)sc->fileoff > corefilesize || - (off_t)sc->fileoff + (off_t)sc->filesize > corefilesize) { + 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: - cic = (const void *)lc; - if (opt->debug) { + } + 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", @@ -138,21 +163,27 @@ validate_core_header(const native_mach_header_t *mh, off_t corefilesize) abort(); } break; - - case proto_LC_FILEREF: - frc = (const void *)lc; + } + case proto_LC_FILEREF: { + const struct proto_fileref_command *frc = (const void *)lc; const char *nm = frc->filename.offset + (char *)lc; - if (opt->debug) { - uuid_string_t uustr; - uuid_unparse_lower(frc->uuid, uustr); - printf("%8s: mem %llx-%llx file %lld-%lld %x/%x '%s' %.12s..\n", - "FILEREF", - frc->vmaddr, - frc->vmaddr + frc->vmsize, + 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, - frc->initprot, frc->maxprot, nm, uustr); + 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) { @@ -160,20 +191,45 @@ validate_core_header(const native_mach_header_t *mh, off_t corefilesize) abort(); } break; - - case LC_THREAD: - tc = (const void *)lc; - if (opt->debug) + } + 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 (opt->debug) { + if (OPTIONS_DEBUG(opt, 2)) { printf(" flavor %u count %u\n", flavor, count); if (count) { - boolean_t nl = false; + bool nl = false; for (unsigned k = 0; k < count; k++) { if (0 == (k & 7)) printf(" [%3u] ", k); @@ -196,9 +252,9 @@ validate_core_header(const native_mach_header_t *mh, off_t corefilesize) } } while ((caddr_t) wbuf < (caddr_t)tc + tc->cmdsize); break; - + } default: - warnx("unknown cmd %u in header\n", lc->cmd); + warnx("unknown cmd %u in header", lc->cmd); abort(); } if (lc->cmdsize) @@ -206,6 +262,12 @@ validate_core_header(const native_mach_header_t *mh, off_t corefilesize) 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(); + } } /* @@ -217,12 +279,16 @@ validate_core_header(const native_mach_header_t *mh, off_t corefilesize) * * - 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) * - * - prototype_LC_COREINFO (experimental), pointing at dyld (10.12 onwards) + * - proto_LC_COREINFO (experimental), pointing at dyld (10.12 onwards) * - * - prototype_LC_FILEREF (experimental) pointing at memory - * content to be mapped in from another file at various offsets + * - 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 * @@ -255,10 +321,16 @@ coredump_write( thread_count = 0; } - if (opt->debug) { - print_memory_region_header(); - walk_region_list(rhead, region_print_memory, NULL); - } + 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() + @@ -266,7 +338,7 @@ coredump_write( ssda.ssd_zfod.headersize + ssda.ssd_vanilla.headersize + ssda.ssd_sparse.headersize; - if (opt->coreinfo) + if (opt->extended) headersize += sizeof (struct proto_coreinfo_command); void *header = calloc(1, headersize); @@ -276,17 +348,17 @@ coredump_write( native_mach_header_t *mh = make_corefile_mach_header(header); struct load_command *lc = (void *)(mh + 1); - if (opt->coreinfo) { + 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->debug) { + 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("Dumping %lu memory segments", segment_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", @@ -294,19 +366,20 @@ coredump_write( printf("\n"); } - vm_size_t pagesize = ((vm_offset_t)1 << pageshift_host); - vm_offset_t pagemask = (vm_offset_t)(pagesize - 1); + 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_foffset = ((vm_offset_t)headersize + pagemask) & ~pagemask, + .wsd_nocache = false, + .wsd_foffset = ((mach_vm_offset_t)headersize + pagemask) & ~pagemask, .wsd_nwritten = 0, }; - int ecode = 0; + int ecode = 0; if (0 != walk_region_list(rhead, region_write_memory, &wsda)) ecode = EX_IOERR; @@ -324,9 +397,11 @@ coredump_write( * Even if we've run out of space, try our best to * write out the header. */ - if (-1 == pwrite(fd, header, headersize, 0)) + if (0 != bounded_pwrite(fd, header, headersize, 0, &wsda.wsd_nocache, NULL)) ecode = EX_IOERR; - else + 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); @@ -349,17 +424,137 @@ coredump_write( 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) +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 = get_task_dyld_info(task); - if (dpi) { - get_sc_uuid(dpi, 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 @@ -377,60 +572,74 @@ coredump(task_t task, int fd) goto done; } - if (opt->debug) + if (OPTIONS_DEBUG(opt, 1)) printf("Optimizing dump content\n"); - walk_region_list(rhead, vanilla_region_optimization, NULL); - - if (dpi) { - if (opt->coreinfo || opt->sparse) { - /* - * Snapshot dyld's info .. - */ - if (!libent_build_nametable(task, dpi)) - warnx("error parsing dyld data => ignored"); - else { - if (opt->coreinfo) { - /* - * 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; - } - } - if (opt->sparse) { - /* - * Use dyld's view of what's being used in the address - * space to shrink the dump. - */ - if (0 == walk_region_list(rhead, decorate_memory_region, (void *)dpi)) { - if (opt->debug) - printf("Performing 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); - } - - if (opt->debug) + 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; } -#ifdef CONFIG_REFSC - struct find_shared_cache_args { task_t fsc_task; vm_object_id_t fsc_object_id; @@ -458,10 +667,9 @@ find_shared_cache(struct region *r, void *arg) if (r->r_pageinfo.offset != 0) return WALK_CONTINUE; /* must map beginning of file */ - if (opt->debug) { + if (OPTIONS_DEBUG(opt, 1)) { hsize_str_t hstr; - printf("Examining shared cache candidate %llx-%llx (%s)\n", - R_ADDR(r), R_ENDADDR(r), str_hsize(hstr, R_SIZE(r))); + printr(r, "examining %s shared cache candidate\n", str_hsize(hstr, R_SIZE(r))); } struct copied_dyld_cache_header *ch; @@ -469,20 +677,20 @@ find_shared_cache(struct region *r, void *arg) 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, "mapping candidate shared region"); + 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 (opt->debug > 2) { + 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 (opt->debug) + 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 */ @@ -495,7 +703,7 @@ find_shared_cache(struct region *r, void *arg) } mach_vm_deallocate(mach_task_self(), (vm_offset_t)ch, chlen); if (fsc->fsc_object_id) { - if (opt->debug) { + if (OPTIONS_DEBUG(opt, 3)) { uuid_string_t uu; uuid_unparse_lower(fsc->fsc_uuid, uu); printf("Shared cache objid %llx uuid %s\n", @@ -506,23 +714,23 @@ find_shared_cache(struct region *r, void *arg) return WALK_CONTINUE; } -static boolean_t +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 (opt->debug) - printr(r, "%s - %s\n", + 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, (size_t)R_SIZE(r), PROT_READ, MAP_PRIVATE, fsc->fsc_fd, r->r_pageinfo.offset); + void *file = mmap(NULL, R_SIZEOF(r), PROT_READ, MAP_PRIVATE, fsc->fsc_fd, r->r_pageinfo.offset); if ((void *)-1L == file) { - if (opt->debug) + if (OPTIONS_DEBUG(opt, 1)) printr(r, "mmap %s - %s\n", fsc->fsc_le->le_filename, strerror(errno)); return false; } - madvise(file, (size_t)R_SIZE(r), MADV_SEQUENTIAL); + madvise(file, R_SIZEOF(r), MADV_SEQUENTIAL); vm_offset_t data = 0; mach_msg_type_number_t data_count; @@ -530,7 +738,7 @@ compare_region_with_shared_cache(const struct region *r, struct find_shared_cach if (KERN_SUCCESS != kr || data_count < R_SIZE(r)) { err_mach(kr, r, "mach_vm_read()"); - munmap(file, (size_t)R_SIZE(r)); + munmap(file, R_SIZEOF(r)); return false; } @@ -549,7 +757,7 @@ compare_region_with_shared_cache(const struct region *r, struct find_shared_cach * Check what's really mapped there and reduce the size accordingly. */ if (!is_actual_size(fsc->fsc_task, r, &cmpsize)) { - if (opt->debug) + if (OPTIONS_DEBUG(opt, 3)) printr(r, "narrowing the comparison (%llu " "-> %llu)\n", R_SIZE(r), cmpsize); } @@ -558,7 +766,7 @@ compare_region_with_shared_cache(const struct region *r, struct find_shared_cach mach_vm_behavior_set(mach_task_self(), data, data_count, VM_BEHAVIOR_SEQUENTIAL); - const boolean_t thesame = memcmp(file, (void *)data, (size_t)cmpsize) == 0; + const bool thesame = memcmp(file, (void *)data, (size_t)cmpsize) == 0; #if 0 if (!thesame) { int diffcount = 0; @@ -577,9 +785,9 @@ compare_region_with_shared_cache(const struct region *r, struct find_shared_cach } #endif mach_vm_deallocate(mach_task_self(), data, data_count); - munmap(file, (size_t)R_SIZE(r)); + munmap(file, R_SIZEOF(r)); - if (!thesame && opt->debug) + if (!thesame && OPTIONS_DEBUG(opt, 3)) printr(r, "mapped file (%s) region is modified\n", fsc->fsc_le->le_filename); return thesame; } @@ -598,47 +806,38 @@ label_shared_cache(struct region *r, void *arg) return WALK_CONTINUE; } if (((r->r_info.protection | r->r_info.max_protection) & VM_PROT_WRITE) != 0) { - /* writable, but was it written? */ - if (r->r_info.pages_dirtied + r->r_info.pages_swapped_out != 0) - return WALK_CONTINUE; // a heuristic .. + /* 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; } - } - - if (opt->debug > 2) { - /* this validation is -really- expensive */ - if (!compare_region_with_shared_cache(r, fsc)) - printr(r, "WARNING: region should match, but doesn't\n"); - } + } /* * This mapped file segment will be represented as a reference - * to the file, rather than as a copy of the file. + * to the file, rather than as a copy of the mapped file. */ - const struct libent *le = libent_lookup_byuuid(fsc->fsc_uuid); - r->r_fileref = calloc(1, sizeof (*r->r_fileref)); - if (r->r_fileref) { - r->r_fileref->fr_libent = le; - if (r->r_fileref->fr_libent) { - r->r_fileref->fr_offset = r->r_pageinfo.offset; - r->r_op = &fileref_ops; - } else { - free(r->r_fileref); - r->r_fileref = NULL; - } - } - return WALK_CONTINUE; + addfileref(r, libent_lookup_byuuid(fsc->fsc_uuid), NULL); + return WALK_CONTINUE; } -#endif /* CONFIG_REFSC */ struct regionhead * coredump_prepare(task_t task, uuid_t sc_uuid) { struct regionhead *rhead = build_region_list(task); - if (opt->debug) { + if (OPTIONS_DEBUG(opt, 2)) { + printf("Region list built\n"); print_memory_region_header(); walk_region_list(rhead, region_print_memory, NULL); } @@ -653,24 +852,27 @@ coredump_prepare(task_t task, uuid_t sc_uuid) const struct libent *le; if (NULL != nm) - le = libent_insert(nm, sc_uuid, 0, NULL); + le = libent_insert(nm, sc_uuid, 0, NULL, NULL, 0); else { - le = libent_insert("(shared cache)", sc_uuid, 0, NULL); + libent_insert("(anonymous shared cache)", sc_uuid, 0, NULL, NULL, 0); if (opt->verbose){ - uuid_string_t uustr; - uuid_unparse_lower(sc_uuid, uustr); - printf("Shared cache UUID: %s, but no filename => ignored\n", uustr); - return rhead; + 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; } -#ifdef CONFIG_REFSC - if (opt->scfileref) { + 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 (opt->debug) { + 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); @@ -694,15 +896,16 @@ coredump_prepare(task_t task, uuid_t sc_uuid) if (opt->verbose) printf("Referenced %s\n", nm); fsca.fsc_le = le; - fsca.fsc_fd = open(fsca.fsc_le->le_filename, O_RDONLY); - - walk_region_list(rhead, label_shared_cache, &fsca); - - close(fsca.fsc_fd); + 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); } } -#endif /* CONFIG_REFSC */ return rhead; } diff --git a/gcore.tproj/vanilla.h b/gcore.tproj/vanilla.h index ac29e85..721c653 100644 --- a/gcore.tproj/vanilla.h +++ b/gcore.tproj/vanilla.h @@ -7,9 +7,10 @@ #ifndef _VANILLA_H #define _VANILLA_H -extern walk_region_cbfn_t vanilla_region_optimization; +struct proc_bsdinfo; -extern int coredump(task_t, int); +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); diff --git a/gcore.tproj/vm.c b/gcore.tproj/vm.c index fdd6814..22b0efe 100644 --- a/gcore.tproj/vm.c +++ b/gcore.tproj/vm.c @@ -15,7 +15,6 @@ #include #include #include - #include /* @@ -70,9 +69,7 @@ new_region(mach_vm_offset_t vmaddr, mach_vm_size_t vmsize, const vm_region_subma R_SETADDR(r, vmaddr); R_SETSIZE(r, vmsize); r->r_info = *infop; -#ifdef CONFIG_PURGABLE r->r_purgable = VM_PURGABLE_DENY; -#endif 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); @@ -84,7 +81,6 @@ new_region(mach_vm_offset_t vmaddr, mach_vm_size_t vmsize, const vm_region_subma return r; } -#ifdef CONFIG_REFSC void del_fileref_region(struct region *r) { @@ -95,16 +91,13 @@ del_fileref_region(struct region *r) poison(r, 0xdeadbeeb, sizeof (*r)); free(r); } -#endif /* CONFIG_REFSC */ void del_zfod_region(struct region *r) { assert(&zfod_ops == r->r_op); assert(r->r_inzfodregion && 0 == r->r_nsubregions); -#ifdef CONFIG_REFSC assert(NULL == r->r_fileref); -#endif poison(r, 0xdeadbeed, sizeof (*r)); free(r); } @@ -114,9 +107,7 @@ del_vanilla_region(struct region *r) { assert(&vanilla_ops == r->r_op); assert(!r->r_inzfodregion && 0 == r->r_nsubregions); -#ifdef CONFIG_REFSC assert(NULL == r->r_fileref); -#endif poison(r, 0xdeadbeef, sizeof (*r)); free(r); } @@ -174,9 +165,10 @@ walk_regions(task_t task, struct regionhead *rhead) mach_vm_offset_t vm_addr = MACH_VM_MIN_ADDRESS; natural_t depth = 0; - if (opt->debug > 3) + 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; @@ -194,7 +186,7 @@ walk_regions(task_t task, struct regionhead *rhead) goto bad; } - if (opt->debug > 3) { + if (OPTIONS_DEBUG(opt, 3)) { struct region *d = new_region(vm_addr, vm_size, &info); ROP_PRINT(d); ROP_DELETE(d); @@ -227,14 +219,13 @@ walk_regions(task_t task, struct regionhead *rhead) if (KERN_SUCCESS != ret) err_mach(ret, r, "getting pageinfo at %llx", R_ADDR(r)); -#ifdef CONFIG_PURGABLE /* 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; -#endif - STAILQ_INSERT_TAIL(rhead, r, r_linkage); + + STAILQ_INSERT_TAIL(rhead, r, r_linkage); vm_addr += vm_size; } @@ -307,7 +298,7 @@ setpageshift(void) pshift++; pageshift_host = pshift; } - if (opt->debug) + if (OPTIONS_DEBUG(opt, 3)) printf("host page size: %lu\n", 1ul << pageshift_host); if (0 == pageshift_app) { @@ -317,58 +308,10 @@ setpageshift(void) pshift++; pageshift_app = pshift; } - if (opt->debug && pageshift_app != pageshift_host) + if (OPTIONS_DEBUG(opt, 3) && pageshift_app != pageshift_host) printf("app page size: %lu\n", 1ul << pageshift_app); } -static const char * -strshared(const int sm) -{ - switch (sm) { - case SM_COW: - return "cow"; - case SM_PRIVATE: - return "priv"; - case SM_EMPTY: - return "empty"; - case SM_SHARED: - return "shr"; - case SM_TRUESHARED: - return "true_shr"; - case SM_PRIVATE_ALIASED: - return "priv_alias"; - case SM_SHARED_ALIASED: - return "shr_alias"; - case SM_LARGE_PAGE: - return "large_pg"; - default: - return "share?"; - } -} - -typedef char prot_str_t[9]; /* rwxNCWT& */ - -static const char * -str_prot(prot_str_t pstr, const vm_prot_t prot) -{ - snprintf(pstr, sizeof (prot_str_t), "%c%c%c", - prot & VM_PROT_READ ? 'r' : '-', - prot & VM_PROT_WRITE ? 'w' : '-', - prot & VM_PROT_EXECUTE ? 'x' : '-'); - /* for completeness */ - if (prot & VM_PROT_NO_CHANGE) - strlcat(pstr, "N", sizeof (prot_str_t)); - if (prot & VM_PROT_COPY) - strlcat(pstr, "C", sizeof (prot_str_t)); - if (prot & VM_PROT_WANTS_COPY) - strlcat(pstr, "W", sizeof (prot_str_t)); - if (prot & 0x20) - strlcat(pstr, "T", sizeof (prot_str_t)); - if (prot & VM_PROT_IS_MASK) - strlcat(pstr, "&", sizeof (prot_str_t)); - return pstr; -} - void print_memory_region_header(void) { @@ -387,10 +330,8 @@ print_memory_region_header(void) static __inline char region_type(const struct region *r) { -#ifdef CONFIG_REFSC if (r->r_fileref) return 'f'; -#endif if (r->r_inzfodregion) return 'z'; if (r->r_incommregion) @@ -403,14 +344,14 @@ region_type(const struct region *r) void print_memory_region(const struct region *r) { - prot_str_t pstr, pstr_max; 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(pstr, r->r_info.protection), - str_prot(pstr_max, r->r_info.max_protection), + str_prot(r->r_info.protection), + str_prot(r->r_info.max_protection), r->r_info.object_id, r->r_pageinfo.object_id ); @@ -418,7 +359,7 @@ print_memory_region(const struct region *r) r->r_info.external_pager ? r->r_pageinfo.offset : r->r_info.offset, r->r_info.user_tag, - strshared(r->r_info.share_mode), + str_shared(r->r_info.share_mode), r->r_info.ref_count ); #ifdef CONFIG_SUBMAP @@ -431,12 +372,12 @@ print_memory_region(const struct region *r) r->r_info.pages_shared_now_private, r->r_info.pages_dirtied, r->r_info.external_pager ? "ext" : ""); -#if CONFIG_REFSC - if (r->r_fileref) + if (r->r_fileref) printf("\n %s at %lld ", - r->r_fileref->fr_libent->le_filename, + r->r_fileref->fr_pathname, r->r_fileref->fr_offset); -#endif + else + printf("%s", str_tagr(tstr, r)); printf("\n"); if (r->r_nsubregions) { printf(" %-33s %7s %12s\t%s\n", @@ -450,19 +391,8 @@ print_memory_region(const struct region *r) S_FILENAME(s)); } } - } else { - switch (r->r_info.user_tag) { - case VM_MEMORY_SHARED_PMAP: - printf("// VM_MEMORY_SHARED_PMAP"); - break; - case VM_MEMORY_UNSHARED_PMAP: - printf("// VM_MEMORY_UNSHARED_PMAP"); - break; - default: - printf("// is a submap"); - break; - } - printf("\n"); + } else { + printf("%5s %5s %5s %3s %s\n", "", "", "", "", str_tagr(tstr, r)); } } @@ -473,6 +403,13 @@ region_print_memory(struct region *r, __unused void *arg) 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 @@ -485,7 +422,7 @@ region_print_memory(struct region *r, __unused void *arg) * Figure out what the "non-faulting" size of the object is to * *host* page size resolution. */ -boolean_t +bool is_actual_size(const task_t task, const struct region *r, mach_vm_size_t *hostvmsize) { if (!r->r_info.external_pager || diff --git a/gcore.tproj/vm.h b/gcore.tproj/vm.h index b029bea..b912782 100644 --- a/gcore.tproj/vm.h +++ b/gcore.tproj/vm.h @@ -21,9 +21,7 @@ extern int pageshift_app; struct region; struct regionhead; -#ifdef CONFIG_REFSC extern void del_fileref_region(struct region *); -#endif extern void del_zfod_region(struct region *); extern void del_sparse_region(struct region *); extern void del_vanilla_region(struct region *); @@ -34,6 +32,7 @@ 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; @@ -42,7 +41,7 @@ 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 boolean_t is_actual_size(const task_t, const struct region *, mach_vm_size_t *); +extern bool is_actual_size(const task_t, const struct region *, mach_vm_size_t *); #endif #endif /* _VM_H */ diff --git a/getty.tproj/generate_plist.sh b/getty.tproj/generate_plist.sh index 2170bc9..ef42607 100644 --- a/getty.tproj/generate_plist.sh +++ b/getty.tproj/generate_plist.sh @@ -4,7 +4,7 @@ set -x cp "${SCRIPT_INPUT_FILE_0}" "${SCRIPT_OUTPUT_FILE_0}" case "$PLATFORM_NAME" in -iphone*|appletv*|watch*) +iphone*|appletv*|watch*|bridge*) ;; macosx) /usr/libexec/PlistBuddy -c "Add :Disabled bool true" "${SCRIPT_OUTPUT_FILE_0}" diff --git a/hostinfo.tproj/hostinfo.c b/hostinfo.tproj/hostinfo.c index 1993b30..1828583 100644 --- a/hostinfo.tproj/hostinfo.c +++ b/hostinfo.tproj/hostinfo.c @@ -54,7 +54,6 @@ main(int argc, char *argv[]) kern_return_t ret; unsigned int size, count; char *cpu_name, *cpu_subname; - int i; int mib[2]; size_t len; uint64_t memsize; @@ -99,6 +98,19 @@ main(int argc, char *argv[]) exit(EXIT_FAILURE); } + unsigned int cpu_count = 0; + unsigned int data_count = 0; + struct processor_basic_info *processor_basic_infop = NULL; + ret = host_processor_info(host, + PROCESSOR_BASIC_INFO, + &cpu_count, + (processor_info_array_t *)&processor_basic_infop, + &data_count); + if (ret != KERN_SUCCESS) { + mach_error(argv[0], ret); + exit(EXIT_FAILURE); + } + mib[0] = CTL_HW; mib[1] = HW_MEMSIZE; len = sizeof(memsize); @@ -125,8 +137,11 @@ main(int argc, char *argv[]) printf(" %s (%s)\n", cpu_name, cpu_subname); printf("Processor%s active:", (hi.avail_cpus > 1) ? "s" : ""); - for (i = 0; i < hi.avail_cpus; i++) - printf(" %d", i); + for (int i = 0; i < cpu_count; i++) { + if (processor_basic_infop[i].running) { + printf(" %d", i); + } + } printf("\n"); if (((float)memsize / (1024.0 * 1024.0)) >= 1024.0) diff --git a/iosim.tproj/iosim.c b/iosim.tproj/iosim.c index 51cf5b0..71ec671 100644 --- a/iosim.tproj/iosim.c +++ b/iosim.tproj/iosim.c @@ -249,7 +249,7 @@ void *io_routine(void *arg) io_thread_id = (int)arg; if (user_specified_file) - strncpy(test_filename, user_fname, MAX_FILENAME); + strlcpy(test_filename, user_fname, MAX_FILENAME); else snprintf(test_filename, MAX_FILENAME, "iosim-%d-%d", (int)getpid(), io_thread_id); diff --git a/lskq.tproj/common.h b/lskq.tproj/common.h index 84cb434..872f576 100644 --- a/lskq.tproj/common.h +++ b/lskq.tproj/common.h @@ -31,15 +31,21 @@ /* * bsd/sys/event.h */ -#define KN_ACTIVE 0x01 -#define KN_QUEUED 0x02 -#define KN_DISABLED 0x04 -#define KN_DROPPING 0x08 -#define KN_USEWAIT 0x10 -#define KN_ATTACHING 0x20 -#define KN_STAYQUEUED 0x40 -#define KN_DEFERDROP 0x80 -#define KN_TOUCH 0x100 +#define KN_ACTIVE 0x0001 +#define KN_QUEUED 0x0002 +#define KN_DISABLED 0x0004 +#define KN_DROPPING 0x0008 +#define KN_USEWAIT 0x0010 +#define KN_ATTACHING 0x0020 +#define KN_STAYACTIVE 0x0040 +#define KN_DEFERDELETE 0x0080 +#define KN_ATTACHED 0x0100 +#define KN_DISPATCH 0x0200 +#define KN_UDATA_SPECIFIC 0x0400 +#define KN_SUPPRESSED 0x0800 +#define KN_STOLENDROP 0x1000 +#define KN_REQVANISH 0x2000 +#define KN_VANISHED 0x4000 /* @@ -50,6 +56,8 @@ #define KQ_KEV32 0x08 #define KQ_KEV64 0x10 #define KQ_KEV_QOS 0x20 +#define KQ_WORKQ 0x40 +#define KQ_WORKLOOP 0x80 /* * bsd/sys/signal.h @@ -110,6 +118,9 @@ filt_strs[] = { "VM", "SOCK", "MEMSTATUS", + "EXCEPT", + "CHANNEL", + "WORKLOOP", }; /* @@ -125,6 +136,10 @@ fdtype_strs[] = { "KQUEUE", "PIPE", "FSEVENTS", + "ATALK", + "POLICY", + "CHANNEL", + "NEXUS", }; #endif /* _LSKQ_COMMON_H_ */ diff --git a/lskq.tproj/lskq.1 b/lskq.tproj/lskq.1 index ff79a92..19ae9bc 100644 --- a/lskq.tproj/lskq.1 +++ b/lskq.tproj/lskq.1 @@ -131,6 +131,16 @@ NOTE_TRIGGER NOTE_FFAND .It Sy o NOTE_FFOR +.Pp +.It EVFILT_WORKLOOP: +.It Sy t w +NOTE_WL_THREAD_REQUEST, NOTE_WL_SYNC_WAIT +.It Sy ! +NOTE_WL_SYNC_WAKE +.It Sy q +NOTE_WL_UPDATE_QOS +.It Sy O o +NOTE_WL_UPDATE_OWNER, NOTE_WL_DISCOVER_OWNER .El .It flags kevent generic flags bitmask. diff --git a/lskq.tproj/lskq.c b/lskq.tproj/lskq.c index 6a3c74d..3037fc5 100644 --- a/lskq.tproj/lskq.c +++ b/lskq.tproj/lskq.c @@ -21,6 +21,7 @@ * @APPLE_LICENSE_HEADER_END@ */ +#include #include #include #include @@ -32,8 +33,14 @@ #include #include #include +#include +#include #include +#define PRIVATE #include +#undef PRIVATE +#include +#include #include "common.h" @@ -136,14 +143,24 @@ fflags_build(struct kevent_extinfo *info, char *str, int len) break; } - case EVFILT_USER: { + case EVFILT_USER: snprintf(str, len, "%c%c%c ", (ff & NOTE_TRIGGER) ? 't' : '-', (ff & NOTE_FFAND) ? 'a' : '-', (ff & NOTE_FFOR) ? 'o' : '-' ); break; - } + + case EVFILT_WORKLOOP: + snprintf(str, len, "%c%c%c%c ", + (ff & NOTE_WL_THREAD_REQUEST) ? 't' : + (ff & NOTE_WL_SYNC_WAIT) ? 'w' : '-', + (ff & NOTE_WL_SYNC_WAKE) ? '!' : '-', + (ff & NOTE_WL_UPDATE_QOS) ? 'q' : '-', + (ff & NOTE_WL_UPDATE_OWNER) ? 'O' : + (ff & NOTE_WL_DISCOVER_OWNER) ? 'o' : '-' + ); + break; default: snprintf(str, len, ""); @@ -157,13 +174,32 @@ fflags_build(struct kevent_extinfo *info, char *str, int len) static inline int filter_is_fd_type(int filter) { - if (filter <= EVFILT_READ && filter >= EVFILT_VNODE) { + switch (filter) { + case EVFILT_VNODE ... EVFILT_READ: + case EVFILT_SOCK: + case EVFILT_NW_CHANNEL: return 1; - } else { + default: return 0; } } +static const char * +thread_qos_name(uint8_t th_qos) +{ + switch (th_qos) { + case 0: return "--"; + case 1: return "MT"; + case 2: return "BG"; + case 3: return "UT"; + case 4: return "DF"; + case 5: return "IN"; + case 6: return "UI"; + case 7: return "MG"; + default: return "??"; + } +} + /* * find index of fd in a list of fdinfo of length nfds */ @@ -252,6 +288,10 @@ print_ident(uint64_t ident, int16_t filter, int width) printf("%#*llx ", width, ident); break; + case EVFILT_WORKLOOP: + printf("%#*llx ", width, ident); + break; + default: printf("%*llu ", width, ident); break; @@ -260,57 +300,117 @@ print_ident(uint64_t ident, int16_t filter, int width) } static void -print_kqfd(int kqfd, int width) +print_kqid(int state, uint64_t kqid) { - if (kqfd == -1) { - printf("%*s ", width, "wq"); + if (state & KQ_WORKQ) { + printf("%18s ", "wq"); + } else if (state & KQ_WORKLOOP) { + printf("%#18" PRIx64 " ", kqid); } else { - printf("%*u ", width, kqfd); + printf("fd %15" PRIi64 " ", kqid); } } #define PROCNAME_WIDTH 20 static void -print_kq_info(int pid, const char *procname, int kqfd, int state) +print_kq_info(int pid, const char *procname, uint64_t kqid, int state) { if (raw) { printf("%5u ", pid); - print_kqfd(kqfd, 5); + print_kqid(state, kqid); printf("%#10x ", state); } else { char tmpstr[PROCNAME_WIDTH+1]; strlcpy(tmpstr, shorten_procname(procname, PROCNAME_WIDTH), PROCNAME_WIDTH+1); printf("%-*s ", PROCNAME_WIDTH, tmpstr); printf("%5u ", pid); - print_kqfd(kqfd, 5); + print_kqid(state, kqid); printf(" %c%c%c ", (state & KQ_SLEEP) ? 'k' : '-', (state & KQ_SEL) ? 's' : '-', - (state & KQ_KEV32) ? '3' : - (state & KQ_KEV64) ? '6' : - (state & KQ_KEV_QOS) ? 'q' : '-' + (state & KQ_WORKQ) ? 'q' : + (state & KQ_WORKLOOP) ? 'l' : '-' ); } } +enum kqtype { + KQTYPE_FD, + KQTYPE_DYNAMIC +}; + static int -process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo *fdlist, int nfds) +process_kqueue(int pid, const char *procname, enum kqtype type, uint64_t kqid, + struct proc_fdinfo *fdlist, int nfds) { int ret, i, nknotes; char tmpstr[256]; int maxknotes = 256; /* arbitrary starting point */ + int kq_state; + bool is_kev_64, is_kev_qos; int err = 0; bool overflow = false; + int fd; + bool dynkq_printed = false; /* * get the basic kqueue info */ struct kqueue_fdinfo kqfdinfo = {}; - ret = proc_pidfdinfo(pid, kqfd, PROC_PIDFDKQUEUEINFO, &kqfdinfo, sizeof(kqfdinfo)); - if (ret != sizeof(kqfdinfo) && kqfd != -1) { + struct kqueue_dyninfo kqinfo = {}; + switch (type) { + case KQTYPE_FD: + ret = proc_pidfdinfo(pid, (int)kqid, PROC_PIDFDKQUEUEINFO, &kqfdinfo, sizeof(kqfdinfo)); + fd = (int)kqid; + break; + case KQTYPE_DYNAMIC: + ret = proc_piddynkqueueinfo(pid, PROC_PIDDYNKQUEUE_INFO, kqid, &kqinfo, sizeof(kqinfo)); + break; + default: + os_crash("invalid kqueue type"); + } + + if (type == KQTYPE_FD && (int)kqid != -1) { + if (ret != sizeof(kqfdinfo)) { /* every proc has an implicit workq kqueue, dont warn if its unused */ - fprintf(stderr, "WARN: FD table changed (pid %i, kq %i)\n", pid, kqfd); + fprintf(stderr, "WARN: FD table changed (pid %i, kq %i)\n", pid, + fd); + } + } else if (type == KQTYPE_DYNAMIC) { + if (ret < sizeof(struct kqueue_info)) { + fprintf(stderr, "WARN: kqueue missing (pid %i, kq %#" PRIx64 ")\n", + pid, kqid); + } else { + kqfdinfo.kqueueinfo = kqinfo.kqdi_info; + } + if (verbose && ret >= sizeof(struct kqueue_dyninfo)) { + print_kq_info(pid, procname, kqid, kqinfo.kqdi_info.kq_state); + + printf("%18s ", " "); // ident + printf("%-9s ", " "); // filter + dynkq_printed = true; + + if (raw) { + printf("%-10s ", " "); // fflags + printf("%-10s ", " "); // flags + printf("%-10s ", " "); // evst + } else { + printf("%-8s ", " "); // fdtype + printf("%-7s ", " "); // fflags + printf("%-15s ", " "); // flags + printf("%-17s ", " "); // evst + } + + int qos = MAX(MAX(kqinfo.kqdi_events_qos, kqinfo.kqdi_async_qos), + kqinfo.kqdi_sync_waiter_qos); + printf("%3s ", thread_qos_name(qos)); + printf("%-18s ", " "); // data + printf("%-18s ", " "); // udata + printf("%#18llx ", kqinfo.kqdi_servicer); // ext0 + printf("%#18llx ", kqinfo.kqdi_owner); // ext1 + printf("\n"); + } } /* @@ -322,25 +422,36 @@ process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo kqextinfo = malloc(sizeof(struct kevent_extinfo) * maxknotes); } if (!kqextinfo) { - perror("failed allocating memory"); err = errno; + perror("failed allocating memory"); goto out; } errno = 0; - nknotes = proc_pidfdinfo(pid, kqfd, PROC_PIDFDKQUEUE_EXTINFO, kqextinfo, - sizeof(struct kevent_extinfo) * maxknotes); + switch (type) { + case KQTYPE_FD: + nknotes = proc_pidfdinfo(pid, fd, PROC_PIDFDKQUEUE_EXTINFO, + kqextinfo, sizeof(struct kevent_extinfo) * maxknotes); + break; + case KQTYPE_DYNAMIC: + nknotes = proc_piddynkqueueinfo(pid, PROC_PIDDYNKQUEUE_EXTINFO, kqid, + kqextinfo, sizeof(struct kevent_extinfo) * maxknotes); + break; + default: + os_crash("invalid kqueue type"); + } + if (nknotes <= 0) { if (errno == 0) { /* proc_*() can't distinguish between error and empty list */ } else if (errno == EAGAIN) { goto again; } else if (errno == EBADF) { - fprintf(stderr, "WARN: FD table changed (pid %i, kq %i)\n", pid, kqfd); + fprintf(stderr, "WARN: FD table changed (pid %i, kq %#" PRIx64 ")\n", pid, kqid); goto out; } else { - perror("failed to get extended kqueue info"); err = errno; + perror("failed to get extended kqueue info"); goto out; } } @@ -356,10 +467,14 @@ process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo overflow = true; } + kq_state = kqfdinfo.kqueueinfo.kq_state; + is_kev_64 = (kq_state & PROC_KQUEUE_64); + is_kev_qos = (kq_state & PROC_KQUEUE_QOS); + if (nknotes == 0) { - if (!ignore_empty) { + if (!ignore_empty && !dynkq_printed) { /* for empty kqueues, print a single empty entry */ - print_kq_info(pid, procname, kqfd, kqfdinfo.kqueueinfo.kq_state); + print_kq_info(pid, procname, kqid, kq_state); printf("%18s \n", "-"); } goto out; @@ -368,7 +483,7 @@ process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo for (i = 0; i < nknotes; i++) { struct kevent_extinfo *info = &kqextinfo[i]; - print_kq_info(pid, procname, kqfd, kqfdinfo.kqueueinfo.kq_state); + print_kq_info(pid, procname, kqid, kqfdinfo.kqueueinfo.kq_state); print_ident(info->kqext_kev.ident, info->kqext_kev.filter, 18); printf("%-9s ", filt_name(info->kqext_kev.filter)); @@ -377,7 +492,6 @@ process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo printf("%#10x ", info->kqext_kev.flags); printf("%#10x ", info->kqext_status); } else { - /* for kevents attached to file descriptors, print the type of FD (file, socket, etc) */ const char *fdstr = ""; if (filter_is_fd_type(info->kqext_kev.filter)) { @@ -413,32 +527,42 @@ process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo ); unsigned st = info->kqext_status; - printf("%c%c%c%c %c%c%c%c%c", + printf("%c%c%c%c %c%c%c%c %c%c%c%c %c%c ", (st & KN_ACTIVE) ? 'a' : '-', (st & KN_QUEUED) ? 'q' : '-', (st & KN_DISABLED) ? 'd' : '-', - (st & KN_STAYQUEUED) ? 's' : '-', + (st & KN_STAYACTIVE) ? 's' : '-', + + (st & KN_DROPPING) ? 'd' : '-', + (st & KN_USEWAIT) ? 'w' : '-', + (st & KN_ATTACHING) ? 'c' : '-', + (st & KN_ATTACHED) ? 'a' : '-', + + (st & KN_DISPATCH) ? 's' : '-', + (st & KN_UDATA_SPECIFIC) ? 'u' : '-', + (st & KN_SUPPRESSED) ? 'p' : '-', + (st & KN_STOLENDROP) ? 't' : '-', - (st & KN_DROPPING) ? 'o' : '-', - (st & KN_USEWAIT) ? 'u' : '-', - (st & KN_ATTACHING) ? 'c' : '-', - (st & KN_DEFERDROP) ? 'f' : '-', - (st & KN_TOUCH) ? 't' : '-' + (st & KN_REQVANISH) ? 'v' : '-', + (st & KN_VANISHED) ? 'n' : '-' ); } + printf("%3s ", thread_qos_name(info->kqext_kev.qos)); + printf("%#18llx ", (unsigned long long)info->kqext_kev.data); if (verbose) { printf("%#18llx ", (unsigned long long)info->kqext_kev.udata); - if (kqfdinfo.kqueueinfo.kq_state & (KQ_KEV64|KQ_KEV_QOS)) { + if (is_kev_qos || is_kev_64) { printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[0]); printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[1]); - } - if (kqfdinfo.kqueueinfo.kq_state & KQ_KEV_QOS) { - printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[2]); - printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[3]); - printf("%#10lx ", (unsigned long)info->kqext_kev.xflags); + + if (is_kev_qos) { + printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[2]); + printf("%#18llx ", (unsigned long long)info->kqext_kev.ext[3]); + printf("%#10lx ", (unsigned long)info->kqext_kev.xflags); + } } } @@ -446,8 +570,8 @@ process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo } if (overflow) { - printf(" ***** output truncated (>=%i knotes on kq %i, proc %i) *****\n", - nknotes, kqfd, pid); + printf(" ***** output truncated (>=%i knotes on kq %" PRIu64 ", proc %i) *****\n", + nknotes, kqid, pid); } out: @@ -459,10 +583,46 @@ process_kqueue_on_fd(int pid, const char *procname, int kqfd, struct proc_fdinfo return err; } +static int +pid_kqids(pid_t pid, kqueue_id_t **kqids_out) +{ + static int kqids_len = 256; + static kqueue_id_t *kqids = NULL; + static uint32_t kqids_size; + + int nkqids; + +retry: + if (os_mul_overflow(sizeof(kqueue_id_t), kqids_len, &kqids_size)) { + assert(kqids_len > PROC_PIDDYNKQUEUES_MAX); + kqids_len = PROC_PIDDYNKQUEUES_MAX; + goto retry; + } + if (!kqids) { + kqids = malloc(kqids_size); + os_assert(kqids != NULL); + } + + nkqids = proc_list_dynkqueueids(pid, kqids, kqids_size); + if (nkqids > kqids_len && kqids_len < PROC_PIDDYNKQUEUES_MAX) { + kqids_len *= 2; + if (kqids_len > PROC_PIDDYNKQUEUES_MAX) { + kqids_len = PROC_PIDDYNKQUEUES_MAX; + } + free(kqids); + kqids = NULL; + goto retry; + } + + *kqids_out = kqids; + return MIN(nkqids, kqids_len); +} + static int process_pid(pid_t pid) { - int i, nfds; + int i, nfds, nkqids; + kqueue_id_t *kqids; int ret = 0; int maxfds = 256; /* arbitrary starting point */ struct proc_fdinfo *fdlist = NULL; @@ -473,21 +633,21 @@ process_pid(pid_t pid) fdlist = malloc(sizeof(struct proc_fdinfo) * maxfds); } if (!fdlist) { - perror("failed to allocate"); ret = errno; + perror("failed to allocate"); goto out; } nfds = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, fdlist, sizeof(struct proc_fdinfo) * maxfds); if (nfds <= 0) { + ret = errno; fprintf(stderr, "%s: failed enumerating file descriptors of process %i: %s", - self, pid, strerror(errno)); - if (errno == EPERM && geteuid() != 0) { + self, pid, strerror(ret)); + if (ret == EPERM && geteuid() != 0) { fprintf(stderr, " (are you root?)"); } fprintf(stderr, "\n"); - ret = errno; goto out; } @@ -514,20 +674,35 @@ process_pid(pid_t pid) } /* handle the special workq kq */ - ret = process_kqueue_on_fd(pid, procname, -1, fdlist, nfds); + ret = process_kqueue(pid, procname, KQTYPE_FD, -1, fdlist, nfds); if (ret) { goto out; } for (i = 0; i < nfds; i++) { if (fdlist[i].proc_fdtype == PROX_FDTYPE_KQUEUE) { - ret = process_kqueue_on_fd(pid, procname, fdlist[i].proc_fd, fdlist, nfds); + ret = process_kqueue(pid, procname, KQTYPE_FD, + (uint64_t)fdlist[i].proc_fd, fdlist, nfds); if (ret) { goto out; } } } + nkqids = pid_kqids(pid, &kqids); + + for (i = 0; i < nkqids; i++) { + ret = process_kqueue(pid, procname, KQTYPE_DYNAMIC, kqids[i], fdlist, nfds); + if (ret) { + goto out; + } + } + + if (nkqids >= PROC_PIDDYNKQUEUES_MAX) { + printf(" ***** output truncated (>=%i dynamic kqueues in proc %i) *****\n", + nkqids, pid); + } + out: if (fdlist) { free(fdlist); @@ -562,8 +737,8 @@ process_all_pids(void) } else if (errno == EAGAIN) { goto again; } else { - perror("failed enumerating pids"); ret = errno; + perror("failed enumerating pids"); goto out; } } @@ -580,7 +755,8 @@ process_all_pids(void) /* listpids gives us pid 0 for some reason */ if (pids[i]) { ret = process_pid(pids[i]); - if (ret) { + /* ignore races with processes exiting */ + if (ret && ret != ESRCH) { goto out; } } @@ -599,22 +775,28 @@ static void cheatsheet(void) { fprintf(stderr, "\nFilter-independent flags:\n\n\ -\033[1mcommand pid kq kqst ident filter fdtype fflags flags evst\033[0m\n\ -\033[1m-------------------- ----- ----- ---- ------------------ --------- -------- ------- --------------- ----------\033[0m\n\ - ┌ EV_UDATA_SPECIFIC\n\ - EV_DISPATCH ┐ │┌ EV_FLAG0 (EV_POLL)\n\ - EV_CLEAR ┐│ ││┌ EV_FLAG1 (EV_OOBAND)\n\ - EV_ONESHOT ┐││ │││┌ EV_EOF\n\ - EV_RECEIPT ┐│││ ││││┌ EV_ERROR\n\ - ││││ │││││\n\ -\033[1mlaunchd 1 4 ks- netbiosd 250 PROC ------- andx r1cs upboe aqds oucft\033[0m \n\ - │ │││ ││││ ││││ │││││\n\ - kqueue file descriptor ┘ │││ EV_ADD ┘│││ KN_ACTIVE ┘│││ ││││└ KN_TOUCH\n\ - KQ_SLEEP ┘││ EV_ENABLE ┘││ KN_QUEUED ┘││ │││└ KN_DEFERDROP\n\ - KQ_SEL ┘│ EV_DISABLE ┘│ KN_DISABLED ┘│ ││└ KN_ATTACHING\n\ - KEV32 (3) ┤ EV_DELETE ┘ KN_STAYQUEUED ┘ │└ KN_USEWAIT\n\ - KEV64 (6) ┤ └ KN_DROPPING\n\ - KEV_QOS (q) ┘\n\ +\033[1m\ +command pid kq kqst ident filter fdtype fflags flags evst\033[0m\n\ +\033[1m\ +-------------------- ----- ------------------ ---- ------------------ --------- -------- ------- --------------- -----------------\033[0m\n\ + ┌ EV_UDATA_SPECIFIC\n\ + EV_DISPATCH ┐ │┌ EV_FLAG0 (EV_POLL)\n\ + EV_CLEAR ┐│ ││┌ EV_FLAG1 (EV_OOBAND)\n\ + EV_ONESHOT ┐││ │││┌ EV_EOF\n\ + EV_RECEIPT ┐│││ ││││┌ EV_ERROR\n\ + ││││ │││││\n\ +\033[1m\ +launchd 1 4 ks- netbiosd 250 PROC ------- andx r1cs upboe aqds dwca supt vn\033[0m \n\ + │ │││ ││││ ││││ ││││ ││││ ││\n\ + kqueue file descriptor/dynamic ID ┘ │││ EV_ADD ┘│││ KN_ACTIVE ┘│││ ││││ ││││ ││\n\ + KQ_SLEEP ┘││ EV_ENABLE ┘││ KN_QUEUED ┘││ ││││ ││││ ││\n\ + KQ_SEL ┘│ EV_DISABLE ┘│ KN_DISABLED ┘│ ││││ ││││ │└ KN_VANISHED\n\ + KQ_WORKQ (q) ┤ EV_DELETE ┘ KN_STAYACTIVE ┘ ││││ ││││ └ KN_REQVANISH\n\ + KQ_WORKLOOP (l) ┘ ││││ ││││\n\ + KN_DROPPING ┘│││ │││└ KN_STOLENDROP\n\ + KN_USEWAIT ┘││ ││└ KN_SUPPRESSED\n\ + KN_ATTACHING ┘│ │└ KN_UDATA_SPECIFIC\n\ + KN_ATTACHED ┘ └ KN_DISPATCH\n\ \n"); } @@ -628,20 +810,21 @@ static void print_header(void) { if (raw) { - printf(" pid kq kqst ident filter fflags flags evst data"); - if (verbose) { - printf(" udata ext0 ext1 ext2 ext3 xflags"); - } - printf("\n"); - printf("----- ----- ---------- ------------------ --------- ---------- ---------- ---------- ------------------"); + printf(" pid kq kqst knid filter fflags flags evst qos data"); + } else { + printf("command pid kq kqst knid filter fdtype fflags flags evst qos data"); + } + + if (verbose) { + printf(" udata servicer / ext0 owner / ext1 ext2 ext3 xflags"); + } + + printf("\n"); + if (raw) { + printf("----- ------------------ ---------- ------------------ --------- ---------- ---------- ---------- --- ------------------"); } else { - printf("command pid kq kqst ident filter fdtype fflags flags evst data"); - if (verbose) { - printf(" udata ext0 ext1 ext2 ext3 xflags"); - } - printf("\n"); - printf("-------------------- ----- ----- ---- ------------------ --------- -------- ------- --------------- ---------- -----------------"); + printf("-------------------- ----- ------------------ ---- ------------------ --------- -------- ------- --------------- ----------------- --- ------------------"); } if (verbose) { diff --git a/lsmp.tproj/common.h b/lsmp.tproj/common.h index ea07dc2..d9552ca 100644 --- a/lsmp.tproj/common.h +++ b/lsmp.tproj/common.h @@ -25,6 +25,7 @@ #define system_cmds_common_h #include +#include "json.h" #define PROC_NAME_LEN 100 #define BUFSTR_LEN 30 @@ -37,6 +38,7 @@ struct prog_configs { boolean_t verbose; int voucher_detail_length; pid_t pid; /* if user focusing only one pid */ + JSON_t json_output; }; extern struct prog_configs lsmp_config; @@ -136,30 +138,47 @@ typedef struct my_per_task_info { #define IKOT_UNKNOWN 39 /* magic catchall */ #define IKOT_MAX_TYPE (IKOT_UNKNOWN+1) /* # of IKOT_ types */ + +#define PORT_FLAG_TO_INDEX(flag) ( __builtin_ctz(flag) ) /* count trailing zeros */ +#define INDEX_TO_PORT_FLAG(idx) ( 1 << idx ) +typedef struct port_status_flag_info { + natural_t flag; /* MACH_PORT_STATUS_FLAG_* */ + const char *compact_name; /* Single character name for compact representation */ + const char *name; /* human readable long name */ +} port_status_flag_info_t; + +/* + * list of names for possible MACH_PORT_STATUS_FLAG_* + * indexed by PORT_FLAG_TO_INDEX(MACH_PORT_STATUS_FLAG_*) + */ +extern const port_status_flag_info_t port_status_flags[]; + +#define _SHOW_PORT_STATUS_FLAG(flags, flag) \ + (flags & flag) ? port_status_flags[PORT_FLAG_TO_INDEX(flag)].compact_name : "-" #define SHOW_PORT_STATUS_FLAGS(flags) \ - (flags & MACH_PORT_STATUS_FLAG_TEMPOWNER) ?"T":"-", \ - (flags & MACH_PORT_STATUS_FLAG_GUARDED) ?"G":"-", \ - (flags & MACH_PORT_STATUS_FLAG_STRICT_GUARD) ?"S":"-", \ - (flags & MACH_PORT_STATUS_FLAG_IMP_DONATION) ?"I":"-", \ - (flags & MACH_PORT_STATUS_FLAG_REVIVE) ?"R":"-", \ - (flags & MACH_PORT_STATUS_FLAG_TASKPTR) ?"P":"-" + _SHOW_PORT_STATUS_FLAG(flags, MACH_PORT_STATUS_FLAG_TEMPOWNER), \ + _SHOW_PORT_STATUS_FLAG(flags, MACH_PORT_STATUS_FLAG_GUARDED), \ + _SHOW_PORT_STATUS_FLAG(flags, MACH_PORT_STATUS_FLAG_STRICT_GUARD), \ + _SHOW_PORT_STATUS_FLAG(flags, MACH_PORT_STATUS_FLAG_IMP_DONATION), \ + _SHOW_PORT_STATUS_FLAG(flags, MACH_PORT_STATUS_FLAG_REVIVE), \ + _SHOW_PORT_STATUS_FLAG(flags, MACH_PORT_STATUS_FLAG_TASKPTR) -uint32_t show_recipe_detail(mach_voucher_attr_recipe_t recipe, char * voucher_outstr, uint32_t maxlen); -char *copy_voucher_detail(mach_port_t task, mach_port_name_t voucher); +uint32_t show_recipe_detail(mach_voucher_attr_recipe_t recipe, char * voucher_outstr, uint32_t maxlen, JSON_t json); +char *copy_voucher_detail(mach_port_t task, mach_port_name_t voucher, JSON_t json); /* mach port related functions */ const char * kobject_name(natural_t kotype); void get_receive_port_context(task_t taskp, mach_port_name_t portname, mach_port_context_t *context); int get_recieve_port_status(task_t taskp, mach_port_name_t portname, mach_port_info_ext_t *info); -void show_task_mach_ports(my_per_task_info_t *taskinfo, uint32_t taskCount, my_per_task_info_t *allTaskInfos); +void show_task_mach_ports(my_per_task_info_t *taskinfo, uint32_t taskCount, my_per_task_info_t *allTaskInfos, JSON_t json); /* task and thread related helper functions */ kern_return_t collect_per_task_info(my_per_task_info_t *taskinfo, task_t target_task); my_per_task_info_t * allocate_taskinfo_memory(uint32_t taskCount); void deallocate_taskinfo_memory(my_per_task_info_t *data); -kern_return_t print_task_exception_info(my_per_task_info_t *taskinfo); -kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo); +kern_return_t print_task_exception_info(my_per_task_info_t *taskinfo, JSON_t json); +kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo, JSON_t json); my_per_task_info_t * get_taskinfo_by_kobject(natural_t kobj); void get_exc_behavior_string(exception_behavior_t b, char *out_string, size_t len); diff --git a/lsmp.tproj/json.h b/lsmp.tproj/json.h new file mode 100644 index 0000000..2a87a8b --- /dev/null +++ b/lsmp.tproj/json.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2017 Apple Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * The contents of this file constitute Original Code as defined in and + * are subject to the Apple Public Source License Version 1.1 (the + * "License"). You may not use this file except in compliance with the + * License. Please obtain a copy of the License at + * http://www.apple.com/publicsource and read it before using this file. + * + * This Original Code and all software distributed under the License are + * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the + * License for the specific language governing rights and limitations + * under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +/* + * Provides a stream-based API for generating JSON output + * + * Handles tedious tasks like worrying about comma placement (and avoiding trailing commas). + * Assumes strings are already escaped (if necessary) and does no error checking (thus it + * may produce invalid JSON when used improperly). + * + * As a convenience, when the provided `json` stream is NULL (i.e. it was never initialized + * by `JSON_OPEN`) these APIs will do nothing. + * + * Example usage: + * + * JSON_t json = JSON_OPEN("/path/to/output.json") + * JSON_OBJECT_BEGIN(json); // root object + * + * JSON_OBJECT_SET(json, version, %.1f, 1.0); + * JSON_OBJECT_SET_BOOL(json, has_fruit, 1); + * + * // Note the required quotes for strings (formatted or not) + * char *mystr = "hello"; + * JSON_OBJECT_SET(json, formatted_string, "%s", mystr); + * JSON_OBJECT_SET(json, literal_string, "my literal string"); + * + * JSON_KEY(json, fruit_array); + * JSON_ARRAY_BEGIN(json); // fruit_array + * JSON_ARRAY_APPEND(json, "my literal string"); + * JSON_ARRAY_APPEND(json, "<0x%08llx>", 0xface); + * JSON_ARRAY_APPEND(json, %d, 3); + * JSON_ARRAY_END(json); // fruit_array + * + * JSON_OBJECT_END(json); // root object + * JSON_CLOSE(json); + */ + +#ifndef _JSON_H_ +#define _JSON_H_ + +#include +#include +#include + +#define _JSON_IF(json, code) \ + if (json != NULL) { \ + code; \ + } +#define _JSON_COMMA(json) \ + if (json->require_comma) { \ + fprintf(json->stream, ","); \ + } + +struct _JSON { + FILE* stream; + bool require_comma; +}; +typedef struct _JSON * JSON_t; + +#pragma mark Open/Close +/* Return a new JSON_t stream */ +static inline JSON_t JSON_OPEN(const char *path) { + JSON_t p = malloc(sizeof(struct _JSON)); + p->stream = fopen(path, "w+"); + p->require_comma = false; + return p; +} + +/* Close an existing JSON stream, removing trailing commas */ +#define JSON_CLOSE(json) _JSON_IF(json, fclose(json->stream); free(json)) + +#pragma mark Keys/Values +/* Output the `key` half of a key/value pair */ +#define JSON_KEY(json, key) _JSON_IF(json, _JSON_COMMA(json); fprintf(json->stream, "\"" #key "\":"); json->require_comma = false) +/* Output the `value` half of a key/value pair */ +#define JSON_VALUE(json, format, ...) _JSON_IF(json, fprintf(json->stream, #format, ##__VA_ARGS__); json->require_comma = true) + +#define _JSON_BEGIN(json, character) _JSON_COMMA(json); fprintf(json->stream, #character); json->require_comma = false; +#define _JSON_END(json, character) fprintf(json->stream, #character); json->require_comma = true; +#define _JSON_BOOL(val) ( val ? "true" : "false" ) + +#pragma mark Objects +/* Start a new JSON object */ +#define JSON_OBJECT_BEGIN(json) _JSON_IF(json, _JSON_BEGIN(json, {)) +/* Set a value in the current JSON object */ +#define JSON_OBJECT_SET(json, key, format, ...) _JSON_IF(json, JSON_KEY(json, key); JSON_VALUE(json, format, ##__VA_ARGS__)) +/* Set a boolean in the current JSON object */ +#define JSON_OBJECT_SET_BOOL(json, key, value) JSON_OBJECT_SET(json, key, %s, _JSON_BOOL(value)) +/* End the current JSON object */ +#define JSON_OBJECT_END(json) _JSON_IF(json, _JSON_END(json, })) + +#pragma mark Arrays +/* Start a new JSON array */ +#define JSON_ARRAY_BEGIN(json) _JSON_IF(json, _JSON_BEGIN(json, [)) +/* Append a value to the current JSON array */ +#define JSON_ARRAY_APPEND(json, format, ...) _JSON_IF(json, _JSON_COMMA(json); JSON_VALUE(json, format, ##__VA_ARGS__)) +/* End the current JSON array */ +#define JSON_ARRAY_END(json) _JSON_IF(json, _JSON_END(json, ])) + +#endif /* _JSON_H_ */ diff --git a/lsmp.tproj/lsmp.1 b/lsmp.tproj/lsmp.1 index 1b4d28d..d4c70e9 100644 --- a/lsmp.tproj/lsmp.1 +++ b/lsmp.tproj/lsmp.1 @@ -21,6 +21,10 @@ Show information in detail for Kernel object based ports. Including thread ports .Nm lsmp .Ar -a Show mach port usage for all tasks in the system +.Pp +.Nm lsmp +.Ar -j +Save output as JSON to . .Sh DESCRIPTION The .Nm lsmp diff --git a/lsmp.tproj/lsmp.c b/lsmp.tproj/lsmp.c index c2e3330..2e46f70 100644 --- a/lsmp.tproj/lsmp.c +++ b/lsmp.tproj/lsmp.c @@ -27,7 +27,9 @@ #include #include #include +#include #include "common.h" +#include "json.h" #if TARGET_OS_EMBEDDED #define TASK_FOR_PID_USAGE_MESG "\nPlease check your boot-args to ensure you have access to task_for_pid()." @@ -40,21 +42,33 @@ struct prog_configs lsmp_config = { .show_voucher_details = FALSE, .verbose = FALSE, .pid = 0, + .json_output = NULL, }; -my_per_task_info_t *psettaskinfo; -mach_msg_type_number_t taskCount; - static void print_usage(char *progname) { fprintf(stderr, "Usage: %s -p [-a|-v|-h] \n", "lsmp"); fprintf(stderr, "Lists information about mach ports. Please see man page for description of each column.\n"); fprintf(stderr, "\t-p : print all mach ports for process id . \n"); fprintf(stderr, "\t-a : print all mach ports for all processeses. \n"); fprintf(stderr, "\t-v : print verbose details for kernel objects.\n"); + fprintf(stderr, "\t-j : save output as JSON to .\n"); fprintf(stderr, "\t-h : print this help.\n"); exit(1); } +static void print_task_info(my_per_task_info_t *taskinfo, mach_msg_type_number_t taskCount, my_per_task_info_t *psettaskinfo, boolean_t verbose, JSON_t json) { + printf("Process (%d) : %s\n", taskinfo->pid, taskinfo->processName); + JSON_OBJECT_BEGIN(json); + JSON_OBJECT_SET(json, pid, %d, taskinfo->pid); + JSON_OBJECT_SET(json, name, "%s", taskinfo->processName); + show_task_mach_ports(taskinfo, taskCount, psettaskinfo, json); + print_task_exception_info(taskinfo, json); + if (verbose) { + printf("\n"); + print_task_threads_special_ports(taskinfo, json); + } + JSON_OBJECT_END(json); +} int main(int argc, char *argv[]) { kern_return_t ret; @@ -64,8 +78,10 @@ int main(int argc, char *argv[]) { char *progname = "lsmp"; int i, option = 0; lsmp_config.voucher_detail_length = 128; /* default values for config */ + my_per_task_info_t *psettaskinfo; + mach_msg_type_number_t taskCount; - while((option = getopt(argc, argv, "hvalp:")) != -1) { + while((option = getopt(argc, argv, "hvalp:j:")) != -1) { switch(option) { case 'a': /* user asked for info on all processes */ @@ -91,6 +107,14 @@ int main(int argc, char *argv[]) { } break; + case 'j': + lsmp_config.json_output = JSON_OPEN(optarg); + if (lsmp_config.json_output == NULL) { + fprintf(stderr, "Unable to open \"%s\": %s\n", optarg, strerror(errno)); + exit(1); + } + break; + default: fprintf(stderr, "Unknown argument. \n"); /* Fall through to 'h' */ @@ -182,39 +206,36 @@ int main(int argc, char *argv[]) { ret = KERN_SUCCESS; } + JSON_OBJECT_BEGIN(lsmp_config.json_output); + JSON_OBJECT_SET(lsmp_config.json_output, version, "%.1f", 1.0); + JSON_KEY(lsmp_config.json_output, processes); + JSON_ARRAY_BEGIN(lsmp_config.json_output); + if (lsmp_config.show_all_tasks == FALSE) { if (taskinfo == NULL) { fprintf(stderr, "Failed to find task ipc information for pid %d\n", lsmp_config.pid); exit(1); } - printf("Process (%d) : %s\n", taskinfo->pid, taskinfo->processName); - show_task_mach_ports(taskinfo, taskCount, psettaskinfo); - print_task_exception_info(taskinfo); - printf("\n"); - print_task_threads_special_ports(taskinfo); - + print_task_info(taskinfo, taskCount, psettaskinfo, TRUE, lsmp_config.json_output); } else { for (i=0; i < taskCount; i++) { if (psettaskinfo[i].valid != TRUE) continue; - printf("Process (%d) : %s\n", psettaskinfo[i].pid, psettaskinfo[i].processName); - show_task_mach_ports(&psettaskinfo[i], taskCount, psettaskinfo); - print_task_exception_info(&psettaskinfo[i]); - - if (lsmp_config.verbose) { - printf("\n"); - print_task_threads_special_ports(&psettaskinfo[i]); - } - + print_task_info(&psettaskinfo[i], taskCount, psettaskinfo, lsmp_config.verbose, lsmp_config.json_output); printf("\n\n"); } } + JSON_ARRAY_END(lsmp_config.json_output); + JSON_OBJECT_END(lsmp_config.json_output); + if (taskCount > 1) { vm_deallocate(mach_task_self(), (vm_address_t)tasks, (vm_size_t)taskCount * sizeof(mach_port_t)); } deallocate_taskinfo_memory(psettaskinfo); + JSON_CLOSE(lsmp_config.json_output); + return(0); } diff --git a/lsmp.tproj/port_details.c b/lsmp.tproj/port_details.c index fec26d7..caed133 100644 --- a/lsmp.tproj/port_details.c +++ b/lsmp.tproj/port_details.c @@ -28,6 +28,7 @@ #include #include #include "common.h" +#include "json.h" const char * kobject_name(natural_t kotype) { @@ -76,43 +77,108 @@ const char * kobject_name(natural_t kotype) } } +const port_status_flag_info_t port_status_flags[] = { + [PORT_FLAG_TO_INDEX(MACH_PORT_STATUS_FLAG_TEMPOWNER)] = { + .flag = MACH_PORT_STATUS_FLAG_TEMPOWNER, + .compact_name = "T", + .name = "tempowner", + }, + [PORT_FLAG_TO_INDEX(MACH_PORT_STATUS_FLAG_GUARDED)] = { + .flag = MACH_PORT_STATUS_FLAG_GUARDED, + .compact_name = "G", + .name = "guarded", + }, + [PORT_FLAG_TO_INDEX(MACH_PORT_STATUS_FLAG_STRICT_GUARD)] = { + .flag = MACH_PORT_STATUS_FLAG_STRICT_GUARD, + .compact_name = "S", + .name = "strict guard", + }, + [PORT_FLAG_TO_INDEX(MACH_PORT_STATUS_FLAG_IMP_DONATION)] = { + .flag = MACH_PORT_STATUS_FLAG_IMP_DONATION, + .compact_name = "I", + .name = "imp donation", + }, + [PORT_FLAG_TO_INDEX(MACH_PORT_STATUS_FLAG_REVIVE)] = { + .flag = MACH_PORT_STATUS_FLAG_REVIVE, + .compact_name = "R", + .name = "revive", + }, + [PORT_FLAG_TO_INDEX(MACH_PORT_STATUS_FLAG_TASKPTR)] = { + .flag = MACH_PORT_STATUS_FLAG_TASKPTR, + .compact_name = "P", + .name = "taskptr", + }, + {0}, +}; + #define VOUCHER_DETAIL_PREFIX " " static const unsigned int voucher_contents_size = 8192; static uint8_t voucher_contents[voucher_contents_size]; +typedef struct { + int total; + int sendcount; + int receivecount; + int sendoncecount; + int portsetcount; + int deadcount; + int dncount; + int vouchercount; +} task_table_entry_counts; + +typedef task_table_entry_counts * task_table_entry_counts_t; + +static void show_task_table_entry(ipc_info_name_t *entry, my_per_task_info_t *taskinfo, uint32_t taskCount, my_per_task_info_t *allTaskInfos, task_table_entry_counts_t counts, JSON_t json); static uint32_t safesize (int len){ return (len > 0) ? len : 0; } -uint32_t show_recipe_detail(mach_voucher_attr_recipe_t recipe, char *voucher_outstr, uint32_t maxlen) { +uint32_t show_recipe_detail(mach_voucher_attr_recipe_t recipe, char *voucher_outstr, uint32_t maxlen, JSON_t json) { + JSON_OBJECT_BEGIN(json); + uint32_t len = 0; len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "Key: %u, ", recipe->key)); len += safesize(snprintf(&voucher_outstr[len], maxlen - len, "Command: %u, ", recipe->command)); len += safesize(snprintf(&voucher_outstr[len], maxlen - len, "Previous voucher: 0x%x, ", recipe->previous_voucher)); len += safesize(snprintf(&voucher_outstr[len], maxlen - len, "Content size: %u\n", recipe->content_size)); + JSON_OBJECT_SET(json, key, %u, recipe->key); + JSON_OBJECT_SET(json, command, %u, recipe->command); + JSON_OBJECT_SET(json, previous_voucher, "0x%x", recipe->previous_voucher); + JSON_OBJECT_SET(json, content_size, %u, recipe->content_size); + switch (recipe->key) { case MACH_VOUCHER_ATTR_KEY_ATM: - len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "ATM ID: %llu\n", *(uint64_t *)(uintptr_t)recipe->content)); + JSON_OBJECT_SET(json, ATM_ID, %llu, *(uint64_t *)(uintptr_t)recipe->content); + len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "ATM ID: %llu", *(uint64_t *)(uintptr_t)recipe->content)); break; case MACH_VOUCHER_ATTR_KEY_IMPORTANCE: - len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "IMPORTANCE INFO: %s\n", (char *)recipe->content)); + // content may not be valid JSON, exclude + // JSON_OBJECT_SET(json, importance_info, "%s", (char *)recipe->content); + len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "IMPORTANCE INFO: %s", (char *)recipe->content)); break; case MACH_VOUCHER_ATTR_KEY_BANK: - len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "RESOURCE ACCOUNTING INFO: %s\n", (char *)recipe->content)); + // content may not be valid JSON, exclude + // JSON_OBJECT_SET(json, resource_accounting_info, "%s", (char *)recipe->content); + len += safesize(snprintf(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX "RESOURCE ACCOUNTING INFO: %s", (char *)recipe->content)); break; default: len += print_hex_data(&voucher_outstr[len], maxlen - len, VOUCHER_DETAIL_PREFIX, "Recipe Contents", (void *)recipe->content, MIN(recipe->content_size, lsmp_config.voucher_detail_length)); break; } + if (len + 1 < maxlen && voucher_outstr[len - 1] != '\n') { + voucher_outstr[len++] = '\n'; + voucher_outstr[len] = '\0'; + } + JSON_OBJECT_END(json); // recipe return len; } -char * copy_voucher_detail(mach_port_t task, mach_port_name_t voucher) { +char * copy_voucher_detail(mach_port_t task, mach_port_name_t voucher, JSON_t json) { unsigned int recipe_size = voucher_contents_size; kern_return_t kr = KERN_SUCCESS; bzero((void *)&voucher_contents[0], sizeof(voucher_contents)); @@ -147,7 +213,7 @@ char * copy_voucher_detail(mach_port_t task, mach_port_name_t voucher) { while (recipe_size > used_size) { recipe = (mach_voucher_attr_recipe_t)&voucher_contents[used_size]; if (recipe->key) { - plen += show_recipe_detail(recipe, &voucher_outstr[plen], detail_maxlen - plen); + plen += show_recipe_detail(recipe, &voucher_outstr[plen], detail_maxlen - plen, json); } used_size += sizeof(mach_voucher_attr_recipe_data_t) + recipe->content_size; } @@ -196,246 +262,426 @@ int get_recieve_port_status(task_t taskp, mach_port_name_t portname, mach_port_i return 0; } -void show_task_mach_ports(my_per_task_info_t *taskinfo, uint32_t taskCount, my_per_task_info_t *allTaskInfos) +void show_task_mach_ports(my_per_task_info_t *taskinfo, uint32_t taskCount, my_per_task_info_t *allTaskInfos, JSON_t json) { - int i, emptycount = 0, portsetcount = 0, sendcount = 0, receivecount = 0, sendoncecount = 0, deadcount = 0, dncount = 0, vouchercount = 0, pid; - kern_return_t ret; - pid_for_task(taskinfo->task, &pid); + int i; + task_table_entry_counts counts = {0}; + + counts.total = taskinfo->tableCount + taskinfo->treeCount; + + JSON_KEY(json, ports) + JSON_ARRAY_BEGIN(json); printf(" name ipc-object rights flags boost reqs recv send sonce oref qlimit msgcount context identifier type\n"); printf("--------- ---------- ---------- -------- ----- ---- ----- ----- ----- ---- ------ -------- ------------------ ----------- ------------\n"); - for (i = 0; i < taskinfo->tableCount; i++) { - int j, k; - boolean_t send = FALSE; - boolean_t sendonce = FALSE; - boolean_t dnreq = FALSE; - int sendrights = 0; - unsigned int kotype = 0; - vm_offset_t kobject = (vm_offset_t)0; - - /* skip empty slots in the table */ - if ((taskinfo->table[i].iin_type & MACH_PORT_TYPE_ALL_RIGHTS) == 0) { - emptycount++; - continue; + for (i = 0; i < taskinfo->tableCount; i++) { + show_task_table_entry(&taskinfo->table[i], taskinfo, taskCount, allTaskInfos, &counts, json); + } + + JSON_ARRAY_END(json); // ports + JSON_OBJECT_SET(json, total, %d, counts.total); + JSON_OBJECT_SET(json, send_rights, %d, counts.sendcount); + JSON_OBJECT_SET(json, receive_rights, %d, counts.receivecount); + JSON_OBJECT_SET(json, send_once_rights, %d, counts.sendoncecount); + JSON_OBJECT_SET(json, port_sets, %d, counts.portsetcount); + JSON_OBJECT_SET(json, dead_names, %d, counts.deadcount); + JSON_OBJECT_SET(json, dead_name requests, %d, counts.dncount); + JSON_OBJECT_SET(json, vouchers, %d, counts.vouchercount); + + printf("\n"); + printf("total = %d\n", counts.total); + printf("SEND = %d\n", counts.sendcount); + printf("RECEIVE = %d\n", counts.receivecount); + printf("SEND_ONCE = %d\n", counts.sendoncecount); + printf("PORT_SET = %d\n", counts.portsetcount); + printf("DEAD_NAME = %d\n", counts.deadcount); + printf("DNREQUEST = %d\n", counts.dncount); + printf("VOUCHERS = %d\n", counts.vouchercount); +} + +static void show_task_table_entry(ipc_info_name_t *entry, my_per_task_info_t *taskinfo, uint32_t taskCount, my_per_task_info_t *allTaskInfos, task_table_entry_counts_t counts, JSON_t json) { + int j, k, port_status_flag_idx; + kern_return_t ret; + boolean_t send = FALSE; + boolean_t sendonce = FALSE; + boolean_t dnreq = FALSE; + int sendrights = 0; + unsigned int kotype = 0; + vm_offset_t kobject = (vm_offset_t)0; + + /* skip empty slots in the table */ + if ((entry->iin_type & MACH_PORT_TYPE_ALL_RIGHTS) == 0) { + counts->total--; + return; + } + + if (entry->iin_type == MACH_PORT_TYPE_PORT_SET) { + mach_port_name_array_t members; + mach_msg_type_number_t membersCnt; + + ret = mach_port_get_set_status(taskinfo->task, + entry->iin_name, + &members, &membersCnt); + if (ret != KERN_SUCCESS) { + fprintf(stderr, "mach_port_get_set_status(0x%08x) failed: %s\n", + entry->iin_name, + mach_error_string(ret)); + return; } - if (taskinfo->table[i].iin_type == MACH_PORT_TYPE_PORT_SET) { - mach_port_name_array_t members; - mach_msg_type_number_t membersCnt; - - ret = mach_port_get_set_status(taskinfo->task, - taskinfo->table[i].iin_name, - &members, &membersCnt); - if (ret != KERN_SUCCESS) { - fprintf(stderr, "mach_port_get_set_status(0x%08x) failed: %s\n", - taskinfo->table[i].iin_name, - mach_error_string(ret)); - continue; - } - printf("0x%08x 0x%08x port-set -------- --- 1 %d members\n", - taskinfo->table[i].iin_name, - taskinfo->table[i].iin_object, - membersCnt); - /* get some info for each portset member */ - for (j = 0; j < membersCnt; j++) { - for (k = 0; k < taskinfo->tableCount; k++) { - if (taskinfo->table[k].iin_name == members[j]) { - mach_port_info_ext_t info; - mach_port_status_t port_status; - mach_port_context_t port_context = (mach_port_context_t)0; - if (0 != get_recieve_port_status(taskinfo->task, taskinfo->table[k].iin_name, &info)) { - bzero((void *)&info, sizeof(info)); + JSON_OBJECT_BEGIN(json); + JSON_OBJECT_SET(json, type, "port set"); + JSON_OBJECT_SET(json, name, "0x%08x", entry->iin_name); + JSON_OBJECT_SET(json, ipc-object, "0x%08x", entry->iin_object); + + JSON_KEY(json, members); + JSON_ARRAY_BEGIN(json); + + printf("0x%08x 0x%08x port-set -------- --- 1 %d members\n", + entry->iin_name, + entry->iin_object, + membersCnt); + /* get some info for each portset member */ + for (j = 0; j < membersCnt; j++) { + for (k = 0; k < taskinfo->tableCount; k++) { + if (taskinfo->table[k].iin_name == members[j]) { + mach_port_info_ext_t info; + mach_port_status_t port_status; + mach_port_context_t port_context = (mach_port_context_t)0; + if (0 != get_recieve_port_status(taskinfo->task, taskinfo->table[k].iin_name, &info)) { + bzero((void *)&info, sizeof(info)); + } + port_status = info.mpie_status; + get_receive_port_context(taskinfo->task, taskinfo->table[k].iin_name, &port_context); + + JSON_OBJECT_BEGIN(json); + JSON_OBJECT_SET(json, ipc-object, "0x%08x", taskinfo->table[k].iin_object); + + JSON_KEY(json, rights); + JSON_ARRAY_BEGIN(json); + JSON_ARRAY_APPEND(json, "recv"); + if (taskinfo->table[k].iin_type & MACH_PORT_TYPE_SEND) { + JSON_ARRAY_APPEND(json, "send"); + } + JSON_ARRAY_END(json); // rights + + JSON_KEY(json, port_status_flags); + JSON_ARRAY_BEGIN(json); + port_status_flag_idx = 0; + while (0 != port_status_flags[port_status_flag_idx++].flag) { + if (port_status.mps_flags & INDEX_TO_PORT_FLAG(port_status_flag_idx)) { + JSON_ARRAY_APPEND(json, "%s", port_status_flags[port_status_flag_idx].name); } - port_status = info.mpie_status; - get_receive_port_context(taskinfo->task, taskinfo->table[k].iin_name, &port_context); - printf(" - 0x%08x %s --%s%s%s%s%s%s %5d %s%s%s %5d %5.0d %5.0d %s %6d %8d 0x%016llx 0x%08x (%d) %s\n", - taskinfo->table[k].iin_object, - (taskinfo->table[k].iin_type & MACH_PORT_TYPE_SEND) ? "recv,send ":"recv ", - SHOW_PORT_STATUS_FLAGS(port_status.mps_flags), - info.mpie_boost_cnt, - (taskinfo->table[k].iin_type & MACH_PORT_TYPE_DNREQUEST) ? "D" : "-", - (port_status.mps_nsrequest) ? "N" : "-", - (port_status.mps_pdrequest) ? "P" : "-", - 1, - taskinfo->table[k].iin_urefs, - port_status.mps_sorights, - (port_status.mps_srights) ? "Y" : "N", - port_status.mps_qlimit, - port_status.mps_msgcount, - (uint64_t)port_context, - taskinfo->table[k].iin_name, - pid, - taskinfo->processName); - break; - } - } - } + } + JSON_ARRAY_END(json); // port status flags + JSON_OBJECT_SET(json, boosts, %d, info.mpie_boost_cnt); + + JSON_KEY(json, notifications); + JSON_ARRAY_BEGIN(json); + if (taskinfo->table[k].iin_type & MACH_PORT_TYPE_DNREQUEST) { + JSON_ARRAY_APPEND(json, "dead name"); + } + if (port_status.mps_nsrequest) { + JSON_ARRAY_APPEND(json, "no sender"); + } + if (port_status.mps_nsrequest) { + JSON_ARRAY_APPEND(json, "port destroy request"); + } + JSON_ARRAY_END(json); // notifications + + JSON_OBJECT_SET(json, recv_rights, %d, 1); + JSON_OBJECT_SET(json, send_rights, %d, taskinfo->table[k].iin_urefs); + JSON_OBJECT_SET(json, send_once_rights, %d, port_status.mps_sorights); + JSON_OBJECT_SET_BOOL(json, oref, port_status.mps_srights); + JSON_OBJECT_SET(json, queue_limit, %d, port_status.mps_qlimit); + JSON_OBJECT_SET(json, msg_count, %d, port_status.mps_msgcount); + JSON_OBJECT_SET(json, context, "0x%016llx", (uint64_t)port_context); + JSON_OBJECT_SET(json, identifier, "0x%08x", taskinfo->table[k].iin_name); + JSON_OBJECT_SET(json, pid, %d, taskinfo->pid); + JSON_OBJECT_SET(json, process, "%s", taskinfo->processName); + JSON_OBJECT_END(json); // member + + printf(" - 0x%08x %s --%s%s%s%s%s%s %5d %s%s%s %5d %5.0d %5.0d %s %6d %8d 0x%016llx 0x%08x (%d) %s\n", + taskinfo->table[k].iin_object, + (taskinfo->table[k].iin_type & MACH_PORT_TYPE_SEND) ? "recv,send ":"recv ", + SHOW_PORT_STATUS_FLAGS(port_status.mps_flags), + info.mpie_boost_cnt, + (taskinfo->table[k].iin_type & MACH_PORT_TYPE_DNREQUEST) ? "D" : "-", + (port_status.mps_nsrequest) ? "N" : "-", + (port_status.mps_pdrequest) ? "P" : "-", + 1, + taskinfo->table[k].iin_urefs, + port_status.mps_sorights, + (port_status.mps_srights) ? "Y" : "N", + port_status.mps_qlimit, + port_status.mps_msgcount, + (uint64_t)port_context, + taskinfo->table[k].iin_name, + taskinfo->pid, + taskinfo->processName); + break; + } + } + } - ret = vm_deallocate(mach_task_self(), (vm_address_t)members, - membersCnt * sizeof(mach_port_name_t)); - if (ret != KERN_SUCCESS) { - fprintf(stderr, "vm_deallocate() failed: %s\n", - mach_error_string(ret)); - exit(1); - } - portsetcount++; - continue; - } - - if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_SEND) { - send = TRUE; - sendrights = taskinfo->table[i].iin_urefs; - sendcount++; - } - - if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_DNREQUEST) { - dnreq = TRUE; - dncount++; - } - - if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_RECEIVE) { - mach_port_status_t status; - mach_port_info_ext_t info; - mach_port_context_t context = (mach_port_context_t)0; - struct k2n_table_node *k2nnode; - ret = get_recieve_port_status(taskinfo->task, taskinfo->table[i].iin_name, &info); - get_receive_port_context(taskinfo->task, taskinfo->table[i].iin_name, &context); - /* its ok to fail in fetching attributes */ - if (ret < 0) { - continue; + JSON_ARRAY_END(json); // members + JSON_OBJECT_END(json); // port-set + + ret = vm_deallocate(mach_task_self(), (vm_address_t)members, + membersCnt * sizeof(mach_port_name_t)); + if (ret != KERN_SUCCESS) { + fprintf(stderr, "vm_deallocate() failed: %s\n", + mach_error_string(ret)); + exit(1); + } + counts->portsetcount++; + return; + } + + if (entry->iin_type & MACH_PORT_TYPE_SEND) { + send = TRUE; + sendrights = entry->iin_urefs; + counts->sendcount++; + } + + if (entry->iin_type & MACH_PORT_TYPE_DNREQUEST) { + dnreq = TRUE; + counts->dncount++; + } + + if (entry->iin_type & MACH_PORT_TYPE_RECEIVE) { + mach_port_status_t status; + mach_port_info_ext_t info; + mach_port_context_t context = (mach_port_context_t)0; + struct k2n_table_node *k2nnode; + ret = get_recieve_port_status(taskinfo->task, entry->iin_name, &info); + get_receive_port_context(taskinfo->task, entry->iin_name, &context); + /* its ok to fail in fetching attributes */ + if (ret < 0) { + return; + } + status = info.mpie_status; + + JSON_OBJECT_BEGIN(json); + JSON_OBJECT_SET(json, type, "port"); + JSON_OBJECT_SET(json, name, "0x%08x", entry->iin_name); + JSON_OBJECT_SET(json, ipc-object, "0x%08x", entry->iin_object); + + JSON_KEY(json, rights); + JSON_ARRAY_BEGIN(json); + JSON_ARRAY_APPEND(json, "recv"); + if (send) JSON_ARRAY_APPEND(json, "send"); + JSON_ARRAY_END(json); // rights + + JSON_KEY(json, port_status_flags); + JSON_ARRAY_BEGIN(json); + port_status_flag_idx = 0; + while (0 != port_status_flags[port_status_flag_idx++].flag) { + if (status.mps_flags & INDEX_TO_PORT_FLAG(port_status_flag_idx)) { + JSON_ARRAY_APPEND(json, "%s", port_status_flags[port_status_flag_idx].name); } - status = info.mpie_status; - printf("0x%08x 0x%08x %s --%s%s%s%s%s%s %5d %s%s%s %5d %5.0d %5.0d %s %6d %8d 0x%016llx \n", - taskinfo->table[i].iin_name, - taskinfo->table[i].iin_object, - (send) ? "recv,send ":"recv ", - SHOW_PORT_STATUS_FLAGS(status.mps_flags), - info.mpie_boost_cnt, - (dnreq) ? "D":"-", - (status.mps_nsrequest) ? "N":"-", - (status.mps_pdrequest) ? "P":"-", - 1, - sendrights, - status.mps_sorights, - (status.mps_srights) ? "Y":"N", - status.mps_qlimit, - status.mps_msgcount, - (uint64_t)context); - receivecount++; - - /* show other rights (in this and other tasks) for the port */ - for (j = 0; j < taskCount; j++) { - if (allTaskInfos[j].valid == FALSE) - continue; - - k2nnode = k2n_table_lookup(allTaskInfos[j].k2ntable, taskinfo->table[i].iin_object); - - while (k2nnode) { - if (k2nnode->info_name != &taskinfo->table[i]) { - assert(k2nnode->info_name->iin_object == taskinfo->table[i].iin_object); - - printf(" + %s -------- %s%s%s %5d <- 0x%08x (%d) %s\n", - (k2nnode->info_name->iin_type & MACH_PORT_TYPE_SEND_ONCE) ? - "send-once " : "send ", - (k2nnode->info_name->iin_type & MACH_PORT_TYPE_DNREQUEST) ? "D" : "-", - "-", - "-", - k2nnode->info_name->iin_urefs, - k2nnode->info_name->iin_name, - allTaskInfos[j].pid, - allTaskInfos[j].processName); - } + } + JSON_ARRAY_END(json); // port status flags + JSON_OBJECT_SET(json, boosts, %d, info.mpie_boost_cnt); - k2nnode = k2n_table_lookup_next(k2nnode, k2nnode->info_name->iin_name); - } - } - continue; - } - else if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_DEAD_NAME) - { - printf("0x%08x 0x%08x dead-name -------- --- %5d \n", - taskinfo->table[i].iin_name, - taskinfo->table[i].iin_object, - taskinfo->table[i].iin_urefs); - deadcount++; - continue; - } - - if (taskinfo->table[i].iin_type & MACH_PORT_TYPE_SEND_ONCE) { - sendonce = TRUE; - sendoncecount++; - } - - printf("0x%08x 0x%08x %s -------- %s%s%s %5.0d ", - taskinfo->table[i].iin_name, - taskinfo->table[i].iin_object, - (send) ? "send ":"send-once ", - (dnreq) ? "D":"-", - "-", - "-", - (send) ? sendrights : 0); - - /* converting to kobjects is not always supported */ - ret = mach_port_kernel_object(taskinfo->task, - taskinfo->table[i].iin_name, - &kotype, (unsigned *)&kobject); - if (ret == KERN_SUCCESS && kotype != 0) { - printf(" 0x%08x %s", (natural_t)kobject, kobject_name(kotype)); - if ((kotype == IKOT_TASK_RESUME) || (kotype == IKOT_TASK) || (kotype == IKOT_TASK_NAME)) { - if (taskinfo->task_kobject == kobject) { - /* neat little optimization since in most cases tasks have themselves in their ipc space */ - printf(" SELF (%d) %s", taskinfo->pid, taskinfo->processName); - } else { - my_per_task_info_t * _found_task = get_taskinfo_by_kobject((natural_t)kobject); - printf(" (%d) %s", _found_task->pid, _found_task->processName); + JSON_KEY(json, notifications); + JSON_ARRAY_BEGIN(json); + if (dnreq) { + JSON_ARRAY_APPEND(json, "dead name"); + } + if (status.mps_nsrequest) { + JSON_ARRAY_APPEND(json, "no sender"); + } + if (status.mps_nsrequest) { + JSON_ARRAY_APPEND(json, "port destroy request"); + } + JSON_ARRAY_END(json); // notifications + + JSON_OBJECT_SET(json, recv_rights, %d, 1); + JSON_OBJECT_SET(json, send_rights, %d, sendrights); + JSON_OBJECT_SET(json, send_once_rights, %d, status.mps_sorights); + JSON_OBJECT_SET_BOOL(json, oref, status.mps_srights); + JSON_OBJECT_SET(json, queue_limit, %d, status.mps_qlimit); + JSON_OBJECT_SET(json, msg_count, %d, status.mps_msgcount); + JSON_OBJECT_SET(json, context, "0x%016llx", (uint64_t)context); + JSON_OBJECT_END(json); // port + + printf("0x%08x 0x%08x %s --%s%s%s%s%s%s %5d %s%s%s %5d %5.0d %5.0d %s %6d %8d 0x%016llx \n", + entry->iin_name, + entry->iin_object, + (send) ? "recv,send ":"recv ", + SHOW_PORT_STATUS_FLAGS(status.mps_flags), + info.mpie_boost_cnt, + (dnreq) ? "D":"-", + (status.mps_nsrequest) ? "N":"-", + (status.mps_pdrequest) ? "P":"-", + 1, + sendrights, + status.mps_sorights, + (status.mps_srights) ? "Y":"N", + status.mps_qlimit, + status.mps_msgcount, + (uint64_t)context); + counts->receivecount++; + + /* show other rights (in this and other tasks) for the port */ + for (j = 0; j < taskCount; j++) { + if (allTaskInfos[j].valid == FALSE) + continue; + + k2nnode = k2n_table_lookup(allTaskInfos[j].k2ntable, entry->iin_object); + + while (k2nnode) { + if (k2nnode->info_name != entry) { + assert(k2nnode->info_name->iin_object == entry->iin_object); + + printf(" + %s -------- %s%s%s %5d <- 0x%08x (%d) %s\n", + (k2nnode->info_name->iin_type & MACH_PORT_TYPE_SEND_ONCE) ? + "send-once " : "send ", + (k2nnode->info_name->iin_type & MACH_PORT_TYPE_DNREQUEST) ? "D" : "-", + "-", + "-", + k2nnode->info_name->iin_urefs, + k2nnode->info_name->iin_name, + allTaskInfos[j].pid, + allTaskInfos[j].processName); } + + k2nnode = k2n_table_lookup_next(k2nnode, k2nnode->info_name->iin_name); } + } + return; + } + else if (entry->iin_type & MACH_PORT_TYPE_DEAD_NAME) + { + JSON_OBJECT_BEGIN(json); + JSON_OBJECT_SET(json, type, "dead name"); + JSON_OBJECT_SET(json, name, "0x%08x", entry->iin_name); + JSON_OBJECT_SET(json, ipc-object, "0x%08x", entry->iin_object); + JSON_OBJECT_SET(json, send_rights, %d, entry->iin_urefs); + JSON_OBJECT_END(json); // dead name + + printf("0x%08x 0x%08x dead-name -------- --- %5d \n", + entry->iin_name, + entry->iin_object, + entry->iin_urefs); + counts->deadcount++; + return; + } - printf("\n"); - if (kotype == IKOT_VOUCHER) { - vouchercount++; - if (lsmp_config.show_voucher_details) { - char * detail = copy_voucher_detail(taskinfo->task, taskinfo->table[i].iin_name); - printf("%s\n", detail); - free(detail); - } + if (entry->iin_type & MACH_PORT_TYPE_SEND_ONCE) { + sendonce = TRUE; + counts->sendoncecount++; + } + + JSON_OBJECT_BEGIN(json); + JSON_OBJECT_SET(json, name, "0x%08x", entry->iin_name); + JSON_OBJECT_SET(json, ipc-object, "0x%08x", entry->iin_object); + + JSON_KEY(json, rights); + JSON_ARRAY_BEGIN(json); + JSON_ARRAY_APPEND(json, "%s", (send) ? "send":"send once"); + JSON_ARRAY_END(json); //rights + + JSON_KEY(json, notifications); + JSON_ARRAY_BEGIN(json); + if (dnreq) JSON_ARRAY_APPEND(json, "dead name"); + JSON_ARRAY_END(json); // notifications + + JSON_OBJECT_SET(json, send_rights, %d, (send) ? sendrights : 0); + + printf("0x%08x 0x%08x %s -------- %s%s%s %5.0d ", + entry->iin_name, + entry->iin_object, + (send) ? "send ":"send-once ", + (dnreq) ? "D":"-", + "-", + "-", + (send) ? sendrights : 0); + + /* converting to kobjects is not always supported */ + ret = mach_port_kernel_object(taskinfo->task, + entry->iin_name, + &kotype, (unsigned *)&kobject); + if (ret == KERN_SUCCESS && kotype != 0) { + JSON_OBJECT_SET(json, identifier, "0x%08x", (natural_t)kobject); + JSON_OBJECT_SET(json, type, "%s", kobject_name(kotype)); + printf(" 0x%08x %s", (natural_t)kobject, kobject_name(kotype)); + if ((kotype == IKOT_TASK_RESUME) || (kotype == IKOT_TASK) || (kotype == IKOT_TASK_NAME)) { + if (taskinfo->task_kobject == kobject) { + /* neat little optimization since in most cases tasks have themselves in their ipc space */ + JSON_OBJECT_SET(json, pid, %d, taskinfo->pid); + JSON_OBJECT_SET(json, process, "%s", taskinfo->processName); + printf(" SELF (%d) %s", taskinfo->pid, taskinfo->processName); + } else { + my_per_task_info_t * _found_task = get_taskinfo_by_kobject((natural_t)kobject); + JSON_OBJECT_SET(json, pid, %d, _found_task->pid); + JSON_OBJECT_SET(json, process, "%s", _found_task->processName); + printf(" (%d) %s", _found_task->pid, _found_task->processName); } - continue; - } - - /* not kobject - find the receive right holder */ - my_per_task_info_t *recv_holder_taskinfo; - mach_port_name_t recv_name = MACH_PORT_NULL; - if (KERN_SUCCESS == get_taskinfo_of_receiver_by_send_right(&taskinfo->table[i], &recv_holder_taskinfo, &recv_name)) { - mach_port_status_t port_status; - mach_port_info_ext_t info; - mach_port_context_t port_context = (mach_port_context_t)0; - if (0 != get_recieve_port_status(recv_holder_taskinfo->task, recv_name, &info)) { - bzero((void *)&port_status, sizeof(port_status)); + } + + if (kotype == IKOT_THREAD) { + for (int i = 0; i < taskinfo->threadCount; i++) { + if (taskinfo->threadInfos[i].th_kobject == kobject) { + printf(" (%#llx)", taskinfo->threadInfos[i].th_id); + break; + } + } + } + + printf("\n"); + if (kotype == IKOT_VOUCHER) { + counts->vouchercount++; + if (lsmp_config.show_voucher_details) { + JSON_KEY(json, recipes); + JSON_ARRAY_BEGIN(json); + char * detail = copy_voucher_detail(taskinfo->task, entry->iin_name, json); + JSON_ARRAY_END(json); // recipes + printf("%s\n", detail); + free(detail); } - port_status = info.mpie_status; - get_receive_port_context(recv_holder_taskinfo->task, recv_name, &port_context); - printf(" -> %6d %8d 0x%016llx 0x%08x (%d) %s\n", - port_status.mps_qlimit, - port_status.mps_msgcount, - (uint64_t)port_context, - recv_name, - recv_holder_taskinfo->pid, - recv_holder_taskinfo->processName); - - } else - printf(" 0x00000000 (-) Unknown Process\n"); + } + JSON_OBJECT_END(json); // kobject + return; + } - } - printf("total = %d\n", taskinfo->tableCount + taskinfo->treeCount - emptycount); - printf("SEND = %d\n", sendcount); - printf("RECEIVE = %d\n", receivecount); - printf("SEND_ONCE = %d\n", sendoncecount); - printf("PORT_SET = %d\n", portsetcount); - printf("DEAD_NAME = %d\n", deadcount); - printf("DNREQUEST = %d\n", dncount); - printf("VOUCHERS = %d\n", vouchercount); + /* not kobject - find the receive right holder */ + my_per_task_info_t *recv_holder_taskinfo; + mach_port_name_t recv_name = MACH_PORT_NULL; + if (KERN_SUCCESS == get_taskinfo_of_receiver_by_send_right(entry, &recv_holder_taskinfo, &recv_name)) { + mach_port_status_t port_status; + mach_port_info_ext_t info; + mach_port_context_t port_context = (mach_port_context_t)0; + if (0 != get_recieve_port_status(recv_holder_taskinfo->task, recv_name, &info)) { + bzero((void *)&port_status, sizeof(port_status)); + } + port_status = info.mpie_status; + get_receive_port_context(recv_holder_taskinfo->task, recv_name, &port_context); + + JSON_OBJECT_SET(json, queue_limit, %d, port_status.mps_qlimit); + JSON_OBJECT_SET(json, msg_count, %d, port_status.mps_msgcount); + JSON_OBJECT_SET(json, context, "0x%016llx", (uint64_t)port_context); + JSON_OBJECT_SET(json, identifier, "0x%08x", recv_name); + JSON_OBJECT_SET(json, pid, %d, recv_holder_taskinfo->pid); + JSON_OBJECT_SET(json, process, "%s", recv_holder_taskinfo->processName); + + printf(" -> %6d %8d 0x%016llx 0x%08x (%d) %s\n", + port_status.mps_qlimit, + port_status.mps_msgcount, + (uint64_t)port_context, + recv_name, + recv_holder_taskinfo->pid, + recv_holder_taskinfo->processName); + + } else { + JSON_OBJECT_SET(json, identifier, "0x%08x", 0); + JSON_OBJECT_SET(json, pid, %d, -1); + JSON_OBJECT_SET(json, process, "unknown"); + printf(" 0x00000000 (-) Unknown Process\n"); + } + JSON_OBJECT_END(json); // non-kobject } uint32_t print_hex_data(char *outstr, size_t maxlen, char *prefix, char *desc, void *addr, int len) { diff --git a/lsmp.tproj/task_details.c b/lsmp.tproj/task_details.c index aeaa160..e8b1ffe 100644 --- a/lsmp.tproj/task_details.c +++ b/lsmp.tproj/task_details.c @@ -191,7 +191,7 @@ kern_return_t collect_per_task_info(my_per_task_info_t *taskinfo, task_t target_ } if (KERN_SUCCESS == thread_get_mach_voucher(threadPorts[i], 0, &th_voucher) && th_voucher != IPC_VOUCHER_NULL) { - char *detail = copy_voucher_detail(mach_task_self(), th_voucher); + char *detail = copy_voucher_detail(mach_task_self(), th_voucher, NULL); taskinfo->threadInfos[i].voucher_detail = strndup(detail, VOUCHER_DETAIL_MAXLEN); free(detail); @@ -288,12 +288,15 @@ void get_exc_mask_string(exception_mask_t m, char *out_string, size_t len) strncat(out_string," GUARD", len); } -kern_return_t print_task_exception_info(my_per_task_info_t *taskinfo) +kern_return_t print_task_exception_info(my_per_task_info_t *taskinfo, JSON_t json) { char behavior_string[30]; char mask_string[200]; + JSON_KEY(json, exception_ports); + JSON_ARRAY_BEGIN(json); + boolean_t header_required = TRUE; for (int i = 0; i < taskinfo->exceptionInfo.count; i++) { if (taskinfo->exceptionInfo.ports[i] != MACH_PORT_NULL) { @@ -304,16 +307,26 @@ kern_return_t print_task_exception_info(my_per_task_info_t *taskinfo) } get_exc_behavior_string(taskinfo->exceptionInfo.behaviors[i], behavior_string, sizeof(behavior_string)); get_exc_mask_string(taskinfo->exceptionInfo.masks[i], mask_string, 200); + + JSON_OBJECT_BEGIN(json); + JSON_OBJECT_SET(json, port, "0x%08x", taskinfo->exceptionInfo.ports[i]); + JSON_OBJECT_SET(json, flavor, "0x%03x", taskinfo->exceptionInfo.flavors[i]); + JSON_OBJECT_SET(json, behavior, "%s", behavior_string); + JSON_OBJECT_SET(json, mask, "%s", mask_string); + JSON_OBJECT_END(json); // exception port + printf(" 0x%08x 0x%03x <%s> %s \n" , taskinfo->exceptionInfo.ports[i], taskinfo->exceptionInfo.flavors[i], behavior_string, mask_string); } } + JSON_ARRAY_END(json); // exception ports + return KERN_SUCCESS; } -kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo) +kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo, JSON_t json) { kern_return_t kret = KERN_SUCCESS; mach_msg_type_number_t threadcount = taskinfo->threadCount; @@ -321,7 +334,12 @@ kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo) boolean_t newline_required = TRUE; struct my_per_thread_info * info = NULL; + JSON_KEY(json, threads); + JSON_ARRAY_BEGIN(json); + for (int i = 0; i < threadcount; i++) { + JSON_OBJECT_BEGIN(json); + info = &taskinfo->threadInfos[i]; if (header_required) { printf("Thread_KObject Thread-ID Port Description."); @@ -337,12 +355,19 @@ kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo) /* TODO: Should print tid and stuff */ printf("0x%08x ", info->th_kobject); printf("0x%llx ", info->th_id); + + JSON_OBJECT_SET(json, kobject, "0x%08x", info->th_kobject); + JSON_OBJECT_SET(json, tid, "0x%llx", info->th_id); } if (info->voucher_detail != NULL) { + /* TODO: include voucher detail in JSON */ printf("%s\n", info->voucher_detail); } + JSON_KEY(json, exception_ports); + JSON_ARRAY_BEGIN(json); + /* print the thread exception ports also */ if (taskinfo->threadExceptionInfos != NULL) { @@ -354,6 +379,8 @@ kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo) if (excinfo->count > 0) { boolean_t header_required = TRUE; for (int i = 0; i < excinfo->count; i++) { + JSON_OBJECT_BEGIN(json); + if (excinfo->ports[i] != MACH_PORT_NULL) { if (header_required) { printf("\n exc_port flavor mask -> name owner\n"); @@ -361,6 +388,12 @@ kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo) } get_exc_behavior_string(excinfo->behaviors[i], behavior_string, sizeof(behavior_string)); get_exc_mask_string(excinfo->masks[i], mask_string, sizeof(mask_string)); + + JSON_OBJECT_SET(json, port, "0x%08x", excinfo->ports[i]); + JSON_OBJECT_SET(json, flavor, "0x%03x", excinfo->flavors[i]); + JSON_OBJECT_SET(json, behavior, "%s", behavior_string); + JSON_OBJECT_SET(json, mask, "%s", mask_string); + printf(" 0x%08x 0x%03x <%s> %s " , excinfo->ports[i], excinfo->flavors[i], behavior_string, mask_string); ipc_info_name_t actual_sendinfo; @@ -368,6 +401,12 @@ kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo) my_per_task_info_t *recv_holder_taskinfo; mach_port_name_t recv_name = MACH_PORT_NULL; if (KERN_SUCCESS == get_taskinfo_of_receiver_by_send_right(&actual_sendinfo, &recv_holder_taskinfo, &recv_name)) { + + JSON_OBJECT_SET(json, name, "0x%08x", recv_name); + JSON_OBJECT_SET(json, ipc-object, "0x%08x", actual_sendinfo.iin_object); + JSON_OBJECT_SET(json, pid, %d, recv_holder_taskinfo->pid); + JSON_OBJECT_SET(json, process, "%s", recv_holder_taskinfo->processName); + printf(" -> 0x%08x 0x%08x (%d) %s\n", recv_name, actual_sendinfo.iin_object, @@ -382,13 +421,15 @@ kern_return_t print_task_threads_special_ports(my_per_task_info_t *taskinfo) printf("\n"); } - + JSON_OBJECT_END(json); // exception port } } - } - + JSON_ARRAY_END(json); // exception ports + JSON_OBJECT_END(json); // thread } + + JSON_ARRAY_END(json); // threads printf("\n"); return kret; } diff --git a/ltop.tproj/ltop.c b/ltop.tproj/ltop.c index 9a4c90d..42348e7 100644 --- a/ltop.tproj/ltop.c +++ b/ltop.tproj/ltop.c @@ -282,7 +282,7 @@ get_all_info(void) static void print_num(int64_t num, int64_t delta) { - char suf = '\0'; + const char *suf = ""; char posneg = ' '; int numwidth; @@ -297,23 +297,22 @@ print_num(int64_t num, int64_t delta) if (llabs(num) > 10000000000) { num /= 1000000000; - suf = 'G'; + suf = "G"; } else if (llabs(num) > 10000000) { num /= 1000000; - suf = 'M'; + suf = "M"; } else if (llabs(num) > 100000) { num /= 1000; - suf = 'K'; + suf = "K"; } posneg = (delta < 0) ? '-' : ((delta > 0) ? '+' : ' '); numwidth = 10; - if (suf != '\0') - numwidth--; + numwidth -= strlen(suf); - printf("%*lld%c%c ", numwidth, num, suf, posneg); + printf("%*lld%s%c ", numwidth, num, suf, posneg); } static void diff --git a/mkfile.tproj/mkfile.8 b/mkfile.tproj/mkfile.8 index 59c772b..d67fbfc 100644 --- a/mkfile.tproj/mkfile.8 +++ b/mkfile.tproj/mkfile.8 @@ -44,4 +44,4 @@ re-exported before the client will be able to access it. This action may only be done when the client is not running. .SH "SEE ALSO" .TP -chmod(2), stat(2), sticky(8) +chmod(2), stat(2), sticky(7) diff --git a/mslutil/mslutil.1 b/mslutil/mslutil.1 new file mode 100644 index 0000000..ea4f068 --- /dev/null +++ b/mslutil/mslutil.1 @@ -0,0 +1,46 @@ +.\" Copyright (c) 2017, Apple Computer, Inc. All rights reserved. +.\" +.Dd March 31, 2017 +.Dt MSLUTIL 1 +.Os "Mac OS X" +.Sh NAME +.Nm mslutil +.Nd Tool to enable / disable malloc stack logging on a specific proces +.Sh SYNOPSIS +.Nm mslutil pid [--enable flavor] | [--disable] +.Sh DESCRIPTION +The +.Nm mslutil +utility enables/disables malloc stack logging on the process specified by +.Nm pid. +It requires root privileges. +.Pp +The options are as follows: +.Bl -tag -width Ds +.\" ========== +.It Fl -enable +Specifying the +.Fl -enable +option enables malloc stack logging, using the +.Pa flavor +provided. +The supported flavors are: +.Pp +.Pa full +Standard malloc stack logging that records both vm and malloc calls +.Pp +.Pa malloc +Standard malloc stack logging that records only malloc calls +.Pp +.Pa vm +Standard malloc stack logging that records only vm calls +.Pp +.Pa lite +Lite mode of malloc stack logging. +.\" ========== +.It Fl -disable +Specifying the +.Fl -disable +option disables any current mode of malloc stack logging. +.\" ========== +.El diff --git a/mslutil/mslutil.c b/mslutil/mslutil.c new file mode 100644 index 0000000..7d20637 --- /dev/null +++ b/mslutil/mslutil.c @@ -0,0 +1,94 @@ +// +// mslutil.c +// mslutil +// +// Created by Christopher Deppe on 3/31/17. +// + +#include +#include +#include +#include +#include +#include + +#define BSD_PID_MAX 99999 /* Copy of PID_MAX from sys/proc_internal.h. */ + +static void print_usage() +{ + printf("usage: mslutil pid [--disable] | [--enable malloc | vm | full | lite]\n"); +} + +static int send_msl_command(uint64_t pid, uint64_t flavor) +{ + uint64_t flags = flavor; + flags <<= 32; + + flags |= (pid & 0xFFFFFFFF); + + int ret = sysctlbyname("kern.memorystatus_vm_pressure_send", 0, 0, &flags, sizeof(flags)); + + if (ret) { + printf("send_msl_command - sysctl: kern.memorystatus_vm_pressure_send failed %s\n", strerror(errno)); + } else { + printf("send_msl_command - success!\n"); + } + + return ret; +} + +int main(int argc, const char * argv[]) +{ + if (argc < 3) { + print_usage(); + exit(1); + } + + int ret = -1; + + pid_t pid = atoi(argv[1]); + + if (pid <= 0 || pid > BSD_PID_MAX) { + printf("Invalid pid\n"); + exit(1); + } + + if (strcmp(argv[2], "--enable") == 0) { + if (argc < 4) { + print_usage(); + exit(1); + } + + uint64_t flavor = 0; + + if (strcmp(argv[3], "full") == 0) { + flavor = MEMORYSTATUS_ENABLE_MSL_MALLOC | MEMORYSTATUS_ENABLE_MSL_VM; + } else if (strcmp(argv[3], "malloc") == 0) { + flavor = MEMORYSTATUS_ENABLE_MSL_MALLOC; + } else if (strcmp(argv[3], "vm") == 0) { + flavor = MEMORYSTATUS_ENABLE_MSL_VM; + } else if (strcmp(argv[3], "lite") == 0) { + flavor = MEMORYSTATUS_ENABLE_MSL_LITE; + } + + if (flavor == 0) { + print_usage(); + exit(1); + } + + ret = send_msl_command(pid, flavor); + } else if (strcmp(argv[2], "--disable") == 0) { + ret = send_msl_command(pid, MEMORYSTATUS_DISABLE_MSL); + } else { + print_usage(); + exit(1); + } + + if (ret != 0) { + exit(1); + } else { + exit(0); + } +} + + diff --git a/nvram.tproj/nvram.c b/nvram.tproj/nvram.c index 96d2c0b..89f27fc 100644 --- a/nvram.tproj/nvram.c +++ b/nvram.tproj/nvram.c @@ -586,6 +586,26 @@ static void PrintOFVariable(const void *key, const void *value, void *context) long length; CFTypeID typeID; + if (gUseXML) { + CFDataRef data; + CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, &key, &value, 1, + &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + if (dict == NULL) { + errx(1, "Error creating dictionary for variable value"); + } + + data = CFPropertyListCreateData( kCFAllocatorDefault, dict, kCFPropertyListXMLFormat_v1_0, 0, NULL ); + if (data == NULL) { + errx(1, "Error creating xml plist for variable"); + } + + fwrite(CFDataGetBytePtr(data), sizeof(UInt8), CFDataGetLength(data), stdout); + + CFRelease(dict); + CFRelease(data); + return; + } + // Get the OF variable's name. nameLen = CFStringGetLength(key) + 1; nameBuffer = malloc(nameLen); diff --git a/stackshot.tproj/stackshot.c b/stackshot.tproj/stackshot.c new file mode 100644 index 0000000..7b0ddc8 --- /dev/null +++ b/stackshot.tproj/stackshot.c @@ -0,0 +1,233 @@ +/* Copyright (c) 2017 Apple Inc. All rights reserved. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +uint64_t +stackshot_get_mach_absolute_time(void *buffer, uint32_t size) +{ + kcdata_iter_t iter = kcdata_iter_find_type(kcdata_iter(buffer, size), KCDATA_TYPE_MACH_ABSOLUTE_TIME); + if (!kcdata_iter_valid(iter) || kcdata_iter_size(iter) < sizeof(uint64_t)) { + fprintf(stderr, "bad kcdata\n"); + exit(1); + } + return *(uint64_t *)kcdata_iter_payload(iter); +} + +static void usage(char **argv) +{ + fprintf (stderr, "usage: %s [-d] [-t] >file\n", argv[0]); + fprintf (stderr, " -d : take delta stackshot\n"); + fprintf (stderr, " -b : get bootprofile\n"); + fprintf (stderr, " -c : get coalition data\n"); + fprintf (stderr, " -i : get instructions and cycles\n"); + fprintf (stderr, " -t : enable tailspin mode\n"); + fprintf (stderr, " -g : get thread group data\n"); + fprintf (stderr, " -s : fork a sleep process\n"); + fprintf (stderr, " -L : disable loadinfo\n"); + fprintf (stderr, " -k : active kernel threads only\n"); + fprintf (stderr, " -I : disable io statistics\n"); + fprintf (stderr, " -S : stress test: while(1) stackshot; \n"); + fprintf (stderr, " -p PID : target a pid\n"); + fprintf (stderr, " -E : grab existing kernel buffer\n"); + exit(1); +} + +void forksleep() { + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + + if (pid == 0) { + execlp("sleep", "sleep", "30", NULL); + perror("execlp"); + exit(1); + } +} + + +int main(int argc, char **argv) { + + uint32_t iostats = 0; + uint32_t active_kernel_threads_only = 0; + uint32_t tailspin = 0; + uint32_t bootprofile = 0; + uint32_t thread_group = 0; + uint32_t coalition = 0; + uint32_t instrs_cycles = 0; + uint32_t flags = 0; + uint32_t loadinfo = STACKSHOT_SAVE_LOADINFO | STACKSHOT_SAVE_KEXT_LOADINFO; + boolean_t delta = FALSE; + boolean_t sleep = FALSE; + boolean_t stress = FALSE; + pid_t pid = -1; + int c; + + while ((c = getopt(argc, argv, "SgIikbcLdtsp:E")) != EOF) { + switch(c) { + case 'I': + iostats |= STACKSHOT_NO_IO_STATS; + break; + case 'k': + active_kernel_threads_only |= STACKSHOT_ACTIVE_KERNEL_THREADS_ONLY; + loadinfo &= ~STACKSHOT_SAVE_LOADINFO; + break; + case 'b': + bootprofile |= STACKSHOT_GET_BOOT_PROFILE; + break; + case 'c': + coalition |= STACKSHOT_SAVE_JETSAM_COALITIONS; + break; + case 'i': + instrs_cycles |= STACKSHOT_INSTRS_CYCLES; + break; + case 'L': + loadinfo = 0; + break; + case 'g': + thread_group |= STACKSHOT_THREAD_GROUP; + break; + case 't': + tailspin |= STACKSHOT_TAILSPIN; + break; + case 'd': + delta = TRUE; + break; + case 's': + sleep = TRUE; + break; + case 'p': + pid = atoi(optarg); + break; + case 'S': + stress = TRUE; + break; + case 'E': + flags = flags | STACKSHOT_RETRIEVE_EXISTING_BUFFER; + break; + case '?': + case 'h': + default: + usage(argv); + break; + } + } + + if (thread_group && delta) { + fprintf(stderr, "stackshot does not support delta snapshots with thread groups\n"); + return 1; + } + + if (optind < argc) { + usage(argv); + } + +top: + ; + + void * config = stackshot_config_create(); + if (!config) { + perror("stackshot_config_create"); + return 1; + } + flags = flags | loadinfo | STACKSHOT_SAVE_IMP_DONATION_PIDS | STACKSHOT_GET_DQ | STACKSHOT_KCDATA_FORMAT | + tailspin | bootprofile | active_kernel_threads_only | iostats | thread_group | coalition | instrs_cycles; + + int err = stackshot_config_set_flags(config, flags); + if (err != 0) { + perror("stackshot_config_set_flags"); + return 1; + } + + if (pid != -1) { + int err = stackshot_config_set_pid(config, pid); + if (err != 0) { + perror("stackshot_config_set_flags"); + return 1; + } + } + + err = stackshot_capture_with_config(config); + if (err != 0) { + perror("stackshot_capture_with_config"); + return 1; + } + + void *buf = stackshot_config_get_stackshot_buffer(config); + if (!buf) { + perror("stackshot_config_get_stackshot_buffer"); + return 1; + } + + uint32_t size = stackshot_config_get_stackshot_size(config); + + if (delta) { + // output the original somewhere? + + uint64_t time = stackshot_get_mach_absolute_time(buf, size); + + err = stackshot_config_dealloc_buffer(config); + assert(!err); + + flags |= STACKSHOT_COLLECT_DELTA_SNAPSHOT; + int err = stackshot_config_set_flags(config, flags); + if (err != 0) { + perror("stackshot_config_set_flags"); + return 1; + } + + err = stackshot_config_set_delta_timestamp(config, time); + if (err != 0) { + perror("stackshot_config_delta_timestamp"); + return 1; + } + + if (sleep) { + forksleep(); + } + usleep(10000); + + err = stackshot_capture_with_config(config); + if (err != 0) { + perror("stackshot_capture_with_config"); + return 1; + } + + buf = stackshot_config_get_stackshot_buffer(config); + if (!buf) { + perror("stackshot_config_get_stackshot_buffer"); + return 1; + } + + size = stackshot_config_get_stackshot_size(config); + + + } + + + if (stress) { + if (config) { + stackshot_config_dealloc(config); + config = NULL; + } + goto top; + } + + fwrite(buf, size, 1, stdout); +} diff --git a/sysctl.tproj/sysctl.c b/sysctl.tproj/sysctl.c index 30b066b..606459a 100644 --- a/sysctl.tproj/sysctl.c +++ b/sysctl.tproj/sysctl.c @@ -680,6 +680,9 @@ oidfmt(int *oid, int len, char *fmt, u_int *kind) } } else if (buf[sizeof(u_int)] == 'L') { *kind = (*kind & ~CTLTYPE) | CTLTYPE_LONG; + if (buf[sizeof(u_int)+1] == 'U') { + *kind = (*kind & ~CTLTYPE) | CTLTYPE_ULONG; + } } break; case CTLTYPE_QUAD: diff --git a/system_cmds.xcodeproj/project.pbxproj b/system_cmds.xcodeproj/project.pbxproj index 4b34aee..da5d7cb 100644 --- a/system_cmds.xcodeproj/project.pbxproj +++ b/system_cmds.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 1812F1ED1C8F923900F3DC9E /* CopyFiles */, ); dependencies = ( + 926913A61EC706130079D787 /* PBXTargetDependency */, 1812F18D1C8F923900F3DC9E /* PBXTargetDependency */, 1812F18F1C8F923900F3DC9E /* PBXTargetDependency */, 1812F1911C8F923900F3DC9E /* PBXTargetDependency */, @@ -70,6 +71,8 @@ C9D64CD21B91066B00CFA43B /* CopyFiles */, ); dependencies = ( + 926913A21EC706010079D787 /* PBXTargetDependency */, + 08CE3D361E6E24CC00DF1B78 /* PBXTargetDependency */, C21481471C1A1447003BCA63 /* PBXTargetDependency */, 78DE9DED1B5048D400FE6DF5 /* PBXTargetDependency */, 97999D351AE84D3A00E8B10F /* PBXTargetDependency */, @@ -224,6 +227,8 @@ C9D64CD01B91064700CFA43B /* CopyFiles */, ); dependencies = ( + 926913A41EC706080079D787 /* PBXTargetDependency */, + 08CE3D381E6E24DF00DF1B78 /* PBXTargetDependency */, C21481491C1A14AD003BCA63 /* PBXTargetDependency */, 78DE9DFA1B504D1200FE6DF5 /* PBXTargetDependency */, 97999D371AE84D4100E8B10F /* PBXTargetDependency */, @@ -301,7 +306,10 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 08ADC98C1E70715D0001CB70 /* ktrace.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08ADC98B1E70715D0001CB70 /* ktrace.framework */; }; + 08CE3D341E6E22F600DF1B78 /* stackshot.c in Sources */ = {isa = PBXBuildFile; fileRef = 08CE3D321E6E22DE00DF1B78 /* stackshot.c */; }; 08DC488E1A12C2D6008AAF38 /* kpgo.c in Sources */ = {isa = PBXBuildFile; fileRef = 08DC488D1A12C2C6008AAF38 /* kpgo.c */; }; + 0D06BC661E8F091F00C6EC2D /* mslutil.c in Sources */ = {isa = PBXBuildFile; fileRef = 0D06BC651E8F091F00C6EC2D /* mslutil.c */; }; 1523FE6C1595056C00661E82 /* ltop.c in Sources */ = {isa = PBXBuildFile; fileRef = 1523FE6B1595056C00661E82 /* ltop.c */; }; 1523FE6D1595058100661E82 /* ltop.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1523FE6A1595056C00661E82 /* ltop.1 */; }; 1812F1EE1C8F923900F3DC9E /* system_cmds.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = C9D64CCF1B91063200CFA43B /* system_cmds.plist */; }; @@ -493,13 +501,13 @@ C21481401C1A122B003BCA63 /* threads.c in Sources */ = {isa = PBXBuildFile; fileRef = C214811A1C1A11E7003BCA63 /* threads.c */; }; C21481451C1A131D003BCA63 /* gcore.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C21481131C1A11E6003BCA63 /* gcore.1 */; }; C248DBB01C1A1D0500F6E9AF /* libcompression.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C248DBAF1C1A1D0500F6E9AF /* libcompression.dylib */; }; + C2DAA94F1D9F22F000FAC263 /* convert.c in Sources */ = {isa = PBXBuildFile; fileRef = C2DAA94B1D9F22BF00FAC263 /* convert.c */; }; C625B28B16D6F27E00168EF7 /* taskpolicy.c in Sources */ = {isa = PBXBuildFile; fileRef = C625B28A16D6F27E00168EF7 /* taskpolicy.c */; }; C625B28D16D6F27E00168EF7 /* taskpolicy.8 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C625B28C16D6F27E00168EF7 /* taskpolicy.8 */; }; C65BF57A144BD7C5009028A3 /* CoreFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BA9B766D13739D27001BB39F /* CoreFoundation.framework */; }; C96F50B215BDCEC3008682F7 /* libutil.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = BA4B7A091373BA4600003422 /* libutil.dylib */; }; C96F50BD15BDFEFB008682F7 /* lsmp.1 in CopyFiles */ = {isa = PBXBuildFile; fileRef = C96F50AC15BDCBF0008682F7 /* lsmp.1 */; }; C96F50BE15BDFF03008682F7 /* lsmp.c in Sources */ = {isa = PBXBuildFile; fileRef = C96F50AD15BDCE8E008682F7 /* lsmp.c */; }; - C97199F21C5206DE006D9758 /* libktrace.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C97199F11C5206DE006D9758 /* libktrace.dylib */; }; C9779F6E159A2A0C009436FD /* libutil.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = BA4B7A091373BA4600003422 /* libutil.dylib */; }; C9D64CD11B91065D00CFA43B /* system_cmds.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = C9D64CCF1B91063200CFA43B /* system_cmds.plist */; }; C9D64CD31B91067500CFA43B /* system_cmds.plist in CopyFiles */ = {isa = PBXBuildFile; fileRef = C9D64CCF1B91063200CFA43B /* system_cmds.plist */; }; @@ -523,6 +531,20 @@ /* End PBXBuildRule section */ /* Begin PBXContainerItemProxy section */ + 08CE3D351E6E24CC00DF1B78 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BA2DE9181372FA9100D1913C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 08CE3D281E6E22A200DF1B78; + remoteInfo = stackshot; + }; + 08CE3D371E6E24DF00DF1B78 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BA2DE9181372FA9100D1913C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 08CE3D281E6E22A200DF1B78; + remoteInfo = stackshot; + }; 08DC488F1A12C6F0008AAF38 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BA2DE9181372FA9100D1913C /* Project object */; @@ -922,6 +944,27 @@ remoteGlobalIDString = 8EC3915B1C9733C2001E28E6; remoteInfo = proc_uuid_policy; }; + 926913A11EC706010079D787 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BA2DE9181372FA9100D1913C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0D06BC5D1E8F08CB00C6EC2D; + remoteInfo = mslutil; + }; + 926913A31EC706080079D787 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BA2DE9181372FA9100D1913C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0D06BC5D1E8F08CB00C6EC2D; + remoteInfo = mslutil; + }; + 926913A51EC706130079D787 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = BA2DE9181372FA9100D1913C /* Project object */; + proxyType = 1; + remoteGlobalIDString = 0D06BC5D1E8F08CB00C6EC2D; + remoteInfo = mslutil; + }; 97999D341AE84D3A00E8B10F /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = BA2DE9181372FA9100D1913C /* Project object */; @@ -1611,6 +1654,15 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ + 08CE3D271E6E22A200DF1B78 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; 08DC48831A12C21B008AAF38 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -1620,6 +1672,15 @@ ); runOnlyForDeploymentPostprocessing = 1; }; + 0D06BC5C1E8F08CB00C6EC2D /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = /usr/share/man/man1/; + dstSubfolderSpec = 0; + files = ( + ); + runOnlyForDeploymentPostprocessing = 1; + }; 1523FE5F1595048900661E82 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -2208,8 +2269,15 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 08ADC98B1E70715D0001CB70 /* ktrace.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ktrace.framework; path = System/Library/PrivateFrameworks/ktrace.framework; sourceTree = SDKROOT; }; + 08CE3D291E6E22A200DF1B78 /* stackshot */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = stackshot; sourceTree = BUILT_PRODUCTS_DIR; }; + 08CE3D301E6E22B000DF1B78 /* stackshot */ = {isa = PBXFileReference; lastKnownFileType = folder; path = stackshot; sourceTree = ""; }; + 08CE3D321E6E22DE00DF1B78 /* stackshot.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = stackshot.c; path = stackshot.tproj/stackshot.c; sourceTree = ""; }; 08DC48851A12C21B008AAF38 /* kpgo */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = kpgo; sourceTree = BUILT_PRODUCTS_DIR; }; 08DC488D1A12C2C6008AAF38 /* kpgo.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = kpgo.c; sourceTree = ""; }; + 0D06BC5E1E8F08CB00C6EC2D /* mslutil */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = mslutil; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D06BC651E8F091F00C6EC2D /* mslutil.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mslutil.c; sourceTree = ""; }; + 0D06BC671E8F0B4100C6EC2D /* mslutil.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; path = mslutil.1; sourceTree = ""; }; 1523FE631595048900661E82 /* ltop */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ltop; sourceTree = BUILT_PRODUCTS_DIR; }; 1523FE6A1595056C00661E82 /* ltop.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; path = ltop.1; sourceTree = ""; }; 1523FE6B1595056C00661E82 /* ltop.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ltop.c; sourceTree = ""; }; @@ -2629,6 +2697,9 @@ C21481201C1A11E7003BCA63 /* vm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = vm.c; path = gcore.tproj/vm.c; sourceTree = ""; }; C21481211C1A11E7003BCA63 /* vm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = vm.h; path = gcore.tproj/vm.h; sourceTree = ""; }; C248DBAF1C1A1D0500F6E9AF /* libcompression.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcompression.dylib; path = /usr/lib/libcompression.dylib; sourceTree = ""; }; + C2DAA9491D9F22BF00FAC263 /* gcore-internal.1 */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.man; name = "gcore-internal.1"; path = "gcore.tproj/gcore-internal.1"; sourceTree = ""; }; + C2DAA94A1D9F22BF00FAC263 /* convert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = convert.h; path = gcore.tproj/convert.h; sourceTree = ""; }; + C2DAA94B1D9F22BF00FAC263 /* convert.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = convert.c; path = gcore.tproj/convert.c; sourceTree = ""; }; C625B28816D6F27E00168EF7 /* taskpolicy */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = taskpolicy; sourceTree = BUILT_PRODUCTS_DIR; }; C625B28A16D6F27E00168EF7 /* taskpolicy.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = taskpolicy.c; sourceTree = ""; }; C625B28C16D6F27E00168EF7 /* taskpolicy.8 */ = {isa = PBXFileReference; lastKnownFileType = text; path = taskpolicy.8; sourceTree = ""; }; @@ -2643,6 +2714,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 08CE3D261E6E22A200DF1B78 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 08DC48821A12C21B008AAF38 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2650,6 +2728,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 0D06BC5B1E8F08CB00C6EC2D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 1523FE5D1595048900661E82 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -2755,7 +2840,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - C97199F21C5206DE006D9758 /* libktrace.dylib in Frameworks */, + 08ADC98C1E70715D0001CB70 /* ktrace.framework in Frameworks */, BA4B7A0A1373BA4600003422 /* libutil.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3029,6 +3114,15 @@ path = kpgo.tproj; sourceTree = ""; }; + 0D06BC5F1E8F08CB00C6EC2D /* mslutil */ = { + isa = PBXGroup; + children = ( + 0D06BC651E8F091F00C6EC2D /* mslutil.c */, + 0D06BC671E8F0B4100C6EC2D /* mslutil.1 */, + ); + path = mslutil; + sourceTree = ""; + }; 1523FE691595056C00661E82 /* ltop.tproj */ = { isa = PBXGroup; children = ( @@ -3161,6 +3255,7 @@ 189337C11CC7CB4800B2A6A4 /* Frameworks */ = { isa = PBXGroup; children = ( + 08ADC98B1E70715D0001CB70 /* ktrace.framework */, 189337C21CC7CB4800B2A6A4 /* CoreFoundation.framework */, ); name = Frameworks; @@ -3322,6 +3417,8 @@ BA2DE9161372FA9100D1913C = { isa = PBXGroup; children = ( + 08CE3D321E6E22DE00DF1B78 /* stackshot.c */, + 08CE3D301E6E22B000DF1B78 /* stackshot */, 18EA07101C99C76C006D3005 /* EmbeddedOSSupportHost.framework */, BA4FD1E11372FAFA0025925C /* APPLE_LICENSE */, BA4FD2FB1372FB710025925C /* BSD.xcconfig */, @@ -3355,6 +3452,7 @@ B3F0E6DA16E9706E008FAD09 /* memory_pressure.tproj */, BA4FD26C1372FAFA0025925C /* mkfile.tproj */, 1865517D18CA7104003B92A7 /* msa */, + 0D06BC5F1E8F08CB00C6EC2D /* mslutil */, BA4FD2701372FAFA0025925C /* newgrp.tproj */, BA4FD2741372FAFA0025925C /* nologin.tproj */, BA4FD2791372FAFA0025925C /* nvram.tproj */, @@ -3918,6 +4016,8 @@ 78DE9DE01B5045DE00FE6DF5 /* wait4path */, C20D8C691C1A102F00C1226B /* gcore */, 8EC391651C9733C2001E28E6 /* proc_uuid_policy */, + 08CE3D291E6E22A200DF1B78 /* stackshot */, + 0D06BC5E1E8F08CB00C6EC2D /* mslutil */, ); name = Products; sourceTree = ""; @@ -3925,6 +4025,9 @@ C21481371C1A11F0003BCA63 /* gcore.tproj */ = { isa = PBXGroup; children = ( + C2DAA9491D9F22BF00FAC263 /* gcore-internal.1 */, + C2DAA94A1D9F22BF00FAC263 /* convert.h */, + C2DAA94B1D9F22BF00FAC263 /* convert.c */, C214810D1C1A11E6003BCA63 /* corefile.c */, C214810E1C1A11E6003BCA63 /* corefile.h */, C214810F1C1A11E6003BCA63 /* dyld_shared_cache.c */, @@ -4020,6 +4123,23 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + 08CE3D281E6E22A200DF1B78 /* stackshot */ = { + isa = PBXNativeTarget; + buildConfigurationList = 08CE3D2F1E6E22A200DF1B78 /* Build configuration list for PBXNativeTarget "stackshot" */; + buildPhases = ( + 08CE3D251E6E22A200DF1B78 /* Sources */, + 08CE3D261E6E22A200DF1B78 /* Frameworks */, + 08CE3D271E6E22A200DF1B78 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = stackshot; + productName = stackshot; + productReference = 08CE3D291E6E22A200DF1B78 /* stackshot */; + productType = "com.apple.product-type.tool"; + }; 08DC48841A12C21B008AAF38 /* kpgo */ = { isa = PBXNativeTarget; buildConfigurationList = 08DC488B1A12C21C008AAF38 /* Build configuration list for PBXNativeTarget "kpgo" */; @@ -4037,6 +4157,23 @@ productReference = 08DC48851A12C21B008AAF38 /* kpgo */; productType = "com.apple.product-type.tool"; }; + 0D06BC5D1E8F08CB00C6EC2D /* mslutil */ = { + isa = PBXNativeTarget; + buildConfigurationList = 0D06BC641E8F08CB00C6EC2D /* Build configuration list for PBXNativeTarget "mslutil" */; + buildPhases = ( + 0D06BC5A1E8F08CB00C6EC2D /* Sources */, + 0D06BC5B1E8F08CB00C6EC2D /* Frameworks */, + 0D06BC5C1E8F08CB00C6EC2D /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = mslutil; + productName = mslutil; + productReference = 0D06BC5E1E8F08CB00C6EC2D /* mslutil */; + productType = "com.apple.product-type.tool"; + }; 1523FE5A1595048900661E82 /* ltop */ = { isa = PBXNativeTarget; buildConfigurationList = 1523FE611595048900661E82 /* Build configuration list for PBXNativeTarget "ltop" */; @@ -4877,10 +5014,18 @@ attributes = { LastUpgradeCheck = 0600; TargetAttributes = { + 08CE3D281E6E22A200DF1B78 = { + CreatedOnToolsVersion = 8.3; + ProvisioningStyle = Automatic; + }; 08DC48841A12C21B008AAF38 = { CreatedOnToolsVersion = 6.3; ProvisioningStyle = Manual; }; + 0D06BC5D1E8F08CB00C6EC2D = { + CreatedOnToolsVersion = 9.0; + ProvisioningStyle = Automatic; + }; 1523FE5A1595048900661E82 = { ProvisioningStyle = Manual; }; @@ -5138,6 +5283,8 @@ BA9BF4EF139684B40018C7BB /* zic */, BA959E7E13968C8E00CA9C60 /* zoneinfo */, BA0A860713968E8500D2272C /* zprint */, + 08CE3D281E6E22A200DF1B78 /* stackshot */, + 0D06BC5D1E8F08CB00C6EC2D /* mslutil */, ); }; /* End PBXProject section */ @@ -5307,7 +5454,7 @@ ); runOnlyForDeploymentPostprocessing = 1; shellPath = /bin/sh; - shellScript = "set -x\nset -e\n\nmkdir -p \"${DSTROOT}/private/var/at\"\ninstall -o daemon -d \"${DSTROOT}/private/var/at/spool\"\ntouch \"${DSTROOT}/private/var/at/at.deny\"\nmkdir -p \"${DSTROOT}/usr/lib\"\nln -sf ../../var/at \"${DSTROOT}/usr/lib/cron\"\n"; + shellScript = "set -x\nset -e\n\ninstall -o daemon -d \"${DSTROOT}/private/var/at\"\ninstall -o daemon -d \"${DSTROOT}/private/var/at/spool\"\ntouch \"${DSTROOT}/private/var/at/at.deny\"\nmkdir -p \"${DSTROOT}/usr/lib\"\nln -sf ../../var/at \"${DSTROOT}/usr/lib/cron\"\n"; showEnvVarsInLog = 0; }; BAAEB39F13730D5C003EA7A9 /* ShellScript */ = { @@ -5423,6 +5570,14 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 08CE3D251E6E22A200DF1B78 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 08CE3D341E6E22F600DF1B78 /* stackshot.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 08DC48811A12C21B008AAF38 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -5431,6 +5586,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 0D06BC5A1E8F08CB00C6EC2D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 0D06BC661E8F091F00C6EC2D /* mslutil.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 1523FE5B1595048900661E82 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -5839,6 +6002,7 @@ C21481381C1A1213003BCA63 /* vm.c in Sources */, C21481391C1A1216003BCA63 /* vanilla.c in Sources */, C214813E1C1A122B003BCA63 /* main.c in Sources */, + C2DAA94F1D9F22F000FAC263 /* convert.c in Sources */, C214813F1C1A122B003BCA63 /* sparse.c in Sources */, C21481401C1A122B003BCA63 /* threads.c in Sources */, ); @@ -5865,6 +6029,16 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 08CE3D361E6E24CC00DF1B78 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 08CE3D281E6E22A200DF1B78 /* stackshot */; + targetProxy = 08CE3D351E6E24CC00DF1B78 /* PBXContainerItemProxy */; + }; + 08CE3D381E6E24DF00DF1B78 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 08CE3D281E6E22A200DF1B78 /* stackshot */; + targetProxy = 08CE3D371E6E24DF00DF1B78 /* PBXContainerItemProxy */; + }; 08DC48901A12C6F0008AAF38 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 08DC48841A12C21B008AAF38 /* kpgo */; @@ -6150,6 +6324,21 @@ target = 8EC3915B1C9733C2001E28E6 /* proc_uuid_policy */; targetProxy = 8EC3916D1C973440001E28E6 /* PBXContainerItemProxy */; }; + 926913A21EC706010079D787 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0D06BC5D1E8F08CB00C6EC2D /* mslutil */; + targetProxy = 926913A11EC706010079D787 /* PBXContainerItemProxy */; + }; + 926913A41EC706080079D787 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0D06BC5D1E8F08CB00C6EC2D /* mslutil */; + targetProxy = 926913A31EC706080079D787 /* PBXContainerItemProxy */; + }; + 926913A61EC706130079D787 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 0D06BC5D1E8F08CB00C6EC2D /* mslutil */; + targetProxy = 926913A51EC706130079D787 /* PBXContainerItemProxy */; + }; 97999D351AE84D3A00E8B10F /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 97999D211AE84C0E00E8B10F /* lskq */; @@ -6643,6 +6832,94 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + 08CE3D2D1E6E22A200DF1B78 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + }; + name = Release; + }; + 08CE3D2E1E6E22A200DF1B78 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = NO; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "$(SDKROOT)/System/Library/Frameworks/System.framework/PrivateHeaders"; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + }; + name = Debug; + }; 08DC48891A12C21C008AAF38 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6723,6 +7000,92 @@ }; name = Debug; }; + 0D06BC621E8F08CB00C6EC2D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + }; + name = Release; + }; + 0D06BC631E8F08CB00C6EC2D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.13; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx.internal; + }; + name = Debug; + }; 1523FE621595048900661E82 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { @@ -6912,6 +7275,7 @@ 18732FF418CBD4A700275344 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_ENTITLEMENTS = dynamic_pager.tproj/entitlements.plist; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", NO_DIRECT_RPC, @@ -6925,6 +7289,10 @@ 18732FF518CBD4A700275344 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + ); GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_LABEL = YES; @@ -7619,6 +7987,7 @@ BA4B79EB1373AF7A00003422 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CODE_SIGN_ENTITLEMENTS = dynamic_pager.tproj/entitlements.plist; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", NO_DIRECT_RPC, @@ -7632,6 +8001,10 @@ BA4B7A041373B9E900003422 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SDKROOT)$(SYSTEM_LIBRARY_DIR)/PrivateFrameworks", + ); GCC_TREAT_WARNINGS_AS_ERRORS = NO; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_LABEL = YES; @@ -8121,6 +8494,15 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 08CE3D2F1E6E22A200DF1B78 /* Build configuration list for PBXNativeTarget "stackshot" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 08CE3D2D1E6E22A200DF1B78 /* Release */, + 08CE3D2E1E6E22A200DF1B78 /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 08DC488B1A12C21C008AAF38 /* Build configuration list for PBXNativeTarget "kpgo" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -8130,6 +8512,14 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 0D06BC641E8F08CB00C6EC2D /* Build configuration list for PBXNativeTarget "mslutil" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 0D06BC621E8F08CB00C6EC2D /* Release */, + 0D06BC631E8F08CB00C6EC2D /* Debug */, + ); + defaultConfigurationIsVisible = 0; + }; 1523FE611595048900661E82 /* Build configuration list for PBXNativeTarget "ltop" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/taskpolicy.tproj/taskpolicy.8 b/taskpolicy.tproj/taskpolicy.8 index c6c9a41..b13af54 100644 --- a/taskpolicy.tproj/taskpolicy.8 +++ b/taskpolicy.tproj/taskpolicy.8 @@ -12,6 +12,7 @@ .Op Fl b .Op Fl t Ar thruput_tier .Op Fl l Ar latency_tier +.Op Fl a .Ar program .Oo .Ar arg1 @@ -65,6 +66,8 @@ Set throughput tier of the process to .It Fl l Set latency tier of the process to .Ar latency_tier . +.It Fl a +Run the program with the resource management policies given to applications. .El .Pp .Sh SEE ALSO diff --git a/taskpolicy.tproj/taskpolicy.c b/taskpolicy.tproj/taskpolicy.c index b5e9a60..3260bb6 100644 --- a/taskpolicy.tproj/taskpolicy.c +++ b/taskpolicy.tproj/taskpolicy.c @@ -54,12 +54,12 @@ int main(int argc, char * argv[]) pid_t pid = 0; posix_spawnattr_t attr; extern char **environ; - bool flagx = false, flagX = false, flagb = false, flagB = false; + bool flagx = false, flagX = false, flagb = false, flagB = false, flaga = false; int flagd = -1, flagg = -1; struct task_qos_policy qosinfo = { LATENCY_QOS_TIER_UNSPECIFIED, THROUGHPUT_QOS_TIER_UNSPECIFIED }; uint64_t qos_clamp = POSIX_SPAWN_PROC_CLAMP_NONE; - while ((ch = getopt(argc, argv, "xXbBd:g:c:t:l:p:")) != -1) { + while ((ch = getopt(argc, argv, "xXbBd:g:c:t:l:p:a")) != -1) { switch (ch) { case 'x': flagx = true; @@ -115,6 +115,9 @@ int main(int argc, char * argv[]) usage(); } break; + case 'a': + flaga = true; + break; case '?': default: usage(); @@ -221,6 +224,14 @@ int main(int argc, char * argv[]) if (ret != 0) errc(EX_NOINPUT, ret, "posix_spawnattr_set_qos_clamp_np"); } + if (flaga) { + ret = posix_spawnattr_setprocesstype_np(&attr, POSIX_SPAWN_PROC_TYPE_APP_DEFAULT); + if (ret != 0) errc(EX_NOINPUT, ret, "posix_spawnattr_setprocesstype_np"); + + ret = posix_spawnattr_set_darwin_role_np(&attr, PRIO_DARWIN_ROLE_UI); + if (ret != 0) errc(EX_NOINPUT, ret, "posix_spawnattr_set_darwin_role_np"); + } + ret = posix_spawnp(&pid, argv[0], NULL, &attr, argv, environ); if (ret != 0) errc(EX_NOINPUT, ret, "posix_spawn"); @@ -230,7 +241,7 @@ int main(int argc, char * argv[]) static void usage(void) { fprintf(stderr, "Usage: %s [-x|-X] [-d ] [-g policy] [-c clamp] [-b] [-t ]\n" - " [-l ] [ [...]]\n", getprogname()); + " [-l ] [-a] [ [...]]\n", getprogname()); fprintf(stderr, " %s [-b|-B] [-t ] [-l ] -p pid\n", getprogname()); exit(EX_USAGE); } diff --git a/trace.tproj/trace.c b/trace.tproj/trace.c index 190e8b2..98aadc8 100644 --- a/trace.tproj/trace.c +++ b/trace.tproj/trace.c @@ -1057,6 +1057,7 @@ void read_trace(void) debugid = kdp->debugid; debugid_base = debugid & DBG_FUNC_MASK; now = kdp->timestamp & KDBG_TIMESTAMP_MASK; + cpunum = kdbg_get_cpu(kdp); /* * Is this event from an IOP? If so, there will be no @@ -1085,7 +1086,6 @@ void read_trace(void) bias = now; now -= bias; - cpunum = kdbg_get_cpu(kdp); thread = kdp->arg5; if (lines == 64 || firsttime) diff --git a/zic.tproj/README b/zic.tproj/README index 985a511..ff974e9 100644 --- a/zic.tproj/README +++ b/zic.tproj/README @@ -1,4 +1,8 @@ -@(#)README 7.11 +@(#)README 8.3 +This file is in the public domain, so clarified as of +2009-05-17 by Arthur David Olson. + +$FreeBSD: head/contrib/tzcode/zic/README 192890 2009-05-27 12:18:39Z edwin $ "What time is it?" -- Richard Deacon as The King "Any time you want it to be." -- Frank Baxter as The Scientist @@ -52,8 +56,10 @@ substituting your desired installation directory for "$HOME/tzdir": To use the new functions, use a "-ltz" option when compiling or linking. -Historical local time information has been included here not because it -is particularly useful, but rather to: +Historical local time information has been included here to: + +* provide a compendium of data about the history of civil time + that is useful even if the data are not 100% accurate; * give an idea of the variety of local time rules that have existed in the past and thus an idea of the variety that may be @@ -63,7 +69,9 @@ is particularly useful, but rather to: system. The information in the time zone data files is by no means authoritative; -if you know that the rules are different from those in a file, by all means +the files currently do not even attempt to cover all time stamps before +1970, and there are undoubtedly errors even for time stamps since 1970. +If you know that the rules are different from those in a file, by all means feel free to change file (and please send the changed version to tz@elsie.nci.nih.gov for use in the future). Europeans take note! diff --git a/zic.tproj/build_zichost.sh b/zic.tproj/build_zichost.sh index f66dea2..f2ad134 100755 --- a/zic.tproj/build_zichost.sh +++ b/zic.tproj/build_zichost.sh @@ -36,6 +36,7 @@ env -i \ LANG="${LANG}" \ HOME="${HOME}" \ $EXTRA_ARGS \ + TOOLCHAINS="${TOOLCHAINS}" \ xcrun -sdk "${SDKROOT}" xcodebuild install \ -target zic \ -sdk "macosx" \ diff --git a/zic.tproj/generate_zoneinfo.sh b/zic.tproj/generate_zoneinfo.sh index 16484d1..079e4fb 100755 --- a/zic.tproj/generate_zoneinfo.sh +++ b/zic.tproj/generate_zoneinfo.sh @@ -68,22 +68,22 @@ if [ $? -ne 0 ]; then fi if [ -n "$RC_BRIDGE" ]; then - ACTUAL_PLATFORM_NAME="bridge${PLATFORM_NAME#watch}" + ACTUAL_PLATFORM_NAME="bridgeos" else ACTUAL_PLATFORM_NAME="${PLATFORM_NAME}" fi case "$ACTUAL_PLATFORM_NAME" in -iphone*|appletv*|watch*) +iphone*|appletv*|watch*|bridge*) mkdir -p "${PRIVATEDIR}/var/db" mkdir -p -m a+rx "${PRIVATEDIR}/var/db/timezone" # This link must precisely start with TZDIR followed by a slash. radar:13532660 ln -hfs "/var/db/timezone/zoneinfo/${LOCALTIME}" "${PRIVATEDIR}/var/db/timezone/localtime" ;; -macosx|bridge*) +macosx) mkdir -p "${PRIVATEDIR}/etc" - ln -hfs "/usr/share/zoneinfo/${LOCALTIME}" "${PRIVATEDIR}/etc/localtime" + ln -hfs "/var/db/timezone/zoneinfo/${LOCALTIME}" "${PRIVATEDIR}/etc/localtime" ;; *) echo "Unsupported platform: $ACTUAL_PLATFORM_NAME" diff --git a/zic.tproj/ialloc.c b/zic.tproj/ialloc.c index fabe0c6..dda367b 100644 --- a/zic.tproj/ialloc.c +++ b/zic.tproj/ialloc.c @@ -1,13 +1,17 @@ -#include +/* +** This file is in the public domain, so clarified as of +** 2006-07-17 by Arthur David Olson. +*/ + #ifndef lint #ifndef NOID -__unused static const char elsieid[] = "@(#)ialloc.c 8.29"; +static const char elsieid[] = "@(#)ialloc.c 8.30"; #endif /* !defined NOID */ #endif /* !defined lint */ #ifndef lint -__unused static const char rcsid[] = - "$FreeBSD: src/usr.sbin/zic/ialloc.c,v 1.6 2000/11/28 18:18:56 charnier Exp $"; +static const char rcsid[] = + "$FreeBSD: head/contrib/tzcode/zic/ialloc.c 192625 2009-05-23 06:31:50Z edwin $"; #endif /* not lint */ /*LINTLIBRARY*/ @@ -17,32 +21,39 @@ __unused static const char rcsid[] = #define nonzero(n) (((n) == 0) ? 1 : (n)) char * -imalloc(const size_t n) +imalloc(n) +const int n; { - return malloc(nonzero(n)); + return malloc((size_t) nonzero(n)); } char * -icalloc(size_t nelem, size_t elsize) +icalloc(nelem, elsize) +int nelem; +int elsize; { if (nelem == 0 || elsize == 0) nelem = elsize = 1; - return calloc(nelem, elsize); + return calloc((size_t) nelem, (size_t) elsize); } void * -irealloc(void * const pointer, const size_t size) +irealloc(pointer, size) +void * const pointer; +const int size; { if (pointer == NULL) return imalloc(size); - return realloc((void *) pointer, nonzero(size)); + return realloc((void *) pointer, (size_t) nonzero(size)); } char * -icatalloc(char * const old, const char * const new) +icatalloc(old, new) +char * const old; +const char * const new; { - char * result; - size_t oldsize, newsize; + register char * result; + register int oldsize, newsize; newsize = (new == NULL) ? 0 : strlen(new); if (old == NULL) @@ -57,20 +68,23 @@ icatalloc(char * const old, const char * const new) } char * -icpyalloc(const char * const string) +icpyalloc(string) +const char * const string; { return icatalloc((char *) NULL, string); } void -ifree(char * const p) +ifree(p) +char * const p; { if (p != NULL) (void) free(p); } void -icfree(char * const p) +icfree(p) +char * const p; { if (p != NULL) (void) free(p); diff --git a/zic.tproj/install_zoneinfo.sh b/zic.tproj/install_zoneinfo.sh index 9bb9af9..776c630 100755 --- a/zic.tproj/install_zoneinfo.sh +++ b/zic.tproj/install_zoneinfo.sh @@ -3,18 +3,22 @@ set -e set -x if [ -n "$RC_BRIDGE" ]; then - ACTUAL_PLATFORM_NAME="bridge${PLATFORM_NAME#watch}" + ACTUAL_PLATFORM_NAME="bridgeos" else ACTUAL_PLATFORM_NAME="${PLATFORM_NAME}" fi case "$ACTUAL_PLATFORM_NAME" in -iphone*|appletv*|watch*) +iphone*|appletv*|watch*|macosx|bridge*) ditto "${BUILT_PRODUCTS_DIR}/zoneinfo" "${DSTROOT}/usr/share/zoneinfo.default" ln -hfs "/var/db/timezone/zoneinfo" "${DSTROOT}/usr/share/zoneinfo" - ;; -macosx|bridge*) - ditto "${BUILT_PRODUCTS_DIR}/zoneinfo" "${DSTROOT}/usr/share/zoneinfo" + case "$ACTUAL_PLATFORM_NAME" in + macosx|bridge*) + mkdir -p "${DSTROOT}/private/var/db/timezone" + chmod 555 "${DSTROOT}/private/var/db/timezone" + ln -hfs "/usr/share/zoneinfo.default" "${DSTROOT}/private/var/db/timezone/zoneinfo" + ;; + esac ;; *) echo "Unsupported platform: $ACTUAL_PLATFORM_NAME" diff --git a/zic.tproj/private.h b/zic.tproj/private.h index 4ffd069..ae931b0 100644 --- a/zic.tproj/private.h +++ b/zic.tproj/private.h @@ -4,7 +4,7 @@ /* ** This file is in the public domain, so clarified as of -** 1996-06-05 by Arthur David Olson (arthur_david_olson@nih.gov). +** 1996-06-05 by Arthur David Olson. */ /* @@ -13,7 +13,7 @@ * I have removed all of the ifdef spaghetti which is not relevant to * zic from this file. * - * $FreeBSD: src/usr.sbin/zic/private.h,v 1.7 2004/06/20 21:41:11 stefanf Exp $ + * $FreeBSD: head/contrib/tzcode/zic/private.h 207590 2010-05-03 22:32:26Z emaste $ */ /* @@ -30,10 +30,12 @@ #ifndef lint #ifndef NOID -static const char privatehid[] = "@(#)private.h 7.53"; +static const char privatehid[] = "@(#)private.h 8.6"; #endif /* !defined NOID */ #endif /* !defined lint */ +#define GRANDPARENTED "Local time zone must be set--use tzsetup" + /* ** Defaults for preprocessor symbols. ** You can override these in your C compiler options, e.g. `-DHAVE_ADJTIME=0'. @@ -43,10 +45,6 @@ static const char privatehid[] = "@(#)private.h 7.53"; #define HAVE_GETTEXT 0 #endif /* !defined HAVE_GETTEXT */ -#ifndef HAVE_STRERROR -#define HAVE_STRERROR 1 -#endif /* !defined HAVE_STRERROR */ - #ifndef HAVE_SYMLINK #define HAVE_SYMLINK 1 #endif /* !defined HAVE_SYMLINK */ @@ -71,47 +69,94 @@ static const char privatehid[] = "@(#)private.h 7.53"; #include "stdio.h" #include "errno.h" #include "string.h" -#include "limits.h" /* for CHAR_BIT */ +#include "limits.h" /* for CHAR_BIT et al. */ #include "time.h" #include "stdlib.h" -#if HAVE_GETTEXT - 0 +#if HAVE_GETTEXT #include "libintl.h" -#endif /* HAVE_GETTEXT - 0 */ +#endif /* HAVE_GETTEXT */ -#if HAVE_SYS_WAIT_H - 0 +#if HAVE_SYS_WAIT_H #include /* for WIFEXITED and WEXITSTATUS */ -#endif /* HAVE_SYS_WAIT_H - 0 */ +#endif /* HAVE_SYS_WAIT_H */ -#if HAVE_UNISTD_H - 0 -#include "unistd.h" /* for F_OK and R_OK */ -#endif /* HAVE_UNISTD_H - 0 */ +#if HAVE_UNISTD_H +#include "unistd.h" /* for F_OK and R_OK, and other POSIX goodness */ +#endif /* HAVE_UNISTD_H */ -#if !(HAVE_UNISTD_H - 0) #ifndef F_OK #define F_OK 0 #endif /* !defined F_OK */ #ifndef R_OK #define R_OK 4 #endif /* !defined R_OK */ -#endif /* !(HAVE_UNISTD_H - 0) */ -/* Unlike 's isdigit, this also works if c < 0 | c > UCHAR_MAX. */ +/* Unlike 's isdigit, this also works if c < 0 | c > UCHAR_MAX. */ #define is_digit(c) ((unsigned)(c) - '0' <= 9) -#define P(x) x +/* +** Define HAVE_STDINT_H's default value here, rather than at the +** start, since __GLIBC__'s value depends on previously-included +** files. +** (glibc 2.1 and later have stdint.h, even with pre-C99 compilers.) +*/ +#ifndef HAVE_STDINT_H +#define HAVE_STDINT_H \ + (199901 <= __STDC_VERSION__ || \ + 2 < (__GLIBC__ + (0 < __GLIBC_MINOR__))) +#endif /* !defined HAVE_STDINT_H */ + +#if HAVE_STDINT_H +#include "stdint.h" +#endif /* !HAVE_STDINT_H */ + +#ifndef INT_FAST64_MAX +/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ +#if defined LLONG_MAX || defined __LONG_LONG_MAX__ +typedef long long int_fast64_t; +#else /* ! (defined LLONG_MAX || defined __LONG_LONG_MAX__) */ +#if (LONG_MAX >> 31) < 0xffffffff +Please use a compiler that supports a 64-bit integer type (or wider); +you may need to compile with "-DHAVE_STDINT_H". +#endif /* (LONG_MAX >> 31) < 0xffffffff */ +typedef long int_fast64_t; +#endif /* ! (defined LLONG_MAX || defined __LONG_LONG_MAX__) */ +#endif /* !defined INT_FAST64_MAX */ + +#ifndef INT32_MAX +#define INT32_MAX 0x7fffffff +#endif /* !defined INT32_MAX */ +#ifndef INT32_MIN +#define INT32_MIN (-1 - INT32_MAX) +#endif /* !defined INT32_MIN */ + +/* +** Workarounds for compilers/systems. + */ + +/* +** Some time.h implementations don't declare asctime_r. +** Others might define it as a macro. +** Fix the former without affecting the latter. + */ +#ifndef asctime_r +extern char * asctime_r(struct tm const *, char *); +#endif + + /* ** Private function declarations. */ -char * icalloc P((size_t nelem, size_t elsize)); -char * icatalloc P((char * old, const char * new)); -char * icpyalloc P((const char * string)); -char * imalloc P((size_t n)); -void * irealloc P((void * pointer, size_t size)); -void icfree P((char * pointer)); -void ifree P((char * pointer)); -char * scheck P((const char *string, const char *format)); +char * icalloc (int nelem, int elsize); +char * icatalloc (char * old, const char * new); +char * icpyalloc (const char * string); +char * imalloc (int n); +void * irealloc (void * pointer, int size); +void icfree (char * pointer); +void ifree (char * pointer); +const char * scheck (const char *string, const char *format); /* ** Finally, some convenience items. @@ -133,6 +178,15 @@ char * scheck P((const char *string, const char *format)); #define TYPE_SIGNED(type) (((type) -1) < 0) #endif /* !defined TYPE_SIGNED */ +/* +** Since the definition of TYPE_INTEGRAL contains floating point numbers, +** it cannot be used in preprocessor directives. +*/ + +#ifndef TYPE_INTEGRAL +#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5) +#endif /* !defined TYPE_INTEGRAL */ + #ifndef INT_STRLEN_MAXIMUM /* ** 302 / 1000 is log10(2.0) rounded up. @@ -141,7 +195,8 @@ char * scheck P((const char *string, const char *format)); ** add one more for a minus sign if the type is signed. */ #define INT_STRLEN_MAXIMUM(type) \ - ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + 1 + TYPE_SIGNED(type)) + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ + 1 + TYPE_SIGNED(type)) #endif /* !defined INT_STRLEN_MAXIMUM */ /* @@ -175,11 +230,11 @@ char * scheck P((const char *string, const char *format)); */ #ifndef _ -#if HAVE_GETTEXT - 0 +#if HAVE_GETTEXT #define _(msgid) gettext(msgid) -#else /* !(HAVE_GETTEXT - 0) */ +#else /* !HAVE_GETTEXT */ #define _(msgid) msgid -#endif /* !(HAVE_GETTEXT - 0) */ +#endif /* !HAVE_GETTEXT */ #endif /* !defined _ */ #ifndef TZ_DOMAIN @@ -190,4 +245,28 @@ char * scheck P((const char *string, const char *format)); ** UNIX was a registered trademark of The Open Group in 2003. */ +#ifndef YEARSPERREPEAT +#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ +#endif /* !defined YEARSPERREPEAT */ + +/* +** The Gregorian year averages 365.2425 days, which is 31556952 seconds. +*/ + +#ifndef AVGSECSPERYEAR +#define AVGSECSPERYEAR 31556952L +#endif /* !defined AVGSECSPERYEAR */ + +#ifndef SECSPERREPEAT +#define SECSPERREPEAT ((int_fast64_t) YEARSPERREPEAT * (int_fast64_t) AVGSECSPERYEAR) +#endif /* !defined SECSPERREPEAT */ + +#ifndef SECSPERREPEAT_BITS +#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */ +#endif /* !defined SECSPERREPEAT_BITS */ + + /* + ** UNIX was a registered trademark of The Open Group in 2003. + */ + #endif /* !defined PRIVATE_H */ diff --git a/zic.tproj/scheck.c b/zic.tproj/scheck.c index dcd4d01..10eea82 100644 --- a/zic.tproj/scheck.c +++ b/zic.tproj/scheck.c @@ -1,31 +1,36 @@ +/* +** This file is in the public domain, so clarified as of +** 2006-07-17 by Arthur David Olson. +*/ + #ifndef lint #ifndef NOID -#include -__unused static const char elsieid[] = "@(#)scheck.c 8.15"; +static const char elsieid[] = "@(#)scheck.c 8.19"; #endif /* !defined lint */ #endif /* !defined NOID */ #ifndef lint -__unused static const char rcsid[] = - "$FreeBSD: src/usr.sbin/zic/scheck.c,v 1.7 2001/07/18 11:27:04 dd Exp $"; +static const char rcsid[] = + "$FreeBSD: head/contrib/tzcode/zic/scheck.c 192625 2009-05-23 06:31:50Z edwin $"; #endif /* not lint */ /*LINTLIBRARY*/ #include "private.h" -char * -scheck(const char * const string, const char * const format) +const char * +scheck(string, format) +const char * const string; +const char * const format; { - char * fbuf; - const char * fp; - char * tp; - int c; - char * result; - char dummy; - static char nada; + register char * fbuf; + register const char * fp; + register char * tp; + register int c; + register const char * result; + char dummy; - result = &nada; + result = ""; if (string == NULL || format == NULL) return result; fbuf = imalloc((int) (2 * strlen(format) + 4)); diff --git a/zic.tproj/tzfile.h b/zic.tproj/tzfile.h new file mode 100644 index 0000000..ec4009b --- /dev/null +++ b/zic.tproj/tzfile.h @@ -0,0 +1,192 @@ +#ifndef TZFILE_H +#define TZFILE_H + + +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +** +** $FreeBSD: head/contrib/tzcode/stdtime/tzfile.h 192625 2009-05-23 06:31:50Z edwin $ +*/ + +/* +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Thank you! +*/ + +/* +** ID +*/ + +#ifndef lint +#ifndef NOID +/* +static char tzfilehid[] = "@(#)tzfile.h 8.1"; +*/ +#endif /* !defined NOID */ +#endif /* !defined lint */ + +/* +** Information about time zone files. +*/ + +#ifndef TZDIR +#ifdef UNIFDEF_TZDIR_SYMLINK +#define TZDIR "/var/db/timezone/zoneinfo" /* Time zone object file directory */ +#else /* !UNIFDEF_TZDIR_SYMLINK */ +#define TZDIR "/usr/share/zoneinfo" /* Time zone object file directory */ +#endif /* UNIFDEF_TZDIR_SYMLINK */ +#endif /* !defined TZDIR */ + +#ifndef TZDEFAULT +#ifdef UNIFDEF_MOVE_LOCALTIME +#define TZDEFAULT "/var/db/timezone/localtime" +#else /* !UNIFDEF_MOVE_LOCALTIME */ +#define TZDEFAULT "/etc/localtime" +#endif /* UNIFDEF_MOVE_LOCALTIME */ +#endif /* !defined TZDEFAULT */ + +#ifndef TZDEFRULES +#define TZDEFRULES "posixrules" +#endif /* !defined TZDEFRULES */ + +/* +** Each file begins with. . . +*/ + +#define TZ_MAGIC "TZif" + +struct tzhead { + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_version[1]; /* '\0' or '2' as of 2005 */ + char tzh_reserved[15]; /* reserved--must be zero */ + char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ +}; + +/* +** . . .followed by. . . +** +** tzh_timecnt (char [4])s coded transition times a la time(2) +** tzh_timecnt (unsigned char)s types of local time starting at above +** tzh_typecnt repetitions of +** one (char [4]) coded UTC offset in seconds +** one (unsigned char) used to set tm_isdst +** one (unsigned char) that's an abbreviation list index +** tzh_charcnt (char)s '\0'-terminated zone abbreviations +** tzh_leapcnt repetitions of +** one (char [4]) coded leap second transition times +** one (char [4]) total correction after above +** tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition +** time is standard time, if FALSE, +** transition time is wall clock time +** if absent, transition times are +** assumed to be wall clock time +** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition +** time is UTC, if FALSE, +** transition time is local time +** if absent, transition times are +** assumed to be local time +*/ + +/* +** If tzh_version is '2' or greater, the above is followed by a second instance +** of tzhead and a second instance of the data in which each coded transition +** time uses 8 rather than 4 chars, +** then a POSIX-TZ-environment-variable-style string for use in handling +** instants after the last transition time stored in the file +** (with nothing between the newlines if there is no POSIX representation for +** such instants). +*/ + +/* +** In the current implementation, "tzset()" refuses to deal with files that +** exceed any of the limits below. +*/ + +#ifndef TZ_MAX_TIMES +#define TZ_MAX_TIMES 1200 +#endif /* !defined TZ_MAX_TIMES */ + +#ifndef TZ_MAX_TYPES +#ifndef NOSOLAR +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#endif /* !defined NOSOLAR */ +#ifdef NOSOLAR +/* +** Must be at least 14 for Europe/Riga as of Jan 12 1995, +** as noted by Earl Chew. +*/ +#define TZ_MAX_TYPES 20 /* Maximum number of local time types */ +#endif /* !defined NOSOLAR */ +#endif /* !defined TZ_MAX_TYPES */ + +#ifndef TZ_MAX_CHARS +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ +#endif /* !defined TZ_MAX_CHARS */ + +#ifndef TZ_MAX_LEAPS +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#endif /* !defined TZ_MAX_LEAPS */ + +#define SECSPERMIN 60 +#define MINSPERHOUR 60 +#define HOURSPERDAY 24 +#define DAYSPERWEEK 7 +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) +#define MONSPERYEAR 12 + +#define TM_SUNDAY 0 +#define TM_MONDAY 1 +#define TM_TUESDAY 2 +#define TM_WEDNESDAY 3 +#define TM_THURSDAY 4 +#define TM_FRIDAY 5 +#define TM_SATURDAY 6 + +#define TM_JANUARY 0 +#define TM_FEBRUARY 1 +#define TM_MARCH 2 +#define TM_APRIL 3 +#define TM_MAY 4 +#define TM_JUNE 5 +#define TM_JULY 6 +#define TM_AUGUST 7 +#define TM_SEPTEMBER 8 +#define TM_OCTOBER 9 +#define TM_NOVEMBER 10 +#define TM_DECEMBER 11 + +#define TM_YEAR_BASE 1900 + +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY TM_THURSDAY + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** and so +** isleap(a + b) == isleap((a + b) % 400) +** or +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not C99). +** We use this to avoid addition overflow problems. +*/ + +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) + +#endif /* !defined TZFILE_H */ diff --git a/zic.tproj/zic.8 b/zic.tproj/zic.8 index b2a60aa..a84e563 100644 --- a/zic.tproj/zic.8 +++ b/zic.tproj/zic.8 @@ -1,4 +1,4 @@ -.\" $FreeBSD: src/usr.sbin/zic/zic.8,v 1.19 2005/02/13 23:45:54 ru Exp $ +.\" $FreeBSD: head/contrib/tzcode/zic/zic.8 214411 2010-10-27 07:14:46Z edwin $ .Dd June 20, 2004 .Dt ZIC 8 .Os @@ -119,10 +119,13 @@ Any line that is blank (after comment stripping) is ignored. Non-blank lines are expected to be of one of three types: rule lines, zone lines, and link lines. .Pp +Names (such as month names) must be in English and are case insensitive. +Abbreviations, if used, must be unambiguous in context. +.Pp A rule line has the form: -.Dl "Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S +.Dl "Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S" For example: -.Dl "Rule US 1967 1973 \- Apr lastSun 2:00 1:00 D +.Dl "Rule US 1967 1973 \- Apr lastSun 2:00 1:00 D" .Pp The fields that make up a rule line are: .Bl -tag -width "LETTER/S" -offset indent @@ -260,9 +263,9 @@ the variable part is null. .El .Pp A zone line has the form: -.Dl "Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL] +.Dl "Zone NAME GMTOFF RULES/SAVE FORMAT [UNTILYEAR [MONTH [DAY [TIME]]]]" For example: -.Dl "Zone Australia/Adelaide 9:30 Aus CST 1971 Oct 31 2:00 +.Dl "Zone Australia/Adelaide 9:30 Aus CST 1971 Oct 31 2:00" The fields that make up a zone line are: .Bl -tag -width indent .It NAME @@ -293,15 +296,15 @@ of the time zone abbreviation goes. Alternately, a slash (/) separates standard and daylight abbreviations. -.It UNTIL +.It UNTILYEAR [MONTH [DAY [TIME]]] The time at which the UTC offset or the rule(s) change for a location. It is specified as a year, a month, a day, and a time of day. If this is specified, the time zone information is generated from the given UTC offset and rule change until the time specified. The month, day, and time of day have the same format as the IN, ON, and AT -columns of a rule; trailing columns can be omitted, and default to the -earliest possible value for the missing columns. +fields of a rule; trailing fields can be omitted, and default to the +earliest possible value for the missing fields. .Pp The next line must be a .Dq continuation @@ -310,18 +313,18 @@ string .Dq Zone and the name are omitted, as the continuation line will place information starting at the time specified as the -.Em UNTIL -field in the previous line in the file used by the previous line. -Continuation lines may contain an -.Em UNTIL -field, just as zone lines do, indicating that the next line is a further +.Em until +information in the previous line in the file used by the previous line. +Continuation lines may contain +.Em until +information, just as zone lines do, indicating that the next line is a further continuation. .El .Pp A link line has the form -.Dl "Link LINK-FROM LINK-TO +.Dl "Link LINK-FROM LINK-TO" For example: -.Dl "Link Europe/Istanbul Asia/Istanbul +.Dl "Link Europe/Istanbul Asia/Istanbul" The .Em LINK-FROM field should appear as the @@ -335,9 +338,9 @@ Except for continuation lines, lines may appear in any order in the input. .Pp Lines in the file that describes leap seconds have the following form: -.Dl "Leap YEAR MONTH DAY HH:MM:SS CORR R/S +.Dl "Leap YEAR MONTH DAY HH:MM:SS CORR R/S" For example: -.Dl "Leap 1974 Dec 31 23:59:60 + S +.Dl "Leap 1974 Dec 31 23:59:60 + S" The .Em YEAR , .Em MONTH , @@ -376,12 +379,81 @@ or .Dq Rolling if the leap second time given by the other fields should be interpreted as local wall clock time. -.Sh NOTE +.Sh "EXTENDED EXAMPLE" +Here is an extended example of +.Nm +input, intended to illustrate many of its features. +.br +.ne 22 +.nf +.in +2m +.ta \w'# Rule\0\0'u +\w'NAME\0\0'u +\w'FROM\0\0'u +\w'1973\0\0'u +\w'TYPE\0\0'u +\w'Apr\0\0'u +\w'lastSun\0\0'u +\w'2:00\0\0'u +\w'SAVE\0\0'u +.sp +# Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S +Rule Swiss 1940 only - Nov 2 0:00 1:00 S +Rule Swiss 1940 only - Dec 31 0:00 0 - +Rule Swiss 1941 1942 - May Sun>=1 2:00 1:00 S +Rule Swiss 1941 1942 - Oct Sun>=1 0:00 0 +.sp .5 +Rule EU 1977 1980 - Apr Sun>=1 1:00u 1:00 S +Rule EU 1977 only - Sep lastSun 1:00u 0 - +Rule EU 1978 only - Oct 1 1:00u 0 - +Rule EU 1979 1995 - Sep lastSun 1:00u 0 - +Rule EU 1981 max - Mar lastSun 1:00u 1:00 S +Rule EU 1996 max - Oct lastSun 1:00u 0 - +.sp +.ta \w'# Zone\0\0'u +\w'Europe/Zurich\0\0'u +\w'0:34:08\0\0'u +\w'RULES/SAVE\0\0'u +\w'FORMAT\0\0'u +# Zone NAME GMTOFF RULES FORMAT UNTIL +Zone Europe/Zurich 0:34:08 - LMT 1848 Sep 12 + 0:29:44 - BMT 1894 Jun + 1:00 Swiss CE%sT 1981 + 1:00 EU CE%sT +.sp +Link Europe/Zurich Switzerland +.sp +.in +.fi +In this example, the zone is named Europe/Zurich but it has an alias +as Switzerland. +Zurich was 34 minutes and 8 seconds west of GMT until 1848-09-12 +at 00:00, when the offset changed to 29 minutes and 44 seconds. +After 1894-06-01 at 00:00 Swiss daylight saving rules (defined with +lines beginning with "Rule Swiss") apply, and the GMT offset became +one hour. +From 1981 to the present, EU daylight saving rules have applied, +and the UTC offset has remained at one hour. +.Pp +In 1940, daylight saving time applied from November 2 at 00:00 to +December 31 at 00:00. +In 1941 and 1942, daylight saving time applied from the first Sunday +in May at 02:00 to the first Sunday in October at 00:00. +The pre-1981 EU daylight-saving rules have no effect here, but are +included for completeness. +Since 1981, daylight saving has begun on the last Sunday in March +at 01:00 UTC. +Until 1995 it ended the last Sunday in September at 01:00 UTC, but +this changed to the last Sunday in October starting in 1996. +.Pp +For purposes of display, "LMT" and "BMT" were initially used, +respectively. +Since Swiss rules and later EU rules were applied, the display name +for the timezone has been CET for standard time and CEST for daylight +saving time. +.Sh NOTES For areas with more than two types of local time, you may need to use local standard time in the .Em AT field of the earliest transition time's rule to ensure that the earliest transition time recorded in the compiled file is correct. +.Pp +If, for a particular zone, a clock advance caused by the start of +daylight saving coincides with and is equal to a clock retreat +caused by a change in UTC offset, +.Nm +produces a single transition to daylight saving at the new UTC offset +(without any change in wall clock time). +To get separate transitions use multiple zone continuation lines +specifying transition instants using universal time. .Sh FILES .Bl -tag -width /usr/share/zoneinfo -compact .It /usr/share/zoneinfo @@ -391,4 +463,6 @@ standard directory used for created files .Xr ctime 3 , .Xr tzfile 5 , .Xr zdump 8 -.\" @(#)zic.8 7.18 +.\" @(#)zic.8 8.6 +.\" This file is in the public domain, so clarified as of +.\" 2009-05-17 by Arthur David Olson. diff --git a/zic.tproj/zic.c b/zic.tproj/zic.c index 8efedaa..75db5be 100644 --- a/zic.tproj/zic.c +++ b/zic.tproj/zic.c @@ -1,9 +1,13 @@ -static const char elsieid[] = "@(#)zic.c 7.116"; +/* +** This file is in the public domain, so clarified as of +** 2006-07-17 by Arthur David Olson. +*/ + +static const char elsieid[] = "@(#)zic.c 8.22"; -#include #ifndef lint -__unused static const char rcsid[] = - "$FreeBSD: src/usr.sbin/zic/zic.c,v 1.18 2007/12/03 10:45:44 kevlo Exp $"; +static const char rcsid[] = + "$FreeBSD: head/contrib/tzcode/zic/zic.c 214411 2010-10-27 07:14:46Z edwin $"; #endif /* not lint */ #include "private.h" @@ -14,11 +18,19 @@ __unused static const char rcsid[] = #include #include +#define ZIC_VERSION '2' + +typedef int_fast64_t zic_t; + +#ifndef ZIC_MAX_ABBR_LEN_WO_WARN +#define ZIC_MAX_ABBR_LEN_WO_WARN 6 +#endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */ + #define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) /* ** On some ancient hosts, predicates like `isspace(C)' are defined -** only if isascii(C) || C == EOF. Modern hosts obey the C Standard, +** only if isascii(C) || C == EOF. Modern hosts obey the C Standard, ** which says they are defined only if C == ((unsigned char) C) || C == EOF. ** Neither the C Standard nor POSIX require that `isascii' exist. ** For portability, we check both ancient and modern requirements. @@ -29,6 +41,11 @@ __unused static const char rcsid[] = #define isascii(x) 1 #endif +#define OFFSET_STRLEN_MAXIMUM (7 + INT_STRLEN_MAXIMUM(long)) +#define RULE_STRLEN_MAXIMUM 8 /* "Mdd.dd.d" */ + +#define end(cp) (strchr((cp), '\0')) + struct rule { const char * r_filename; int r_linenum; @@ -37,6 +54,8 @@ struct rule { int r_loyear; /* for example, 1986 */ int r_hiyear; /* for example, 1986 */ const char * r_yrtype; + int r_lowasnum; + int r_hiwasnum; int r_month; /* 0..11 */ @@ -53,7 +72,7 @@ struct rule { const char * r_abbrvar; /* variable part of abbreviation */ int r_todo; /* a rule to do (used in outzone) */ - time_t r_temp; /* used in outzone */ + zic_t r_temp; /* used in outzone */ }; /* @@ -79,73 +98,81 @@ struct zone { int z_nrules; struct rule z_untilrule; - time_t z_untiltime; + zic_t z_untiltime; }; -static void addtt P((time_t starttime, int type)); -static int addtype P((long gmtoff, const char * abbr, int isdst, - int ttisstd, int ttisgmt)); -static void leapadd P((time_t t, int positive, int rolling, int count)); -static void adjleap P((void)); -static void associate P((void)); -static int ciequal P((const char * ap, const char * bp)); -static void convert P((long val, char * buf)); -static void dolink P((const char * fromfile, const char * tofile)); -static void doabbr P((char * abbr, const char * format, - const char * letters, int isdst)); -static void eat P((const char * name, int num)); -static void eats P((const char * name, int num, - const char * rname, int rnum)); -static long eitol P((int i)); -static void error P((const char * message)); -static char ** getfields P((char * buf)); -static long gethms P((const char * string, const char * errstrng, - int signable)); -static void infile P((const char * filename)); -static void inleap P((char ** fields, int nfields)); -static void inlink P((char ** fields, int nfields)); -static void inrule P((char ** fields, int nfields)); -static int inzcont P((char ** fields, int nfields)); -static int inzone P((char ** fields, int nfields)); -static int inzsub P((char ** fields, int nfields, int iscont)); -static int itsabbr P((const char * abbr, const char * word)); -static int itsdir P((const char * name)); -static int lowerit P((int c)); -static char * memcheck P((char * tocheck)); -static int mkdirs P((char * filename)); -static void newabbr P((const char * abbr)); -static long oadd P((long t1, long t2)); -static void outzone P((const struct zone * zp, int ntzones)); -static void puttzcode P((long code, FILE * fp)); -static int rcomp P((const void * leftp, const void * rightp)); -static time_t rpytime P((const struct rule * rp, int wantedy)); -static void rulesub P((struct rule * rp, +static void addtt(zic_t starttime, int type); +static int addtype(long gmtoff, const char * abbr, int isdst, + int ttisstd, int ttisgmt); +static void leapadd(zic_t t, int positive, int rolling, int count); +static void adjleap(void); +static void associate(void); +static int ciequal(const char * ap, const char * bp); +static void convert(long val, char * buf); +static void convert64(zic_t val, char * buf); +static void dolink(const char * fromfield, const char * tofield); +static void doabbr(char * abbr, const char * format, + const char * letters, int isdst, int doquotes); +static void eat(const char * name, int num); +static void eats(const char * name, int num, + const char * rname, int rnum); +static long eitol(int i); +static void error(const char * message); +static char ** getfields(char * buf); +static long gethms(const char * string, const char * errstrng, + int signable); +static void infile(const char * filename); +static void inleap(char ** fields, int nfields); +static void inlink(char ** fields, int nfields); +static void inrule(char ** fields, int nfields); +static int inzcont(char ** fields, int nfields); +static int inzone(char ** fields, int nfields); +static int inzsub(char ** fields, int nfields, int iscont); +static int is32(zic_t x); +static int itsabbr(const char * abbr, const char * word); +static int itsdir(const char * name); +static int lowerit(int c); +static char * memcheck(char * tocheck); +static int mkdirs(char * filename); +static void newabbr(const char * abbr); +static long oadd(long t1, long t2); +static void outzone(const struct zone * zp, int ntzones); +static void puttzcode(long code, FILE * fp); +static void puttzcode64(zic_t code, FILE * fp); +static int rcomp(const void * leftp, const void * rightp); +static zic_t rpytime(const struct rule * rp, int wantedy); +static void rulesub(struct rule * rp, const char * loyearp, const char * hiyearp, const char * typep, const char * monthp, - const char * dayp, const char * timep)); -static void setboundaries P((void)); -static void setgroup P((gid_t *flag, const char *name)); -static void setuser P((uid_t *flag, const char *name)); -static time_t tadd P((time_t t1, long t2)); -static void usage P((void)); -static void writezone P((const char * name)); -static int yearistype P((int year, const char * type)); - -#if !(HAVE_STRERROR - 0) -static char * strerror P((int)); -#endif /* !(HAVE_STRERROR - 0) */ + const char * dayp, const char * timep); +static int stringoffset(char * result, long offset); +static int stringrule(char * result, const struct rule * rp, + long dstoff, long gmtoff); +static void stringzone(char * result, + const struct zone * zp, int ntzones); +static void setboundaries(void); +static void setgroup(gid_t *flag, const char *name); +static void setuser(uid_t *flag, const char *name); +static zic_t tadd(zic_t t1, long t2); +static void usage(FILE *stream, int status); +static void writezone(const char * name, const char * string); +static int yearistype(int year, const char * type); static int charcnt; static int errors; static const char * filename; static int leapcnt; +static int leapseen; +static int leapminyear; +static int leapmaxyear; static int linenum; -static time_t max_time; +static int max_abbrvar_len; +static int max_format_len; +static zic_t max_time; static int max_year; -static int max_year_representable; -static time_t min_time; +static zic_t min_time; static int min_year; -static int min_year_representable; +static zic_t min_time; static int noise; static const char * rfilename; static int rlinenum; @@ -254,8 +281,8 @@ struct lookup { const int l_value; }; -static struct lookup const * byword P((const char * string, - const struct lookup * lp)); +static struct lookup const * byword(const char * string, + const struct lookup * lp); static struct lookup const line_codes[] = { { "Rule", LC_RULE }, @@ -332,7 +359,7 @@ static const int len_years[2] = { }; static struct attype { - time_t at; + zic_t at; unsigned char type; } attypes[TZ_MAX_TIMES]; static long gmtoffs[TZ_MAX_TYPES]; @@ -341,7 +368,7 @@ static unsigned char abbrinds[TZ_MAX_TYPES]; static char ttisstds[TZ_MAX_TYPES]; static char ttisgmts[TZ_MAX_TYPES]; static char chars[TZ_MAX_CHARS]; -static time_t trans[TZ_MAX_LEAPS]; +static zic_t trans[TZ_MAX_LEAPS]; static long corr[TZ_MAX_LEAPS]; static char roll[TZ_MAX_LEAPS]; @@ -350,7 +377,8 @@ static char roll[TZ_MAX_LEAPS]; */ static char * -memcheck(char * const ptr) +memcheck(ptr) +char * const ptr; { if (ptr == NULL) errx(EXIT_FAILURE, _("memory exhausted")); @@ -366,21 +394,12 @@ memcheck(char * const ptr) ** Error handling. */ -#if !(HAVE_STRERROR - 0) -static char * -strerror(int errnum) -{ - extern char * sys_errlist[]; - extern int sys_nerr; - - return (errnum > 0 && errnum <= sys_nerr) ? - sys_errlist[errnum] : _("Unknown system error"); -} -#endif /* !(HAVE_STRERROR - 0) */ - static void -eats(const char * const name, const int num, const char * const rname, - const int rnum) +eats(name, num, rname, rnum) +const char * const name; +const int num; +const char * const rname; +const int rnum; { filename = name; linenum = num; @@ -389,13 +408,16 @@ eats(const char * const name, const int num, const char * const rname, } static void -eat(const char * const name, const int num) +eat(name, num) +const char * const name; +const int num; { eats(name, num, (char *) NULL, -1); } static void -error(const char * const string) +error(string) +const char * const string; { /* ** Match the format of "cc" to allow sh users to @@ -412,7 +434,8 @@ error(const char * const string) } static void -warning(const char * const string) +warning(string) +const char * const string; { char * cp; @@ -424,12 +447,14 @@ warning(const char * const string) } static void -usage P((void)) -{ - (void) fprintf(stderr, "%s\n%s\n", -_("usage: zic [--version] [-s] [-v] [-l localtime] [-p posixrules] [-d directory]"), -_(" [-L leapseconds] [-y yearistype] [filename ... ]")); - (void) exit(EXIT_FAILURE); +usage(FILE *stream, int status) + { + (void) fprintf(stream, _("usage is zic \ +[ --version ] [--help] [ -v ] [ -l localtime ] [ -p posixrules ] \\\n\ +\t[ -d directory ] [ -L leapseconds ] [ -y yearistype ] [ filename ... ]\n\ +\n\ +Report bugs to tz@elsie.nci.nih.gov.\n")); + exit(status); } static const char * psxrules; @@ -437,7 +462,6 @@ static const char * lcltime; static const char * directory; static const char * leapsec; static const char * yitcommand; -static int sflag = FALSE; static int Dflag; static uid_t uflag = (uid_t)-1; static gid_t gflag = (gid_t)-1; @@ -445,30 +469,39 @@ static mode_t mflag = (S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR); int -main(int argc, char * argv[]) +main(argc, argv) +int argc; +char * argv[]; { - int i; - int j; - int c; + register int i; + register int j; + register int c; #ifdef unix (void) umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH)); #endif /* defined unix */ -#if HAVE_GETTEXT - 0 - (void) setlocale(LC_MESSAGES, ""); +#if HAVE_GETTEXT + (void) setlocale(LC_ALL, ""); #ifdef TZ_DOMAINDIR (void) bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR); #endif /* defined TEXTDOMAINDIR */ (void) textdomain(TZ_DOMAIN); -#endif /* HAVE_GETTEXT - 0 */ +#endif /* HAVE_GETTEXT */ + if (TYPE_BIT(zic_t) < 64) { + (void) fprintf(stderr, "zic: %s\n", + _("wild compilation-time specification of zic_t")); + exit(EXIT_FAILURE); + } for (i = 1; i < argc; ++i) if (strcmp(argv[i], "--version") == 0) { errx(EXIT_SUCCESS, "%s", elsieid); + } else if (strcmp(argv[i], "--help") == 0) { + usage(stdout, EXIT_SUCCESS); } while ((c = getopt(argc, argv, "Dd:g:l:m:p:L:u:vsy:")) != -1) switch (c) { default: - usage(); + usage(stderr, EXIT_FAILURE); case 'D': Dflag = 1; break; @@ -527,11 +560,11 @@ _("more than one -L option specified")); noise = TRUE; break; case 's': - sflag = TRUE; + (void) printf("zic: -s ignored\n"); break; } if (optind == argc - 1 && strcmp(argv[optind], "=") == 0) - usage(); /* usage message by request */ + usage(stderr, EXIT_FAILURE); /* usage message by request */ if (directory == NULL) directory = TZDIR; if (yitcommand == NULL) @@ -547,7 +580,7 @@ _("more than one -L option specified")); for (i = optind; i < argc; ++i) infile(argv[i]); if (errors) - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); associate(); for (i = 0; i < nzones; i = j) { /* @@ -563,6 +596,11 @@ _("more than one -L option specified")); for (i = 0; i < nlinks; ++i) { eat(links[i].l_filename, links[i].l_linenum); dolink(links[i].l_from, links[i].l_to); + if (noise) + for (j = 0; j < nlinks; ++j) + if (strcmp(links[i].l_to, + links[j].l_from) == 0) + warning(_("link to link")); } if (lcltime != NULL) { eat("command line", 1); @@ -576,24 +614,26 @@ _("more than one -L option specified")); } static void -dolink(const char * const fromfile, const char * const tofile) +dolink(fromfield, tofield) +const char * const fromfield; +const char * const tofield; { - char * fromname; - char * toname; + register char * fromname; + register char * toname; - if (fromfile[0] == '/') - fromname = ecpyalloc(fromfile); + if (fromfield[0] == '/') + fromname = ecpyalloc(fromfield); else { fromname = ecpyalloc(directory); fromname = ecatalloc(fromname, "/"); - fromname = ecatalloc(fromname, fromfile); + fromname = ecatalloc(fromname, fromfield); } - if (tofile[0] == '/') - toname = ecpyalloc(tofile); + if (tofield[0] == '/') + toname = ecpyalloc(tofield); else { toname = ecpyalloc(directory); toname = ecatalloc(toname, "/"); - toname = ecatalloc(toname, tofile); + toname = ecatalloc(toname, tofield); } /* ** We get to be careful here since @@ -605,25 +645,30 @@ dolink(const char * const fromfile, const char * const tofile) int result; if (mkdirs(toname) != 0) - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); result = link(fromname, toname); -#if (HAVE_SYMLINK - 0) +#if HAVE_SYMLINK if (result != 0 && - access(fromname, F_OK) == 0 && - !itsdir(fromname)) { - const char *s = tofile; - char * symlinkcontents = NULL; - while ((s = strchr(s+1, '/')) != NULL) - symlinkcontents = ecatalloc(symlinkcontents, "../"); - symlinkcontents = ecatalloc(symlinkcontents, fromfile); - - result = symlink(symlinkcontents, toname); - if (result == 0) + access(fromname, F_OK) == 0 && + !itsdir(fromname)) { + const char *s = tofield; + register char * symlinkcontents = NULL; + while ((s = strchr(s+1, '/')) != NULL) + symlinkcontents = + ecatalloc(symlinkcontents, + "../"); + symlinkcontents = + ecatalloc(symlinkcontents, + fromname); + result = + symlink(symlinkcontents, + toname); + if (result == 0) warning(_("hard link failed, symbolic link used")); - ifree(symlinkcontents); + ifree(symlinkcontents); } -#endif +#endif /* HAVE_SYMLINK */ if (result != 0) { err(EXIT_FAILURE, _("can't link from %s to %s"), fromname, toname); @@ -633,50 +678,25 @@ warning(_("hard link failed, symbolic link used")); ifree(toname); } -#ifndef INT_MAX -#define INT_MAX ((int) (((unsigned)~0)>>1)) -#endif /* !defined INT_MAX */ - -#ifndef INT_MIN -#define INT_MIN ((int) ~(((unsigned)~0)>>1)) -#endif /* !defined INT_MIN */ - -/* -** The tz file format currently allows at most 32-bit quantities. -** This restriction should be removed before signed 32-bit values -** wrap around in 2038, but unfortunately this will require a -** change to the tz file format. -*/ - -#define MAX_BITS_IN_FILE 32 -#define TIME_T_BITS_IN_FILE ((TYPE_BIT(time_t) < MAX_BITS_IN_FILE) ? TYPE_BIT(time_t) : MAX_BITS_IN_FILE) +#define TIME_T_BITS_IN_FILE 64 static void -setboundaries P((void)) +setboundaries (void) { - if (TYPE_SIGNED(time_t)) { - min_time = ~ (time_t) 0; - min_time <<= TIME_T_BITS_IN_FILE - 1; - max_time = ~ (time_t) 0 - min_time; - if (sflag) - min_time = 0; - } else { - min_time = 0; - max_time = 2 - sflag; - max_time <<= TIME_T_BITS_IN_FILE - 1; - --max_time; - } - min_year = TM_YEAR_BASE + gmtime(&min_time)->tm_year; - max_year = TM_YEAR_BASE + gmtime(&max_time)->tm_year; - min_year_representable = min_year; - max_year_representable = max_year; + register int i; + + min_time = -1; + for (i = 0; i < TIME_T_BITS_IN_FILE - 1; ++i) + min_time *= 2; + max_time = -(min_time + 1); } static int -itsdir(const char * const name) +itsdir(name) +const char * const name; { - char * myname; - int accres; + register char * myname; + register int accres; myname = ecpyalloc(name); myname = ecatalloc(myname, "/."); @@ -694,19 +714,21 @@ itsdir(const char * const name) */ static int -rcomp(const void *cp1, const void *cp2) +rcomp(cp1, cp2) +const void * cp1; +const void * cp2; { return strcmp(((const struct rule *) cp1)->r_name, ((const struct rule *) cp2)->r_name); } static void -associate P((void)) +associate(void) { - struct zone * zp; - struct rule * rp; - int base, out; - int i, j; + register struct zone * zp; + register struct rule * rp; + register int base, out; + register int i, j; if (nrules != 0) { (void) qsort((void *) rules, (size_t) nrules, @@ -763,7 +785,7 @@ associate P((void)) */ eat(zp->z_filename, zp->z_linenum); zp->z_stdoff = gethms(zp->z_rule, _("unruly zone"), - TRUE); + TRUE); /* ** Note, though, that if there's no rule, ** a '%s' in the format is a bad thing. @@ -773,20 +795,21 @@ associate P((void)) } } if (errors) - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } static void -infile(const char * name) +infile(name) +const char * name; { - FILE * fp; - char ** fields; - char * cp; - const struct lookup * lp; - int nfields; - int wantcont; - int num; - char buf[BUFSIZ]; + register FILE * fp; + register char ** fields; + register char * cp; + register const struct lookup * lp; + register int nfields; + register int wantcont; + register int num; + char buf[BUFSIZ]; if (strcmp(name, "-") == 0) { name = _("standard input"); @@ -801,7 +824,7 @@ infile(const char * name) cp = strchr(buf, '\n'); if (cp == NULL) { error(_("line too long")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } *cp = '\0'; fields = getfields(buf); @@ -864,9 +887,13 @@ _("panic: invalid l_value %d"), lp->l_value); */ static long -gethms(const char *string, const char * const errstring, const int signable) +gethms(string, errstring, signable) +const char * string; +const char * const errstring; +const int signable; { - int hh, mm, ss, sign; + long hh; + int mm, ss, sign; if (string == NULL || *string == '\0') return 0; @@ -876,31 +903,38 @@ gethms(const char *string, const char * const errstring, const int signable) sign = -1; ++string; } else sign = 1; - if (sscanf(string, scheck(string, "%d"), &hh) == 1) + if (sscanf(string, scheck(string, "%ld"), &hh) == 1) mm = ss = 0; - else if (sscanf(string, scheck(string, "%d:%d"), &hh, &mm) == 2) + else if (sscanf(string, scheck(string, "%ld:%d"), &hh, &mm) == 2) ss = 0; - else if (sscanf(string, scheck(string, "%d:%d:%d"), + else if (sscanf(string, scheck(string, "%ld:%d:%d"), &hh, &mm, &ss) != 3) { error(errstring); return 0; } - if ((hh < 0 || hh >= HOURSPERDAY || + if (hh < 0 || mm < 0 || mm >= MINSPERHOUR || - ss < 0 || ss > SECSPERMIN) && - !(hh == HOURSPERDAY && mm == 0 && ss == 0)) { + ss < 0 || ss > SECSPERMIN) { error(errstring); return 0; } - if (noise && hh == HOURSPERDAY) + if (LONG_MAX / SECSPERHOUR < hh) { + error(_("time overflow")); + return 0; + } + if (noise && hh == HOURSPERDAY && mm == 0 && ss == 0) warning(_("24:00 not handled by pre-1998 versions of zic")); - return eitol(sign) * - (eitol(hh * MINSPERHOUR + mm) * - eitol(SECSPERMIN) + eitol(ss)); + if (noise && (hh > HOURSPERDAY || + (hh == HOURSPERDAY && (mm != 0 || ss != 0)))) +warning(_("values over 24 hours not handled by pre-2007 versions of zic")); + return oadd(eitol(sign) * hh * eitol(SECSPERHOUR), + eitol(sign) * (eitol(mm) * eitol(SECSPERMIN) + eitol(ss))); } static void -inrule(char ** const fields, const int nfields) +inrule(fields, nfields) +register char ** const fields; +const int nfields; { static struct rule r; @@ -919,15 +953,19 @@ inrule(char ** const fields, const int nfields) fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]); r.r_name = ecpyalloc(fields[RF_NAME]); r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]); + if (max_abbrvar_len < strlen(r.r_abbrvar)) + max_abbrvar_len = strlen(r.r_abbrvar); rules = (struct rule *) (void *) erealloc((char *) rules, (int) ((nrules + 1) * sizeof *rules)); rules[nrules++] = r; } static int -inzone(char ** const fields, const int nfields) +inzone(fields, nfields) +register char ** const fields; +const int nfields; { - int i; + register int i; static char * buf; if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) { @@ -968,7 +1006,9 @@ _("duplicate zone name %s (file \"%s\", line %d)"), } static int -inzcont(char ** const fields, const int nfields) +inzcont(fields, nfields) +register char ** const fields; +const int nfields; { if (nfields < ZONEC_MINFIELDS || nfields > ZONEC_MAXFIELDS) { error(_("wrong number of fields on Zone continuation line")); @@ -978,14 +1018,17 @@ inzcont(char ** const fields, const int nfields) } static int -inzsub(char ** const fields, const int nfields, const int iscont) +inzsub(fields, nfields, iscont) +register char ** const fields; +const int nfields; +const int iscont; { - char * cp; + register char * cp; static struct zone z; - int i_gmtoff, i_rule, i_format; - int i_untilyear, i_untilmonth; - int i_untilday, i_untiltime; - int hasuntil; + register int i_gmtoff, i_rule, i_format; + register int i_untilyear, i_untilmonth; + register int i_untilday, i_untiltime; + register int hasuntil; if (iscont) { i_gmtoff = ZFC_GMTOFF; @@ -1017,6 +1060,8 @@ inzsub(char ** const fields, const int nfields, const int iscont) } z.z_rule = ecpyalloc(fields[i_rule]); z.z_format = ecpyalloc(fields[i_format]); + if (max_format_len < strlen(z.z_format)) + max_format_len = strlen(z.z_format); hasuntil = nfields > i_untilyear; if (hasuntil) { z.z_untilrule.r_filename = filename; @@ -1037,7 +1082,9 @@ inzsub(char ** const fields, const int nfields, const int iscont) zones[nzones - 1].z_untiltime > min_time && zones[nzones - 1].z_untiltime < max_time && zones[nzones - 1].z_untiltime >= z.z_untiltime) { - error(_("Zone continuation line end time is not after end time of previous line")); + error(_( +"Zone continuation line end time is not after end time of previous line" + )); return FALSE; } } @@ -1052,14 +1099,16 @@ inzsub(char ** const fields, const int nfields, const int iscont) } static void -inleap(char ** const fields, const int nfields) +inleap(fields, nfields) +register char ** const fields; +const int nfields; { - const char * cp; - const struct lookup * lp; - int i, j; - int year, month, day; - long dayoff, tod; - time_t t; + register const char * cp; + register const struct lookup * lp; + register int i, j; + int year, month, day; + long dayoff, tod; + zic_t t; if (nfields != LEAP_FIELDS) { error(_("wrong number of fields on Leap line")); @@ -1068,12 +1117,17 @@ inleap(char ** const fields, const int nfields) dayoff = 0; cp = fields[LP_YEAR]; if (sscanf(cp, scheck(cp, "%d"), &year) != 1) { - /* - * Leapin' Lizards! - */ - error(_("invalid leaping year")); - return; + /* + ** Leapin' Lizards! + */ + error(_("invalid leaping year")); + return; } + if (!leapseen || leapmaxyear < year) + leapmaxyear = year; + if (!leapseen || leapminyear > year) + leapminyear = year; + leapseen = TRUE; j = EPOCH_YEAR; while (j != year) { if (year > j) { @@ -1103,7 +1157,7 @@ inleap(char ** const fields, const int nfields) return; } dayoff = oadd(dayoff, eitol(day - 1)); - if (dayoff < 0 && !TYPE_SIGNED(time_t)) { + if (dayoff < 0 && !TYPE_SIGNED(zic_t)) { error(_("time before zero")); return; } @@ -1115,12 +1169,12 @@ inleap(char ** const fields, const int nfields) error(_("time too large")); return; } - t = (time_t) dayoff * SECSPERDAY; + t = (zic_t) dayoff * SECSPERDAY; tod = gethms(fields[LP_TIME], _("invalid time of day"), FALSE); cp = fields[LP_CORR]; { - int positive; - int count; + register int positive; + int count; if (strcmp(cp, "") == 0) { /* infile() turns "-" into "" */ positive = FALSE; @@ -1139,7 +1193,9 @@ inleap(char ** const fields, const int nfields) return; } if ((lp = byword(fields[LP_ROLL], leap_types)) == NULL) { - error(_("illegal Rolling/Stationary field on Leap line")); + error(_( + "illegal Rolling/Stationary field on Leap line" + )); return; } leapadd(tadd(t, tod), positive, lp->l_value, count); @@ -1147,7 +1203,9 @@ inleap(char ** const fields, const int nfields) } static void -inlink(char ** const fields, const int nfields) +inlink(fields, nfields) +register char ** const fields; +const int nfields; { struct link l; @@ -1173,15 +1231,19 @@ inlink(char ** const fields, const int nfields) } static void -rulesub(struct rule * const rp, const char * const loyearp, - const char * const hiyearp, const char * const typep, - const char * const monthp, const char * const dayp, - const char * const timep) +rulesub(rp, loyearp, hiyearp, typep, monthp, dayp, timep) +register struct rule * const rp; +const char * const loyearp; +const char * const hiyearp; +const char * const typep; +const char * const monthp; +const char * const dayp; +const char * const timep; { - const struct lookup * lp; - const char * cp; - char * dp; - char * ep; + register const struct lookup * lp; + register const char * cp; + register char * dp; + register char * ep; if ((lp = byword(monthp, mon_names)) == NULL) { error(_("invalid month name")); @@ -1220,7 +1282,8 @@ rulesub(struct rule * const rp, const char * const loyearp, */ cp = loyearp; lp = byword(cp, begin_years); - if (lp != NULL) switch ((int) lp->l_value) { + rp->r_lowasnum = lp == NULL; + if (!rp->r_lowasnum) switch ((int) lp->l_value) { case YR_MINIMUM: rp->r_loyear = INT_MIN; break; @@ -1233,14 +1296,11 @@ rulesub(struct rule * const rp, const char * const loyearp, } else if (sscanf(cp, scheck(cp, "%d"), &rp->r_loyear) != 1) { error(_("invalid starting year")); return; - } else if (noise) { - if (rp->r_loyear < min_year_representable) - warning(_("starting year too low to be represented")); - else if (rp->r_loyear > max_year_representable) - warning(_("starting year too high to be represented")); } cp = hiyearp; - if ((lp = byword(cp, end_years)) != NULL) switch ((int) lp->l_value) { + lp = byword(cp, end_years); + rp->r_hiwasnum = lp == NULL; + if (!rp->r_hiwasnum) switch ((int) lp->l_value) { case YR_MINIMUM: rp->r_hiyear = INT_MIN; break; @@ -1256,11 +1316,6 @@ rulesub(struct rule * const rp, const char * const loyearp, } else if (sscanf(cp, scheck(cp, "%d"), &rp->r_hiyear) != 1) { error(_("invalid ending year")); return; - } else if (noise) { - if (rp->r_loyear < min_year_representable) - warning(_("ending year too low to be represented")); - else if (rp->r_loyear > max_year_representable) - warning(_("ending year too high to be represented")); } if (rp->r_loyear > rp->r_hiyear) { error(_("starting year greater than ending year")); @@ -1275,8 +1330,6 @@ rulesub(struct rule * const rp, const char * const loyearp, } rp->r_yrtype = ecpyalloc(typep); } - if (rp->r_loyear < min_year && rp->r_loyear > 0) - min_year = rp->r_loyear; /* ** Day work. ** Accept things such as: @@ -1325,17 +1378,33 @@ rulesub(struct rule * const rp, const char * const loyearp, } static void -convert(const long val, char * const buf) +convert(val, buf) +const long val; +char * const buf; { - int i; - long shift; + register int i; + register int shift; for (i = 0, shift = 24; i < 4; ++i, shift -= 8) buf[i] = val >> shift; } static void -puttzcode(const long val, FILE * const fp) +convert64(val, buf) +const zic_t val; +char * const buf; +{ + register int i; + register int shift; + + for (i = 0, shift = 56; i < 8; ++i, shift -= 8) + buf[i] = val >> shift; +} + +static void +puttzcode(val, fp) +const long val; +FILE * const fp; { char buf[4]; @@ -1343,25 +1412,50 @@ puttzcode(const long val, FILE * const fp) (void) fwrite((void *) buf, (size_t) sizeof buf, (size_t) 1, fp); } +static void +puttzcode64(val, fp) +const zic_t val; +FILE * const fp; +{ + char buf[8]; + + convert64(val, buf); + (void) fwrite((void *) buf, (size_t) sizeof buf, (size_t) 1, fp); +} + static int -atcomp(const void *avp, const void *bvp) +atcomp(avp, bvp) +const void * avp; +const void * bvp; { - if (((const struct attype *) avp)->at < ((const struct attype *) bvp)->at) - return -1; - else if (((const struct attype *) avp)->at > ((const struct attype *) bvp)->at) - return 1; - else return 0; + const zic_t a = ((const struct attype *) avp)->at; + const zic_t b = ((const struct attype *) bvp)->at; + + return (a < b) ? -1 : (a > b); +} + +static int +is32(x) +const zic_t x; +{ + return INT32_MIN <= x && x <= INT32_MAX; } static void -writezone(const char * const name) +writezone(name, string) +const char * const name; +const char * const string; { - FILE * fp; - int i, j; - static char * fullname; - static struct tzhead tzh; - time_t ats[TZ_MAX_TIMES]; - unsigned char types[TZ_MAX_TIMES]; + register FILE * fp; + register int i, j; + register int leapcnt32, leapi32; + register int timecnt32, timei32; + register int pass; + static char * fullname; + static const struct tzhead tzh0; + static struct tzhead tzh; + zic_t ats[TZ_MAX_TIMES]; + unsigned char types[TZ_MAX_TIMES]; /* ** Sort. @@ -1384,14 +1478,13 @@ writezone(const char * const name) while (fromi < timecnt && attypes[fromi].type == 0) ++fromi; /* handled by default rule */ for ( ; fromi < timecnt; ++fromi) { - if (toi != 0 - && ((attypes[fromi].at - + gmtoffs[attypes[toi - 1].type]) - <= (attypes[toi - 1].at - + gmtoffs[toi == 1 ? 0 - : attypes[toi - 2].type]))) { - attypes[toi - 1].type = attypes[fromi].type; - continue; + if (toi != 0 && ((attypes[fromi].at + + gmtoffs[attypes[toi - 1].type]) <= + (attypes[toi - 1].at + gmtoffs[toi == 1 ? 0 + : attypes[toi - 2].type]))) { + attypes[toi - 1].type = + attypes[fromi].type; + continue; } if (toi == 0 || attypes[toi - 1].type != attypes[fromi].type) @@ -1406,6 +1499,36 @@ writezone(const char * const name) ats[i] = attypes[i].at; types[i] = attypes[i].type; } + /* + ** Correct for leap seconds. + */ + for (i = 0; i < timecnt; ++i) { + j = leapcnt; + while (--j >= 0) + if (ats[i] > trans[j] - corr[j]) { + ats[i] = tadd(ats[i], corr[j]); + break; + } + } + /* + ** Figure out 32-bit-limited starts and counts. + */ + timecnt32 = timecnt; + timei32 = 0; + leapcnt32 = leapcnt; + leapi32 = 0; + while (timecnt32 > 0 && !is32(ats[timecnt32 - 1])) + --timecnt32; + while (timecnt32 > 0 && !is32(ats[timei32])) { + --timecnt32; + ++timei32; + } + while (leapcnt32 > 0 && !is32(trans[leapcnt32 - 1])) + --leapcnt32; + while (leapcnt32 > 0 && !is32(trans[leapi32])) { + --leapcnt32; + ++leapi32; + } fullname = erealloc(fullname, (int) (strlen(directory) + 1 + strlen(name) + 1)); (void) sprintf(fullname, "%s/%s", directory, name); @@ -1418,70 +1541,201 @@ writezone(const char * const name) if ((fp = fopen(fullname, "wb")) == NULL) { if (mkdirs(fullname) != 0) - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); if ((fp = fopen(fullname, "wb")) == NULL) err(EXIT_FAILURE, _("can't create %s"), fullname); } - convert(eitol(typecnt), tzh.tzh_ttisgmtcnt); - convert(eitol(typecnt), tzh.tzh_ttisstdcnt); - convert(eitol(leapcnt), tzh.tzh_leapcnt); - convert(eitol(timecnt), tzh.tzh_timecnt); - convert(eitol(typecnt), tzh.tzh_typecnt); - convert(eitol(charcnt), tzh.tzh_charcnt); - (void) strncpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic); -#define DO(field) (void) fwrite((void *) tzh.field, (size_t) sizeof tzh.field, (size_t) 1, fp) - DO(tzh_magic); - DO(tzh_reserved); - DO(tzh_ttisgmtcnt); - DO(tzh_ttisstdcnt); - DO(tzh_leapcnt); - DO(tzh_timecnt); - DO(tzh_typecnt); - DO(tzh_charcnt); -#undef DO - for (i = 0; i < timecnt; ++i) { - j = leapcnt; - while (--j >= 0) - if (ats[i] >= trans[j]) { - ats[i] = tadd(ats[i], corr[j]); - break; + for (pass = 1; pass <= 2; ++pass) { + register int thistimei, thistimecnt; + register int thisleapi, thisleapcnt; + register int thistimelim, thisleaplim; + int writetype[TZ_MAX_TIMES]; + int typemap[TZ_MAX_TYPES]; + register int thistypecnt; + char thischars[TZ_MAX_CHARS]; + char thischarcnt; + int indmap[TZ_MAX_CHARS]; + + if (pass == 1) { + thistimei = timei32; + thistimecnt = timecnt32; + thisleapi = leapi32; + thisleapcnt = leapcnt32; + } else { + thistimei = 0; + thistimecnt = timecnt; + thisleapi = 0; + thisleapcnt = leapcnt; + } + thistimelim = thistimei + thistimecnt; + thisleaplim = thisleapi + thisleapcnt; + for (i = 0; i < typecnt; ++i) + writetype[i] = thistimecnt == timecnt; + if (thistimecnt == 0) { + /* + ** No transition times fall in the current + ** (32- or 64-bit) window. + */ + if (typecnt != 0) + writetype[typecnt - 1] = TRUE; + } else { + for (i = thistimei - 1; i < thistimelim; ++i) + if (i >= 0) + writetype[types[i]] = TRUE; + /* + ** For America/Godthab and Antarctica/Palmer + */ + if (thistimei == 0) + writetype[0] = TRUE; + } +#ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH + /* + ** For some pre-2011 systems: if the last-to-be-written + ** standard (or daylight) type has an offset different from the + ** most recently used offset, + ** append an (unused) copy of the most recently used type + ** (to help get global "altzone" and "timezone" variables + ** set correctly). + */ + { + register int mrudst, mrustd, hidst, histd, type; + + hidst = histd = mrudst = mrustd = -1; + for (i = thistimei; i < thistimelim; ++i) + if (isdsts[types[i]]) + mrudst = types[i]; + else mrustd = types[i]; + for (i = 0; i < typecnt; ++i) + if (writetype[i]) { + if (isdsts[i]) + hidst = i; + else histd = i; + } + if (hidst >= 0 && mrudst >= 0 && hidst != mrudst && + gmtoffs[hidst] != gmtoffs[mrudst]) { + isdsts[mrudst] = -1; + type = addtype(gmtoffs[mrudst], + &chars[abbrinds[mrudst]], + TRUE, + ttisstds[mrudst], + ttisgmts[mrudst]); + isdsts[mrudst] = TRUE; + writetype[type] = TRUE; } - puttzcode((long) ats[i], fp); - } - if (timecnt > 0) - (void) fwrite((void *) types, (size_t) sizeof types[0], - (size_t) timecnt, fp); - for (i = 0; i < typecnt; ++i) { - puttzcode((long) gmtoffs[i], fp); - (void) putc(isdsts[i], fp); - (void) putc(abbrinds[i], fp); - } - if (charcnt != 0) - (void) fwrite((void *) chars, (size_t) sizeof chars[0], - (size_t) charcnt, fp); - for (i = 0; i < leapcnt; ++i) { - if (roll[i]) { - if (timecnt == 0 || trans[i] < ats[0]) { - j = 0; - while (isdsts[j]) - if (++j >= typecnt) { - j = 0; - break; - } - } else { - j = 1; - while (j < timecnt && trans[i] >= ats[j]) - ++j; - j = types[j - 1]; + if (histd >= 0 && mrustd >= 0 && histd != mrustd && + gmtoffs[histd] != gmtoffs[mrustd]) { + isdsts[mrustd] = -1; + type = addtype(gmtoffs[mrustd], + &chars[abbrinds[mrustd]], + FALSE, + ttisstds[mrustd], + ttisgmts[mrustd]); + isdsts[mrustd] = FALSE; + writetype[type] = TRUE; + } + } +#endif /* !defined LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH */ + thistypecnt = 0; + for (i = 0; i < typecnt; ++i) + typemap[i] = writetype[i] ? thistypecnt++ : -1; + for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i) + indmap[i] = -1; + thischarcnt = 0; + for (i = 0; i < typecnt; ++i) { + register char * thisabbr; + + if (!writetype[i]) + continue; + if (indmap[abbrinds[i]] >= 0) + continue; + thisabbr = &chars[abbrinds[i]]; + for (j = 0; j < thischarcnt; ++j) + if (strcmp(&thischars[j], thisabbr) == 0) + break; + if (j == thischarcnt) { + (void) strcpy(&thischars[(int) thischarcnt], + thisabbr); + thischarcnt += strlen(thisabbr) + 1; } - puttzcode((long) tadd(trans[i], -gmtoffs[j]), fp); - } else puttzcode((long) trans[i], fp); - puttzcode((long) corr[i], fp); - } - for (i = 0; i < typecnt; ++i) - (void) putc(ttisstds[i], fp); - for (i = 0; i < typecnt; ++i) - (void) putc(ttisgmts[i], fp); + indmap[abbrinds[i]] = j; + } +#define DO(field) (void) fwrite((void *) tzh.field, \ + (size_t) sizeof tzh.field, (size_t) 1, fp) + tzh = tzh0; + (void) strncpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic); + tzh.tzh_version[0] = ZIC_VERSION; + convert(eitol(thistypecnt), tzh.tzh_ttisgmtcnt); + convert(eitol(thistypecnt), tzh.tzh_ttisstdcnt); + convert(eitol(thisleapcnt), tzh.tzh_leapcnt); + convert(eitol(thistimecnt), tzh.tzh_timecnt); + convert(eitol(thistypecnt), tzh.tzh_typecnt); + convert(eitol(thischarcnt), tzh.tzh_charcnt); + DO(tzh_magic); + DO(tzh_version); + DO(tzh_reserved); + DO(tzh_ttisgmtcnt); + DO(tzh_ttisstdcnt); + DO(tzh_leapcnt); + DO(tzh_timecnt); + DO(tzh_typecnt); + DO(tzh_charcnt); +#undef DO + for (i = thistimei; i < thistimelim; ++i) + if (pass == 1) + puttzcode((long) ats[i], fp); + else puttzcode64(ats[i], fp); + for (i = thistimei; i < thistimelim; ++i) { + unsigned char uc; + + uc = typemap[types[i]]; + (void) fwrite((void *) &uc, + (size_t) sizeof uc, + (size_t) 1, + fp); + } + for (i = 0; i < typecnt; ++i) + if (writetype[i]) { + puttzcode(gmtoffs[i], fp); + (void) putc(isdsts[i], fp); + (void) putc((unsigned char) indmap[abbrinds[i]], fp); + } + if (thischarcnt != 0) + (void) fwrite((void *) thischars, + (size_t) sizeof thischars[0], + (size_t) thischarcnt, fp); + for (i = thisleapi; i < thisleaplim; ++i) { + register zic_t todo; + + if (roll[i]) { + if (timecnt == 0 || trans[i] < ats[0]) { + j = 0; + while (isdsts[j]) + if (++j >= typecnt) { + j = 0; + break; + } + } else { + j = 1; + while (j < timecnt && + trans[i] >= ats[j]) + ++j; + j = types[j - 1]; + } + todo = tadd(trans[i], -gmtoffs[j]); + } else todo = trans[i]; + if (pass == 1) + puttzcode((long) todo, fp); + else puttzcode64(todo, fp); + puttzcode(corr[i], fp); + } + for (i = 0; i < typecnt; ++i) + if (writetype[i]) + (void) putc(ttisstds[i], fp); + for (i = 0; i < typecnt; ++i) + if (writetype[i]) + (void) putc(ttisgmts[i], fp); + } + (void) fprintf(fp, "\n%s\n", string); if (ferror(fp) || fclose(fp)) errx(EXIT_FAILURE, _("error writing %s"), fullname); if (chmod(fullname, mflag) < 0) @@ -1494,38 +1748,254 @@ writezone(const char * const name) } static void -doabbr(char * const abbr, const char * const format, const char * const letters, - const int isdst) +doabbr(abbr, format, letters, isdst, doquotes) +char * const abbr; +const char * const format; +const char * const letters; +const int isdst; +const int doquotes; { - if (strchr(format, '/') == NULL) { + register char * cp; + register char * slashp; + register int len; + + slashp = strchr(format, '/'); + if (slashp == NULL) { if (letters == NULL) (void) strcpy(abbr, format); else (void) sprintf(abbr, format, letters); - } else if (isdst) - (void) strcpy(abbr, strchr(format, '/') + 1); - else { - (void) strcpy(abbr, format); - *strchr(abbr, '/') = '\0'; + } else if (isdst) { + (void) strcpy(abbr, slashp + 1); + } else { + if (slashp > format) + (void) strncpy(abbr, format, + (unsigned) (slashp - format)); + abbr[slashp - format] = '\0'; + } + if (!doquotes) + return; + for (cp = abbr; *cp != '\0'; ++cp) + if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", *cp) == NULL && + strchr("abcdefghijklmnopqrstuvwxyz", *cp) == NULL) + break; + len = strlen(abbr); + if (len > 0 && *cp == '\0') + return; + abbr[len + 2] = '\0'; + abbr[len + 1] = '>'; + for ( ; len > 0; --len) + abbr[len] = abbr[len - 1]; + abbr[0] = '<'; +} + +static void +updateminmax(x) +const int x; +{ + if (min_year > x) + min_year = x; + if (max_year < x) + max_year = x; +} + +static int +stringoffset(result, offset) +char * result; +long offset; +{ + register int hours; + register int minutes; + register int seconds; + + result[0] = '\0'; + if (offset < 0) { + (void) strcpy(result, "-"); + offset = -offset; + } + seconds = offset % SECSPERMIN; + offset /= SECSPERMIN; + minutes = offset % MINSPERHOUR; + offset /= MINSPERHOUR; + hours = offset; + if (hours >= HOURSPERDAY) { + result[0] = '\0'; + return -1; + } + (void) sprintf(end(result), "%d", hours); + if (minutes != 0 || seconds != 0) { + (void) sprintf(end(result), ":%02d", minutes); + if (seconds != 0) + (void) sprintf(end(result), ":%02d", seconds); + } + return 0; +} + +static int +stringrule(result, rp, dstoff, gmtoff) +char * result; +const struct rule * const rp; +const long dstoff; +const long gmtoff; +{ + register long tod; + + result = end(result); + if (rp->r_dycode == DC_DOM) { + register int month, total; + + if (rp->r_dayofmonth == 29 && rp->r_month == TM_FEBRUARY) + return -1; + total = 0; + for (month = 0; month < rp->r_month; ++month) + total += len_months[0][month]; + (void) sprintf(result, "J%d", total + rp->r_dayofmonth); + } else { + register int week; + + if (rp->r_dycode == DC_DOWGEQ) { + week = 1 + rp->r_dayofmonth / DAYSPERWEEK; + if ((week - 1) * DAYSPERWEEK + 1 != rp->r_dayofmonth) + return -1; + } else if (rp->r_dycode == DC_DOWLEQ) { + if (rp->r_dayofmonth == len_months[1][rp->r_month]) + week = 5; + else { + week = 1 + rp->r_dayofmonth / DAYSPERWEEK; + if (week * DAYSPERWEEK - 1 != rp->r_dayofmonth) + return -1; + } + } else return -1; /* "cannot happen" */ + (void) sprintf(result, "M%d.%d.%d", + rp->r_month + 1, week, rp->r_wday); + } + tod = rp->r_tod; + if (rp->r_todisgmt) + tod += gmtoff; + if (rp->r_todisstd && rp->r_stdoff == 0) + tod += dstoff; + if (tod < 0) { + result[0] = '\0'; + return -1; + } + if (tod != 2 * SECSPERMIN * MINSPERHOUR) { + (void) strcat(result, "/"); + if (stringoffset(end(result), tod) != 0) + return -1; } + return 0; } static void -outzone(const struct zone * const zpfirst, const int zonecount) +stringzone(result, zpfirst, zonecount) +char * result; +const struct zone * const zpfirst; +const int zonecount; { - const struct zone * zp; - struct rule * rp; - int i, j; - int usestart, useuntil; - time_t starttime, untiltime; - long gmtoff; - long stdoff; - int year; - long startoff; - int startttisstd; - int startttisgmt; - int type; - char startbuf[BUFSIZ]; + register const struct zone * zp; + register struct rule * rp; + register struct rule * stdrp; + register struct rule * dstrp; + register int i; + register const char * abbrvar; + + result[0] = '\0'; + zp = zpfirst + zonecount - 1; + stdrp = dstrp = NULL; + for (i = 0; i < zp->z_nrules; ++i) { + rp = &zp->z_rules[i]; + if (rp->r_hiwasnum || rp->r_hiyear != INT_MAX) + continue; + if (rp->r_yrtype != NULL) + continue; + if (rp->r_stdoff == 0) { + if (stdrp == NULL) + stdrp = rp; + else return; + } else { + if (dstrp == NULL) + dstrp = rp; + else return; + } + } + if (stdrp == NULL && dstrp == NULL) { + /* + ** There are no rules running through "max". + ** Let's find the latest rule. + */ + for (i = 0; i < zp->z_nrules; ++i) { + rp = &zp->z_rules[i]; + if (stdrp == NULL || rp->r_hiyear > stdrp->r_hiyear || + (rp->r_hiyear == stdrp->r_hiyear && + rp->r_month > stdrp->r_month)) + stdrp = rp; + } + if (stdrp != NULL && stdrp->r_stdoff != 0) + return; /* We end up in DST (a POSIX no-no). */ + /* + ** Horrid special case: if year is 2037, + ** presume this is a zone handled on a year-by-year basis; + ** do not try to apply a rule to the zone. + */ + if (stdrp != NULL && stdrp->r_hiyear == 2037) + return; + } + if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_stdoff != 0)) + return; + abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar; + doabbr(result, zp->z_format, abbrvar, FALSE, TRUE); + if (stringoffset(end(result), -zp->z_gmtoff) != 0) { + result[0] = '\0'; + return; + } + if (dstrp == NULL) + return; + doabbr(end(result), zp->z_format, dstrp->r_abbrvar, TRUE, TRUE); + if (dstrp->r_stdoff != SECSPERMIN * MINSPERHOUR) + if (stringoffset(end(result), + -(zp->z_gmtoff + dstrp->r_stdoff)) != 0) { + result[0] = '\0'; + return; + } + (void) strcat(result, ","); + if (stringrule(result, dstrp, dstrp->r_stdoff, zp->z_gmtoff) != 0) { + result[0] = '\0'; + return; + } + (void) strcat(result, ","); + if (stringrule(result, stdrp, dstrp->r_stdoff, zp->z_gmtoff) != 0) { + result[0] = '\0'; + return; + } +} +static void +outzone(zpfirst, zonecount) +const struct zone * const zpfirst; +const int zonecount; +{ + register const struct zone * zp; + register struct rule * rp; + register int i, j; + register int usestart, useuntil; + register zic_t starttime, untiltime; + register long gmtoff; + register long stdoff; + register int year; + register long startoff; + register int startttisstd; + register int startttisgmt; + register int type; + register char * startbuf; + register char * ab; + register char * envvar; + register int max_abbr_len; + register int max_envvar_len; + + max_abbr_len = 2 + max_format_len + max_abbrvar_len; + max_envvar_len = 2 * max_abbr_len + 5 * 9; + startbuf = emalloc(max_abbr_len + 1); + ab = emalloc(max_abbr_len + 1); + envvar = emalloc(max_envvar_len + 1); INITIALIZE(untiltime); INITIALIZE(starttime); /* @@ -1535,11 +2005,57 @@ outzone(const struct zone * const zpfirst, const int zonecount) typecnt = 0; charcnt = 0; /* - ** Thanks to Earl Chew (earl@dnd.icp.nec.com.au) + ** Thanks to Earl Chew ** for noting the need to unconditionally initialize startttisstd. */ startttisstd = FALSE; startttisgmt = FALSE; + min_year = max_year = EPOCH_YEAR; + if (leapseen) { + updateminmax(leapminyear); + updateminmax(leapmaxyear + (leapmaxyear < INT_MAX)); + } + for (i = 0; i < zonecount; ++i) { + zp = &zpfirst[i]; + if (i < zonecount - 1) + updateminmax(zp->z_untilrule.r_loyear); + for (j = 0; j < zp->z_nrules; ++j) { + rp = &zp->z_rules[j]; + if (rp->r_lowasnum) + updateminmax(rp->r_loyear); + if (rp->r_hiwasnum) + updateminmax(rp->r_hiyear); + } + } + /* + ** Generate lots of data if a rule can't cover all future times. + */ + stringzone(envvar, zpfirst, zonecount); + if (noise && envvar[0] == '\0') { + register char * wp; + +wp = ecpyalloc(_("no POSIX environment variable for zone")); + wp = ecatalloc(wp, " "); + wp = ecatalloc(wp, zpfirst->z_name); + warning(wp); + ifree(wp); + } + if (envvar[0] == '\0') { + if (min_year >= INT_MIN + YEARSPERREPEAT) + min_year -= YEARSPERREPEAT; + else min_year = INT_MIN; + if (max_year <= INT_MAX - YEARSPERREPEAT) + max_year += YEARSPERREPEAT; + else max_year = INT_MAX; + } + /* + ** For the benefit of older systems, + ** generate data from 1900 through 2037. + */ + if (min_year > 1900) + min_year = 1900; + if (max_year < 2037) + max_year = 2037; for (i = 0; i < zonecount; ++i) { /* ** A guess that may well be corrected later. @@ -1557,7 +2073,7 @@ outzone(const struct zone * const zpfirst, const int zonecount) if (zp->z_nrules == 0) { stdoff = zp->z_stdoff; doabbr(startbuf, zp->z_format, - (char *) NULL, stdoff != 0); + (char *) NULL, stdoff != 0, FALSE); type = addtype(oadd(zp->z_gmtoff, stdoff), startbuf, stdoff != 0, startttisstd, startttisgmt); @@ -1584,10 +2100,9 @@ outzone(const struct zone * const zpfirst, const int zonecount) rp->r_temp = rpytime(rp, year); } for ( ; ; ) { - int k; - time_t jtime, ktime; - long offset; - char buf[BUFSIZ]; + register int k; + register zic_t jtime, ktime; + register long offset; INITIALIZE(ktime); if (useuntil) { @@ -1643,23 +2158,27 @@ outzone(const struct zone * const zpfirst, const int zonecount) stdoff); doabbr(startbuf, zp->z_format, rp->r_abbrvar, - rp->r_stdoff != 0); + rp->r_stdoff != 0, + FALSE); continue; } if (*startbuf == '\0' && - startoff == oadd(zp->z_gmtoff, - stdoff)) { - doabbr(startbuf, zp->z_format, - rp->r_abbrvar, - rp->r_stdoff != 0); + startoff == oadd(zp->z_gmtoff, + stdoff)) { + doabbr(startbuf, + zp->z_format, + rp->r_abbrvar, + rp->r_stdoff != + 0, + FALSE); } } eats(zp->z_filename, zp->z_linenum, rp->r_filename, rp->r_linenum); - doabbr(buf, zp->z_format, rp->r_abbrvar, - rp->r_stdoff != 0); + doabbr(ab, zp->z_format, rp->r_abbrvar, + rp->r_stdoff != 0, FALSE); offset = oadd(zp->z_gmtoff, rp->r_stdoff); - type = addtype(offset, buf, rp->r_stdoff != 0, + type = addtype(offset, ab, rp->r_stdoff != 0, rp->r_todisstd, rp->r_todisgmt); addtt(ktime, type); } @@ -1692,11 +2211,16 @@ error(_("can't determine time zone abbreviation to use just after until time")); starttime = tadd(starttime, -gmtoff); } } - writezone(zpfirst->z_name); + writezone(zpfirst->z_name, envvar); + ifree(startbuf); + ifree(ab); + ifree(envvar); } static void -addtt(const time_t starttime, int type) +addtt(starttime, type) +const zic_t starttime; +int type; { if (starttime <= min_time || (timecnt == 1 && attypes[0].at < min_time)) { @@ -1707,14 +2231,14 @@ addtt(const time_t starttime, int type) if (abbrinds[type] != 0) (void) strcpy(chars, &chars[abbrinds[type]]); abbrinds[0] = 0; - charcnt = (int)strlen(chars) + 1; + charcnt = strlen(chars) + 1; typecnt = 1; timecnt = 0; type = 0; } if (timecnt >= TZ_MAX_TIMES) { error(_("too many transitions?!")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } attypes[timecnt].at = starttime; attypes[timecnt].type = type; @@ -1722,22 +2246,26 @@ addtt(const time_t starttime, int type) } static int -addtype(const long gmtoff, const char * const abbr, const int isdst, - const int ttisstd, const int ttisgmt) +addtype(gmtoff, abbr, isdst, ttisstd, ttisgmt) +const long gmtoff; +const char * const abbr; +const int isdst; +const int ttisstd; +const int ttisgmt; { - int i, j; + register int i, j; if (isdst != TRUE && isdst != FALSE) { error(_("internal error - addtype called with bad isdst")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } if (ttisstd != TRUE && ttisstd != FALSE) { error(_("internal error - addtype called with bad ttisstd")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } if (ttisgmt != TRUE && ttisgmt != FALSE) { error(_("internal error - addtype called with bad ttisgmt")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } /* ** See if there's already an entry for this zone type. @@ -1756,7 +2284,11 @@ addtype(const long gmtoff, const char * const abbr, const int isdst, */ if (typecnt >= TZ_MAX_TYPES) { error(_("too many local time types")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); + } + if (! (-1L - 2147483647L <= gmtoff && gmtoff <= 2147483647L)) { + error(_("UTC offset out of range")); + exit(EXIT_FAILURE); } gmtoffs[i] = gmtoff; isdsts[i] = isdst; @@ -1774,19 +2306,23 @@ addtype(const long gmtoff, const char * const abbr, const int isdst, } static void -leapadd(const time_t t, const int positive, const int rolling, int count) +leapadd(t, positive, rolling, count) +const zic_t t; +const int positive; +const int rolling; +int count; { - int i, j; + register int i, j; if (leapcnt + (positive ? count : 1) > TZ_MAX_LEAPS) { error(_("too many leap seconds")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } for (i = 0; i < leapcnt; ++i) if (t <= trans[i]) { if (t == trans[i]) { error(_("repeated leap second moment")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } break; } @@ -1804,10 +2340,10 @@ leapadd(const time_t t, const int positive, const int rolling, int count) } static void -adjleap P((void)) +adjleap(void) { - int i; - long last = 0; + register int i; + register long last = 0; /* ** propagate leap seconds forward @@ -1819,7 +2355,9 @@ adjleap P((void)) } static int -yearistype(const int year, const char * const type) +yearistype(year, type) +const int year; +const char * const type; { static char * buf; int result; @@ -1838,19 +2376,21 @@ yearistype(const int year, const char * const type) error(_("wild result from command execution")); warnx(_("command was '%s', result was %d"), buf, result); for ( ; ; ) - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } static int -lowerit(int a) +lowerit(a) +int a; { a = (unsigned char) a; return (isascii(a) && isupper(a)) ? tolower(a) : a; } -/* case-insensitive equality */ static int -ciequal(const char *ap, const char *bp) +ciequal(ap, bp) /* case-insensitive equality */ +register const char * ap; +register const char * bp; { while (lowerit(*ap) == lowerit(*bp++)) if (*ap++ == '\0') @@ -1859,7 +2399,9 @@ ciequal(const char *ap, const char *bp) } static int -itsabbr(const char *abbr, const char *word) +itsabbr(abbr, word) +register const char * abbr; +register const char * word; { if (lowerit(*abbr) != lowerit(*word)) return FALSE; @@ -1873,10 +2415,12 @@ itsabbr(const char *abbr, const char *word) } static const struct lookup * -byword(const char * const word, const struct lookup * const table) +byword(word, table) +register const char * const word; +register const struct lookup * const table; { - const struct lookup * foundlp; - const struct lookup * lp; + register const struct lookup * foundlp; + register const struct lookup * lp; if (word == NULL || table == NULL) return NULL; @@ -1900,11 +2444,12 @@ byword(const char * const word, const struct lookup * const table) } static char ** -getfields(char *cp) +getfields(cp) +register char * cp; { - char * dp; - char ** array; - int nsubs; + register char * dp; + register char ** array; + register int nsubs; if (cp == NULL) return NULL; @@ -1912,8 +2457,9 @@ getfields(char *cp) emalloc((int) ((strlen(cp) + 1) * sizeof *array)); nsubs = 0; for ( ; ; ) { - while (isascii(*cp) && isspace((unsigned char) *cp)) - ++cp; + while (isascii((unsigned char) *cp) && + isspace((unsigned char) *cp)) + ++cp; if (*cp == '\0' || *cp == '#') break; array[nsubs++] = dp = cp; @@ -1938,22 +2484,26 @@ getfields(char *cp) } static long -oadd(const long t1, const long t2) +oadd(t1, t2) +const long t1; +const long t2; { - long t; + register long t; t = t1 + t2; if ((t2 > 0 && t <= t1) || (t2 < 0 && t >= t1)) { error(_("time overflow")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } return t; } -static time_t -tadd(const time_t t1, const long t2) +static zic_t +tadd(t1, t2) +const zic_t t1; +const long t2; { - time_t t; + register zic_t t; if (t1 == max_time && t2 > 0) return max_time; @@ -1962,7 +2512,7 @@ tadd(const time_t t1, const long t2) t = t1 + t2; if ((t2 > 0 && t <= t1) || (t2 < 0 && t >= t1)) { error(_("time overflow")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } return t; } @@ -1972,12 +2522,14 @@ tadd(const time_t t1, const long t2) ** 1970, 00:00 LOCAL time - in that year that the rule refers to. */ -static time_t -rpytime(const struct rule * const rp, const int wantedy) +static zic_t +rpytime(rp, wantedy) +register const struct rule * const rp; +register const int wantedy; { - int y, m, i; - long dayoff; /* with a nod to Margaret O. */ - time_t t; + register int y, m, i; + register long dayoff; /* with a nod to Margaret O. */ + register zic_t t; if (wantedy == INT_MIN) return min_time; @@ -2007,13 +2559,13 @@ rpytime(const struct rule * const rp, const int wantedy) --i; else { error(_("use of 2/29 in non leap-year")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } } --i; dayoff = oadd(dayoff, eitol(i)); if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ) { - long wday; + register long wday; #define LDAYSPERWEEK ((long) DAYSPERWEEK) wday = eitol(EPOCH_WDAY); @@ -2041,38 +2593,77 @@ rpytime(const struct rule * const rp, const int wantedy) } if (i < 0 || i >= len_months[isleap(y)][m]) { if (noise) - warning(_("rule goes past start/end of month--will not work with pre-2004 versions of zic")); + warning(_("rule goes past start/end of month--\ +will not work with pre-2004 versions of zic")); } } - if (dayoff < 0 && !TYPE_SIGNED(time_t)) - return min_time; if (dayoff < min_time / SECSPERDAY) return min_time; if (dayoff > max_time / SECSPERDAY) return max_time; - t = (time_t) dayoff * SECSPERDAY; + t = (zic_t) dayoff * SECSPERDAY; return tadd(t, rp->r_tod); } static void -newabbr(const char * const string) +newabbr(string) +const char * const string; { - int i; + register int i; - i = (int)strlen(string) + 1; + if (strcmp(string, GRANDPARENTED) != 0) { + register const char * cp; + register char * wp; + + /* + ** Want one to ZIC_MAX_ABBR_LEN_WO_WARN alphabetics + ** optionally followed by a + or - and a number from 1 to 14. + */ + cp = string; + wp = NULL; + while (isascii((unsigned char) *cp) && + isalpha((unsigned char) *cp)) + ++cp; + if (cp - string == 0) +wp = _("time zone abbreviation lacks alphabetic at start"); + if (noise && cp - string > 3) +wp = _("time zone abbreviation has more than 3 alphabetics"); + if (cp - string > ZIC_MAX_ABBR_LEN_WO_WARN) +wp = _("time zone abbreviation has too many alphabetics"); + if (wp == NULL && (*cp == '+' || *cp == '-')) { + ++cp; + if (isascii((unsigned char) *cp) && + isdigit((unsigned char) *cp)) + if (*cp++ == '1' && + *cp >= '0' && *cp <= '4') + ++cp; + } + if (*cp != '\0') +wp = _("time zone abbreviation differs from POSIX standard"); + if (wp != NULL) { + wp = ecpyalloc(wp); + wp = ecatalloc(wp, " ("); + wp = ecatalloc(wp, string); + wp = ecatalloc(wp, ")"); + warning(wp); + ifree(wp); + } + } + i = strlen(string) + 1; if (charcnt + i > TZ_MAX_CHARS) { error(_("too many, or too long, time zone abbreviations")); - (void) exit(EXIT_FAILURE); + exit(EXIT_FAILURE); } (void) strcpy(&chars[charcnt], string); charcnt += eitol(i); } static int -mkdirs(char * const argname) +mkdirs(argname) +char * argname; { - char * name; - char * cp; + register char * name; + register char * cp; if (argname == NULL || *argname == '\0' || Dflag) return 0; @@ -2110,7 +2701,8 @@ mkdirs(char * const argname) } static long -eitol(const int i) +eitol(i) +const int i; { long l; @@ -2124,7 +2716,9 @@ eitol(const int i) #include static void -setgroup(gid_t *flag, const char *name) +setgroup(flag, name) + gid_t *flag; + const char *name; { struct group *gr; @@ -2138,7 +2732,7 @@ setgroup(gid_t *flag, const char *name) ul = strtoul(name, &ep, 10); if (ul == (unsigned long)(gid_t)ul && *ep == '\0') { - *flag = (gid_t)ul; + *flag = ul; return; } errx(EXIT_FAILURE, _("group `%s' not found"), name); @@ -2147,7 +2741,9 @@ setgroup(gid_t *flag, const char *name) } static void -setuser(uid_t *flag, const char *name) +setuser(flag, name) + uid_t *flag; + const char *name; { struct passwd *pw; @@ -2161,7 +2757,7 @@ setuser(uid_t *flag, const char *name) ul = strtoul(name, &ep, 10); if (ul == (unsigned long)(gid_t)ul && *ep == '\0') { - *flag = (uid_t)ul; + *flag = ul; return; } errx(EXIT_FAILURE, _("user `%s' not found"), name); diff --git a/zprint.tproj/zprint.c b/zprint.tproj/zprint.c index 104161c..34283f2 100644 --- a/zprint.tproj/zprint.c +++ b/zprint.tproj/zprint.c @@ -97,9 +97,12 @@ static int find_deltas(mach_zone_name_t *, task_zone_info_t *, task_zone_info_t static void colprintzoneheader(void); static boolean_t substr(const char *a, size_t alen, const char *b, size_t blen); -static int SortName(const void * left, const void * right); -static int SortSize(const void * left, const void * right); -static void PrintLarge(mach_memory_info_t *wiredInfo, unsigned int wiredInfoCnt, int (*func)(const void *, const void *), boolean_t column); +static int SortName(void * thunk, const void * left, const void * right); +static int SortSize(void * thunk, const void * left, const void * right); +static void PrintLarge(mach_memory_info_t *wiredInfo, unsigned int wiredInfoCnt, + task_zone_info_t *zoneInfo, mach_zone_name_t *zoneNames, + unsigned int zoneCnt, uint64_t zoneElements, + int (*func)(void *, const void *, const void *), boolean_t column); static char *program; @@ -149,6 +152,7 @@ main(int argc, char **argv) unsigned int wiredInfoCnt = 0; task_zone_info_t *max_info = NULL; char *deltas = NULL; + uint64_t zoneElements; kern_return_t kr; int i, j; @@ -316,6 +320,7 @@ main(int argc, char **argv) } must_print = find_deltas(name, info, max_info, deltas, infoCnt, first_time); + zoneElements = 0; if (must_print) { if (ColFormat) { if (!first_time) @@ -328,12 +333,14 @@ main(int argc, char **argv) colprintzone(&name[i], &info[i]); else printzone(&name[i], &info[i]); + zoneElements += info[i].tzi_count; } } } if (ShowLarge && first_time) { - PrintLarge(wiredInfo, wiredInfoCnt, + PrintLarge(wiredInfo, wiredInfoCnt, &info[0], &name[0], + nameCnt, zoneElements, SortZones ? &SortSize : &SortName, ColFormat); } @@ -629,8 +636,10 @@ kern_vm_counter_name(uint64_t tag) case (VM_KERN_COUNT_MANAGED): name = "VM_KERN_COUNT_MANAGED"; break; case (VM_KERN_COUNT_RESERVED): name = "VM_KERN_COUNT_RESERVED"; break; case (VM_KERN_COUNT_WIRED): name = "VM_KERN_COUNT_WIRED"; break; + case (VM_KERN_COUNT_WIRED_BOOT): name = "VM_KERN_COUNT_WIRED_BOOT"; break; case (VM_KERN_COUNT_WIRED_MANAGED): name = "VM_KERN_COUNT_WIRED_MANAGED"; break; case (VM_KERN_COUNT_STOLEN): name = "VM_KERN_COUNT_STOLEN"; break; + case (VM_KERN_COUNT_BOOT_STOLEN): name = "VM_KERN_COUNT_BOOT_STOLEN"; break; case (VM_KERN_COUNT_LOPAGE): name = "VM_KERN_COUNT_LOPAGE"; break; case (VM_KERN_COUNT_MAP_KERNEL): name = "VM_KERN_COUNT_MAP_KERNEL"; break; case (VM_KERN_COUNT_MAP_ZONE): name = "VM_KERN_COUNT_MAP_ZONE"; break; @@ -661,10 +670,12 @@ static CFMutableDictionaryRef gTagDict; static mach_memory_info_t * gSites; static char * -GetSiteName(int siteIdx) +GetSiteName(int siteIdx, mach_zone_name_t * zoneNames, unsigned int zoneNamesCnt) { const char * name; + uintptr_t kmodid; char * result; + char * append; mach_vm_address_t addr; CFDictionaryRef kextInfo; CFStringRef bundleID; @@ -681,7 +692,13 @@ GetSiteName(int siteIdx) site = &gSites[siteIdx]; addr = site->site; type = (VM_KERN_SITE_TYPE & site->flags); - switch (type) + kmodid = 0; + + if (VM_KERN_SITE_NAMED & site->flags) + { + asprintf(&result, "%s", &site->name[0]); + } + else switch (type) { case VM_KERN_SITE_TAG: result = kern_vm_tag_name(addr); @@ -692,14 +709,18 @@ GetSiteName(int siteIdx) break; case VM_KERN_SITE_KMOD: - kextInfo = CFDictionaryGetValue(gTagDict, (const void *)(uintptr_t) addr); + + kmodid = (uintptr_t) addr; + kextInfo = CFDictionaryGetValue(gTagDict, (const void *)kmodid); if (kextInfo) { bundleID = (CFStringRef)CFDictionaryGetValue(kextInfo, kCFBundleIdentifierKey); name = CFStringGetCStringPtr(bundleID, kCFStringEncodingUTF8); // wiredSize = (CFNumberRef)CFDictionaryGetValue(kextInfo, CFSTR(kOSBundleWiredSizeKey)); } - asprintf(&result, "%-64s%3lld", name ? name : "(unknown kmod)", addr); + + if (name) asprintf(&result, "%s", name); + else asprintf(&result, "(unloaded kmod)"); break; case VM_KERN_SITE_KERNEL: @@ -726,24 +747,63 @@ GetSiteName(int siteIdx) break; } - return (result); + if (result + && (VM_KERN_SITE_ZONE & site->flags) + && zoneNames + && (site->zone < zoneNamesCnt)) + { + size_t namelen, zonelen; + namelen = strlen(result); + zonelen = strnlen(zoneNames[site->zone].mzn_name, sizeof(zoneNames[site->zone].mzn_name)); + if (((namelen + zonelen) > 61) && (zonelen < 61)) namelen = (61 - zonelen); + asprintf(&append, "%.*s[%.*s]", + (int)namelen, + result, + (int)zonelen, + zoneNames[site->zone].mzn_name); + free(result); + result = append; + } + if (result && kmodid) + { + asprintf(&append, "%-64s%3ld", result, kmodid); + free(result); + result = append; + } + + return (result); } +struct CompareThunk +{ + mach_zone_name_t *zoneNames; + unsigned int zoneNamesCnt; +}; + static int -SortName(const void * left, const void * right) +SortName(void * thunk, const void * left, const void * right) { + const struct CompareThunk * t = (typeof(t)) thunk; const int * idxL; const int * idxR; char * l; char * r; + CFStringRef lcf; + CFStringRef rcf; int result; idxL = (typeof(idxL)) left; idxR = (typeof(idxR)) right; - l = GetSiteName(*idxL); - r = GetSiteName(*idxR); + l = GetSiteName(*idxL, t->zoneNames, t->zoneNamesCnt); + r = GetSiteName(*idxR, t->zoneNames, t->zoneNamesCnt); + + lcf = CFStringCreateWithCString(kCFAllocatorDefault, l, kCFStringEncodingUTF8); + rcf = CFStringCreateWithCString(kCFAllocatorDefault, r, kCFStringEncodingUTF8); + + result = (int) CFStringCompareWithOptionsAndLocale(lcf, rcf, CFRangeMake(0, CFStringGetLength(lcf)), kCFCompareNumerically, NULL); - result = strcmp(l, r); + CFRelease(lcf); + CFRelease(rcf); free(l); free(r); @@ -751,7 +811,7 @@ SortName(const void * left, const void * right) } static int -SortSize(const void * left, const void * right) +SortSize(void * thunk, const void * left, const void * right) { const mach_memory_info_t * siteL; const mach_memory_info_t * siteR; @@ -771,10 +831,14 @@ SortSize(const void * left, const void * right) static void PrintLarge(mach_memory_info_t *wiredInfo, unsigned int wiredInfoCnt, - int (*func)(const void *, const void *), boolean_t column) + task_zone_info_t *zoneInfo, mach_zone_name_t *zoneNames, + unsigned int zoneCnt, uint64_t zoneElements, + int (*func)(void *, const void *, const void *), boolean_t column) { uint64_t zonetotal; uint64_t top_wired; + uint64_t size; + uint64_t elemsTagged; CFDictionaryRef allKexts; unsigned int idx, site, first; @@ -802,36 +866,55 @@ PrintLarge(mach_memory_info_t *wiredInfo, unsigned int wiredInfoCnt, for (idx = 0; idx < wiredInfoCnt; idx++) sorted[idx] = idx; first = 0; // VM_KERN_MEMORY_FIRST_DYNAMIC - qsort(&sorted[first], + struct CompareThunk thunk; + thunk.zoneNames = zoneNames; + thunk.zoneNamesCnt = zoneCnt; + qsort_r(&sorted[first], wiredInfoCnt - first, sizeof(sorted[0]), + &thunk, func); + elemsTagged = 0; for (headerPrinted = false, idx = 0; idx < wiredInfoCnt; idx++) { site = sorted[idx]; - if (!gSites[site].size) continue; - if (VM_KERN_COUNT_WIRED == gSites[site].site) top_wired = gSites[site].size; + if ((VM_KERN_SITE_COUNTER & gSites[site].flags) + && (VM_KERN_COUNT_WIRED == gSites[site].site)) top_wired = gSites[site].size; if (VM_KERN_SITE_HIDE & gSites[site].flags) continue; - if (!(VM_KERN_SITE_WIRED & gSites[site].flags)) continue; + if (!((VM_KERN_SITE_WIRED | VM_KERN_SITE_ZONE) & gSites[site].flags)) continue; + + if ((VM_KERN_SITE_ZONE & gSites[site].flags) + && gSites[site].zone < zoneCnt) + { + elemsTagged += gSites[site].size / zoneInfo[gSites[site].zone].tzi_elem_size; + } + + if ((gSites[site].size < 1024) && (gSites[site].peak < 1024)) continue; - name = GetSiteName(site); + name = GetSiteName(site, zoneNames, zoneCnt); if (!substr(zname, znamelen, name, strlen(name))) continue; if (!headerPrinted) { printf("-------------------------------------------------------------------------------------------------------------\n"); - printf(" kmod vm cur\n"); - printf("wired memory id tag size\n"); + printf(" kmod vm peak cur\n"); + printf("wired memory id tag size waste size\n"); printf("-------------------------------------------------------------------------------------------------------------\n"); headerPrinted = true; } printf("%-67s", name); free(name); - printf("%12d", site); + printf("%12d", gSites[site].tag); - printf(" %11s", ""); - PRINTK(" %12llu", gSites[site].size); - totalsize += gSites[site].size; + if (gSites[site].peak) PRINTK(" %10llu", gSites[site].peak); + else printf(" %11s", ""); + + if (gSites[site].collectable_bytes) PRINTK(" %5llu", gSites[site].collectable_bytes); + else printf(" %6s", ""); + + PRINTK(" %9llu", gSites[site].size); + + if (!(VM_KERN_SITE_ZONE & gSites[site].flags)) totalsize += gSites[site].size; printf("\n"); } @@ -841,37 +924,49 @@ PrintLarge(mach_memory_info_t *wiredInfo, unsigned int wiredInfoCnt, printf("%-67s", "zones"); printf("%12s", ""); printf(" %11s", ""); - PRINTK(" %12llu", zonetotal); + printf(" %6s", ""); + PRINTK(" %9llu", zonetotal); printf("\n"); } if (headerPrinted) { + if (elemsTagged) + { + snprintf(totalstr, sizeof(totalstr), "%lld of %lld", elemsTagged, zoneElements); + printf("zone tags%100s\n", totalstr); + } snprintf(totalstr, sizeof(totalstr), "%6.2fM of %6.2fM", totalsize / 1024.0 / 1024.0, top_wired / 1024.0 / 1024.0); - printf("total%100s\n", totalstr); + printf("total%104s\n", totalstr); } for (headerPrinted = false, idx = 0; idx < wiredInfoCnt; idx++) { site = sorted[idx]; - if (!gSites[site].size) continue; + size = gSites[site].mapped; + if (!size) continue; if (VM_KERN_SITE_HIDE & gSites[site].flags) continue; - if (VM_KERN_SITE_WIRED & gSites[site].flags) continue; + if ((size == gSites[site].size) + && ((VM_KERN_SITE_WIRED | VM_KERN_SITE_ZONE) & gSites[site].flags)) continue; - name = GetSiteName(site); + name = GetSiteName(site, NULL, 0); if (!substr(zname, znamelen, name, strlen(name))) continue; if (!headerPrinted) { printf("-------------------------------------------------------------------------------------------------------------\n"); - printf(" largest\n"); - printf("maps free free size\n"); + printf(" largest peak cur\n"); + printf("maps free free size size\n"); printf("-------------------------------------------------------------------------------------------------------------\n"); headerPrinted = true; } - printf("%-67s", name); + printf("%-55s", name); free(name); - PRINTK(" %10llu", gSites[site].free); - PRINTK(" %10llu", gSites[site].largest); - PRINTK(" %12llu", gSites[site].size); + if (gSites[site].free) PRINTK(" %10llu", gSites[site].free); + else printf(" %11s", ""); + if (gSites[site].largest) PRINTK(" %10llu", gSites[site].largest); + else printf(" %11s", ""); + if (gSites[site].peak) PRINTK(" %10llu", gSites[site].peak); + else printf(" %11s", ""); + PRINTK(" %16llu", size); printf("\n"); } -- 2.45.2