Index: lib/sanitizer_common/sanitizer_procmaps.h =================================================================== --- lib/sanitizer_common/sanitizer_procmaps.h +++ lib/sanitizer_common/sanitizer_procmaps.h @@ -70,6 +70,8 @@ bool NextSegmentLoad(uptr *start, uptr *end, uptr *offset, char filename[], uptr filename_size, ModuleArch *arch, u8 *uuid, uptr *protection); + void GetSegmentAddrRange(uptr *start, uptr *end, uptr vmaddr, uptr vmsize); + bool ParseCurrentImageHeader(); int current_image_; u32 current_magic_; u32 current_filetype_; Index: lib/sanitizer_common/sanitizer_procmaps_mac.cc =================================================================== --- lib/sanitizer_common/sanitizer_procmaps_mac.cc +++ lib/sanitizer_common/sanitizer_procmaps_mac.cc @@ -18,6 +18,7 @@ #include #include +#include // These are not available in older macOS SDKs. #ifndef CPU_SUBTYPE_X86_64_H @@ -71,6 +72,12 @@ internal_memset(current_uuid_, 0, kModuleUUIDSize); } +// The dyld load address should be unchanged throughout process execution, +// and it is expensive to compute once many libraries have been loaded, +// so cache it here and do not reset. +static uptr dyld_hdr = 0; +static const char kDyldPath[] = "/usr/lib/dyld"; + // static void MemoryMappingLayout::CacheMemoryMappings() { // No-op on Mac for now. @@ -95,14 +102,12 @@ const char *lc = current_load_cmd_addr_; current_load_cmd_addr_ += ((const load_command *)lc)->cmdsize; if (((const load_command *)lc)->cmd == kLCSegment) { - const sptr dlloff = _dyld_get_image_vmaddr_slide(current_image_); const SegmentCommand* sc = (const SegmentCommand *)lc; - if (start) *start = sc->vmaddr + dlloff; + GetSegmentAddrRange(start, end, sc->vmaddr, sc->vmsize); if (protection) { // Return the initial protection. *protection = sc->initprot; } - if (end) *end = sc->vmaddr + sc->vmsize + dlloff; if (offset) { if (current_filetype_ == /*MH_EXECUTE*/ 0x2) { *offset = sc->vmaddr; @@ -111,8 +116,12 @@ } } if (filename) { - internal_strncpy(filename, _dyld_get_image_name(current_image_), - filename_size); + if (current_image_ < 0) { + internal_strncpy(filename, kDyldPath, filename_size); + } else { + internal_strncpy(filename, _dyld_get_image_name(current_image_), + filename_size); + } } if (arch) { *arch = current_arch_; @@ -180,37 +189,104 @@ return false; } -bool MemoryMappingLayout::Next(uptr *start, uptr *end, uptr *offset, - char filename[], uptr filename_size, - uptr *protection, ModuleArch *arch, u8 *uuid) { - for (; current_image_ >= 0; current_image_--) { - const mach_header* hdr = _dyld_get_image_header(current_image_); - if (!hdr) continue; - if (current_load_cmd_count_ < 0) { - // Set up for this image; - current_load_cmd_count_ = hdr->ncmds; - current_magic_ = hdr->magic; - current_filetype_ = hdr->filetype; - current_arch_ = ModuleArchFromCpuType(hdr->cputype, hdr->cpusubtype); - switch (current_magic_) { -#ifdef MH_MAGIC_64 - case MH_MAGIC_64: { - current_load_cmd_addr_ = (char*)hdr + sizeof(mach_header_64); - break; +void MemoryMappingLayout::GetSegmentAddrRange(uptr *start, uptr *end, + uptr vmaddr, uptr vmsize) { + if (current_image_ < 0) { + // vmaddr is masked with 0xfffff because on macOS versions < 10.12, + // it contains an absolute address rather than an offset for dyld. + // To make matters even more complicated, this absolute address + // isn't actually the absolute segment address, but the offset portion + // of the address is accurate when combined with the dyld base address, + // and the mask will give just this offset. + if (start) *start = (vmaddr & 0xfffff) + dyld_hdr; + if (end) *end = (vmaddr & 0xfffff) + vmsize + dyld_hdr; + } else { + const sptr dlloff = _dyld_get_image_vmaddr_slide(current_image_); + if (start) *start = vmaddr + dlloff; + if (end) *end = vmaddr + vmsize + dlloff; + } +} + +// _dyld_get_image_header() and related APIs don't report dyld itself. +// We work around this by manually recursing through the memory map +// until we hit a Mach header matching dyld instead. These recurse +// calls are expensive, but the first memory map generation occurs +// early in the process, when dyld is one of the only images loaded, +// so it will be hit after only a few iterations. +static const struct mach_header *get_dyld_image_header() { + if (!dyld_hdr) { + mach_port_name_t port; + if (task_for_pid(mach_task_self(), internal_getpid(), &port) != + KERN_SUCCESS) { + return nullptr; + } + + unsigned depth = 1; + vm_size_t size = 0; + vm_address_t address = 0; + kern_return_t err = KERN_SUCCESS; + mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64; + + while (err == KERN_SUCCESS) { + struct vm_region_submap_info_64 info; + err = vm_region_recurse_64(port, &address, &size, &depth, + (vm_region_info_t)&info, &count); + if (size >= sizeof(struct mach_header) && + info.protection & MemoryMappingLayout::kProtectionRead) { + struct mach_header *hdr = (struct mach_header *)address; + if ((hdr->magic == MH_MAGIC || hdr->magic == MH_MAGIC_64) && + hdr->filetype == MH_DYLINKER) { + dyld_hdr = (uptr)hdr; + return hdr; } + } + address += size; + } + + return nullptr; + } + + return (const struct mach_header *)dyld_hdr; +} + +bool MemoryMappingLayout::ParseCurrentImageHeader() { + const struct mach_header *hdr = (current_image_ < 0) + ? get_dyld_image_header() + : _dyld_get_image_header(current_image_); + + if (!hdr) return false; + if (current_load_cmd_count_ < 0) { + // Set up for this image; + current_load_cmd_count_ = hdr->ncmds; + current_magic_ = hdr->magic; + current_filetype_ = hdr->filetype; + current_arch_ = ModuleArchFromCpuType(hdr->cputype, hdr->cpusubtype); + switch (hdr->magic) { +#ifdef MH_MAGIC_64 + case MH_MAGIC_64: { + current_load_cmd_addr_ = (char *)hdr + sizeof(mach_header_64); + break; + } #endif - case MH_MAGIC: { - current_load_cmd_addr_ = (char*)hdr + sizeof(mach_header); - break; - } - default: { - continue; - } + case MH_MAGIC: { + current_load_cmd_addr_ = (char *)hdr + sizeof(mach_header); + break; } - FindUUID((const load_command *)current_load_cmd_addr_, ¤t_uuid_[0]); - current_instrumented_ = - IsModuleInstrumented((const load_command *)current_load_cmd_addr_); + default: { return false; } } + FindUUID((const load_command *)current_load_cmd_addr_, ¤t_uuid_[0]); + current_instrumented_ = + IsModuleInstrumented((const load_command *)current_load_cmd_addr_); + } + return true; +} + +bool MemoryMappingLayout::Next(uptr *start, uptr *end, uptr *offset, + char filename[], uptr filename_size, + uptr *protection, ModuleArch *arch, u8 *uuid) { + // Intentionally loop to -1 here, to force a manual discovery of dyld. + do { + if (!ParseCurrentImageHeader()) continue; for (; current_load_cmd_count_ >= 0; current_load_cmd_count_--) { switch (current_magic_) { @@ -235,7 +311,7 @@ } // If we get here, no more load_cmd's in this image talk about // segments. Go on to the next image. - } + } while (current_image_-- >= 0); return false; }