535 lines · cpp
1//===-- sanitizer_procmaps_mac.cpp ----------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// Information about the process mappings (Mac-specific parts).10//===----------------------------------------------------------------------===//11 12#include "sanitizer_platform.h"13#if SANITIZER_APPLE14#include "sanitizer_common.h"15#include "sanitizer_placement_new.h"16#include "sanitizer_procmaps.h"17 18#include <mach-o/dyld.h>19#include <mach-o/loader.h>20#include <mach/mach.h>21 22// These are not available in older macOS SDKs.23# ifndef CPU_SUBTYPE_X86_64_H24# define CPU_SUBTYPE_X86_64_H ((cpu_subtype_t)8) /* Haswell */25# endif26# ifndef CPU_SUBTYPE_ARM_V7S27# define CPU_SUBTYPE_ARM_V7S ((cpu_subtype_t)11) /* Swift */28# endif29# ifndef CPU_SUBTYPE_ARM_V7K30# define CPU_SUBTYPE_ARM_V7K ((cpu_subtype_t)12)31# endif32# ifndef CPU_TYPE_ARM6433# define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)34# endif35# ifndef CPU_SUBTYPE_ARM64E36# define CPU_SUBTYPE_ARM64E ((cpu_subtype_t)2)37# endif38 39namespace __sanitizer {40 41// Contains information used to iterate through sections.42struct MemoryMappedSegmentData {43 char name[kMaxSegName];44 uptr nsects;45 const char *current_load_cmd_addr;46 u32 lc_type;47 uptr base_virt_addr;48};49 50template <typename Section>51static void NextSectionLoad(LoadedModule *module, MemoryMappedSegmentData *data,52 bool isWritable) {53 const Section *sc = (const Section *)data->current_load_cmd_addr;54 data->current_load_cmd_addr += sizeof(Section);55 56 uptr sec_start = sc->addr + data->base_virt_addr;57 uptr sec_end = sec_start + sc->size;58 module->addAddressRange(sec_start, sec_end, /*executable=*/false, isWritable,59 sc->sectname);60}61 62static bool VerifyMemoryMapping(MemoryMappingLayout* mapping) {63 InternalMmapVector<LoadedModule> modules;64 modules.reserve(128); // matches DumpProcessMap65 mapping->DumpListOfModules(&modules);66 67 InternalMmapVector<LoadedModule::AddressRange> segments;68 for (uptr i = 0; i < modules.size(); ++i) {69 for (auto& range : modules[i].ranges()) {70 segments.push_back(range);71 }72 }73 74 // Verify that none of the segments overlap:75 // 1. Sort the segments by the start address76 // 2. Check that every segment starts after the previous one ends.77 Sort(segments.data(), segments.size(),78 [](LoadedModule::AddressRange& a, LoadedModule::AddressRange& b) {79 return a.beg < b.beg;80 });81 82 // To avoid spam, we only print the report message once-per-process.83 static bool invalid_module_map_reported = false;84 bool well_formed = true;85 86 for (size_t i = 1; i < segments.size(); i++) {87 uptr cur_start = segments[i].beg;88 uptr prev_end = segments[i - 1].end;89 if (cur_start < prev_end) {90 well_formed = false;91 VReport(2, "Overlapping mappings: %s start = %p, %s end = %p\n",92 segments[i].name, (void*)cur_start, segments[i - 1].name,93 (void*)prev_end);94 if (!invalid_module_map_reported) {95 Report(96 "WARN: Invalid dyld module map detected. This is most likely a bug "97 "in the sanitizer.\n");98 Report("WARN: Backtraces may be unreliable.\n");99 invalid_module_map_reported = true;100 }101 }102 }103 104 for (auto& m : modules) m.clear();105 106 mapping->Reset();107 return well_formed;108}109 110void MemoryMappedSegment::AddAddressRanges(LoadedModule *module) {111 // Don't iterate over sections when the caller hasn't set up the112 // data pointer, when there are no sections, or when the segment113 // is executable. Avoid iterating over executable sections because114 // it will confuse libignore, and because the extra granularity115 // of information is not needed by any sanitizers.116 if (!data_ || !data_->nsects || IsExecutable()) {117 module->addAddressRange(start, end, IsExecutable(), IsWritable(),118 data_ ? data_->name : nullptr);119 return;120 }121 122 do {123 if (data_->lc_type == LC_SEGMENT) {124 NextSectionLoad<struct section>(module, data_, IsWritable());125#ifdef MH_MAGIC_64126 } else if (data_->lc_type == LC_SEGMENT_64) {127 NextSectionLoad<struct section_64>(module, data_, IsWritable());128#endif129 }130 } while (--data_->nsects);131}132 133MemoryMappingLayout::MemoryMappingLayout(bool cache_enabled) {134 Reset();135 VerifyMemoryMapping(this);136}137 138MemoryMappingLayout::~MemoryMappingLayout() {139}140 141bool MemoryMappingLayout::Error() const {142 return false;143}144 145// More information about Mach-O headers can be found in mach-o/loader.h146// Each Mach-O image has a header (mach_header or mach_header_64) starting with147// a magic number, and a list of linker load commands directly following the148// header.149// A load command is at least two 32-bit words: the command type and the150// command size in bytes. We're interested only in segment load commands151// (LC_SEGMENT and LC_SEGMENT_64), which tell that a part of the file is mapped152// into the task's address space.153// The |vmaddr|, |vmsize| and |fileoff| fields of segment_command or154// segment_command_64 correspond to the memory address, memory size and the155// file offset of the current memory segment.156// Because these fields are taken from the images as is, one needs to add157// _dyld_get_image_vmaddr_slide() to get the actual addresses at runtime.158 159void MemoryMappingLayout::Reset() {160 // Count down from the top.161 // TODO(glider): as per man 3 dyld, iterating over the headers with162 // _dyld_image_count is thread-unsafe. We need to register callbacks for163 // adding and removing images which will invalidate the MemoryMappingLayout164 // state.165 data_.current_image = _dyld_image_count();166 data_.current_load_cmd_count = -1;167 data_.current_load_cmd_addr = 0;168 data_.current_magic = 0;169 data_.current_filetype = 0;170 data_.current_arch = kModuleArchUnknown;171 internal_memset(data_.current_uuid, 0, kModuleUUIDSize);172}173 174// The dyld load address should be unchanged throughout process execution,175// and it is expensive to compute once many libraries have been loaded,176// so cache it here and do not reset.177static mach_header *dyld_hdr = 0;178static const char kDyldPath[] = "/usr/lib/dyld";179static const int kDyldImageIdx = -1;180 181// static182void MemoryMappingLayout::CacheMemoryMappings() {183 // No-op on Mac for now.184}185 186void MemoryMappingLayout::LoadFromCache() {187 // No-op on Mac for now.188}189 190static bool IsDyldHdr(const mach_header *hdr) {191 return (hdr->magic == MH_MAGIC || hdr->magic == MH_MAGIC_64) &&192 hdr->filetype == MH_DYLINKER;193}194 195// _dyld_get_image_header() and related APIs don't report dyld itself.196// We work around this by manually recursing through the memory map197// until we hit a Mach header matching dyld instead. These recurse198// calls are expensive, but the first memory map generation occurs199// early in the process, when dyld is one of the only images loaded,200// so it will be hit after only a few iterations. These assumptions don't hold201// on macOS 13+ anymore (dyld itself has moved into the shared cache).202static mach_header *GetDyldImageHeaderViaVMRegion() {203 vm_address_t address = 0;204 205 while (true) {206 vm_size_t size = 0;207 unsigned depth = 1;208 struct vm_region_submap_info_64 info;209 mach_msg_type_number_t count = VM_REGION_SUBMAP_INFO_COUNT_64;210 kern_return_t err =211 vm_region_recurse_64(mach_task_self(), &address, &size, &depth,212 (vm_region_info_t)&info, &count);213 if (err != KERN_SUCCESS) return nullptr;214 215 if (size >= sizeof(mach_header) && info.protection & kProtectionRead) {216 mach_header *hdr = (mach_header *)address;217 if (IsDyldHdr(hdr)) {218 return hdr;219 }220 }221 address += size;222 }223}224 225extern "C" {226struct dyld_shared_cache_dylib_text_info {227 uint64_t version; // current version 2228 // following fields all exist in version 1229 uint64_t loadAddressUnslid;230 uint64_t textSegmentSize;231 uuid_t dylibUuid;232 const char *path; // pointer invalid at end of iterations233 // following fields all exist in version 2234 uint64_t textSegmentOffset; // offset from start of cache235};236typedef struct dyld_shared_cache_dylib_text_info237 dyld_shared_cache_dylib_text_info;238 239extern bool _dyld_get_shared_cache_uuid(uuid_t uuid);240extern const void *_dyld_get_shared_cache_range(size_t *length);241extern intptr_t _dyld_get_image_slide(const struct mach_header* mh);242extern int dyld_shared_cache_iterate_text(243 const uuid_t cacheUuid,244 void (^callback)(const dyld_shared_cache_dylib_text_info *info));245} // extern "C"246 247static mach_header *GetDyldImageHeaderViaSharedCache() {248 uuid_t uuid;249 bool hasCache = _dyld_get_shared_cache_uuid(uuid);250 if (!hasCache)251 return nullptr;252 253 size_t cacheLength;254 __block uptr cacheStart = (uptr)_dyld_get_shared_cache_range(&cacheLength);255 CHECK(cacheStart && cacheLength);256 257 __block mach_header *dyldHdr = nullptr;258 int res = dyld_shared_cache_iterate_text(259 uuid, ^(const dyld_shared_cache_dylib_text_info *info) {260 CHECK_GE(info->version, 2);261 mach_header *hdr =262 (mach_header *)(cacheStart + info->textSegmentOffset);263 if (IsDyldHdr(hdr))264 dyldHdr = hdr;265 });266 CHECK_EQ(res, 0);267 268 return dyldHdr;269}270 271const mach_header *get_dyld_hdr() {272 if (!dyld_hdr) {273 // On macOS 13+, dyld itself has moved into the shared cache. Looking it up274 // via vm_region_recurse_64() causes spins/hangs/crashes.275 if (GetMacosAlignedVersion() >= MacosVersion(13, 0)) {276 dyld_hdr = GetDyldImageHeaderViaSharedCache();277 if (!dyld_hdr) {278 VReport(1,279 "Failed to lookup the dyld image header in the shared cache on "280 "macOS 13+ (or no shared cache in use). Falling back to "281 "lookup via vm_region_recurse_64().\n");282 dyld_hdr = GetDyldImageHeaderViaVMRegion();283 }284 } else {285 dyld_hdr = GetDyldImageHeaderViaVMRegion();286 }287 CHECK(dyld_hdr);288 }289 290 return dyld_hdr;291}292 293// Next and NextSegmentLoad were inspired by base/sysinfo.cc in294// Google Perftools, https://github.com/gperftools/gperftools.295 296// NextSegmentLoad scans the current image for the next segment load command297// and returns the start and end addresses and file offset of the corresponding298// segment.299// Note that the segment addresses are not necessarily sorted.300template <u32 kLCSegment, typename SegmentCommand>301static bool NextSegmentLoad(MemoryMappedSegment *segment,302 MemoryMappedSegmentData *seg_data,303 MemoryMappingLayoutData *layout_data) {304 const char *lc = layout_data->current_load_cmd_addr;305 306 layout_data->current_load_cmd_addr += ((const load_command *)lc)->cmdsize;307 layout_data->current_load_cmd_count--;308 if (((const load_command *)lc)->cmd == kLCSegment) {309 const SegmentCommand* sc = (const SegmentCommand *)lc;310 if (internal_strcmp(sc->segname, "__LINKEDIT") == 0) {311 // The LINKEDIT sections are for internal linker use, and may alias312 // with the LINKEDIT section for other modules. (If we included them,313 // our memory map would contain overlappping sections.)314 return false;315 }316 317 uptr base_virt_addr;318 if (layout_data->current_image == kDyldImageIdx)319 base_virt_addr = (uptr)_dyld_get_image_slide(get_dyld_hdr());320 else321 base_virt_addr =322 (uptr)_dyld_get_image_vmaddr_slide(layout_data->current_image);323 324 segment->start = sc->vmaddr + base_virt_addr;325 segment->end = segment->start + sc->vmsize;326 // Most callers don't need section information, so only fill this struct327 // when required.328 if (seg_data) {329 seg_data->nsects = sc->nsects;330 seg_data->current_load_cmd_addr =331 (const char *)lc + sizeof(SegmentCommand);332 seg_data->lc_type = kLCSegment;333 seg_data->base_virt_addr = base_virt_addr;334 internal_strncpy(seg_data->name, sc->segname,335 ARRAY_SIZE(seg_data->name));336 seg_data->name[ARRAY_SIZE(seg_data->name) - 1] = 0;337 }338 339 // Return the initial protection.340 segment->protection = sc->initprot;341 segment->offset = (layout_data->current_filetype ==342 /*MH_EXECUTE*/ 0x2)343 ? sc->vmaddr344 : sc->fileoff;345 if (segment->filename) {346 const char *src = (layout_data->current_image == kDyldImageIdx)347 ? kDyldPath348 : _dyld_get_image_name(layout_data->current_image);349 internal_strncpy(segment->filename, src, segment->filename_size);350 segment->filename[segment->filename_size - 1] = 0;351 }352 segment->arch = layout_data->current_arch;353 internal_memcpy(segment->uuid, layout_data->current_uuid, kModuleUUIDSize);354 return true;355 }356 return false;357}358 359ModuleArch ModuleArchFromCpuType(cpu_type_t cputype, cpu_subtype_t cpusubtype) {360 cpusubtype = cpusubtype & ~CPU_SUBTYPE_MASK;361 switch (cputype) {362 case CPU_TYPE_I386:363 return kModuleArchI386;364 case CPU_TYPE_X86_64:365 if (cpusubtype == CPU_SUBTYPE_X86_64_ALL)366 return kModuleArchX86_64;367 if (cpusubtype == CPU_SUBTYPE_X86_64_H)368 return kModuleArchX86_64H;369 CHECK(0 && "Invalid subtype of x86_64");370 return kModuleArchUnknown;371 case CPU_TYPE_ARM:372 if (cpusubtype == CPU_SUBTYPE_ARM_V6)373 return kModuleArchARMV6;374 if (cpusubtype == CPU_SUBTYPE_ARM_V7)375 return kModuleArchARMV7;376 if (cpusubtype == CPU_SUBTYPE_ARM_V7S)377 return kModuleArchARMV7S;378 if (cpusubtype == CPU_SUBTYPE_ARM_V7K)379 return kModuleArchARMV7K;380 CHECK(0 && "Invalid subtype of ARM");381 return kModuleArchUnknown;382 case CPU_TYPE_ARM64:383 if (cpusubtype == CPU_SUBTYPE_ARM64E)384 return kModuleArchARM64E;385 return kModuleArchARM64;386 default:387 CHECK(0 && "Invalid CPU type");388 return kModuleArchUnknown;389 }390}391 392static const load_command *NextCommand(const load_command *lc) {393 return (const load_command *)((const char *)lc + lc->cmdsize);394}395 396# ifdef MH_MAGIC_64397static constexpr size_t header_size = sizeof(mach_header_64);398# else399static constexpr size_t header_size = sizeof(mach_header);400# endif401 402static void FindUUID(const load_command *first_lc, const mach_header *hdr,403 u8 *uuid_output) {404 uint32_t curcmd = 0;405 for (const load_command *lc = first_lc; curcmd < hdr->ncmds;406 curcmd++, lc = NextCommand(lc)) {407 CHECK_LT((const char *)lc,408 (const char *)hdr + header_size + hdr->sizeofcmds);409 410 if (lc->cmd != LC_UUID)411 continue;412 413 const uuid_command *uuid_lc = (const uuid_command *)lc;414 const uint8_t *uuid = &uuid_lc->uuid[0];415 internal_memcpy(uuid_output, uuid, kModuleUUIDSize);416 return;417 }418}419 420static bool IsModuleInstrumented(const load_command *first_lc,421 const mach_header *hdr) {422 uint32_t curcmd = 0;423 for (const load_command *lc = first_lc; curcmd < hdr->ncmds;424 curcmd++, lc = NextCommand(lc)) {425 CHECK_LT((const char *)lc,426 (const char *)hdr + header_size + hdr->sizeofcmds);427 428 if (lc->cmd != LC_LOAD_DYLIB)429 continue;430 431 const dylib_command *dylib_lc = (const dylib_command *)lc;432 uint32_t dylib_name_offset = dylib_lc->dylib.name.offset;433 const char *dylib_name = ((const char *)dylib_lc) + dylib_name_offset;434 dylib_name = StripModuleName(dylib_name);435 if (dylib_name != 0 && (internal_strstr(dylib_name, "libclang_rt."))) {436 return true;437 }438 }439 return false;440}441 442const ImageHeader *MemoryMappingLayout::CurrentImageHeader() {443 const mach_header *hdr = (data_.current_image == kDyldImageIdx)444 ? get_dyld_hdr()445 : _dyld_get_image_header(data_.current_image);446 return (const ImageHeader *)hdr;447}448 449bool MemoryMappingLayout::Next(MemoryMappedSegment *segment) {450 for (; data_.current_image >= kDyldImageIdx; data_.current_image--) {451 const mach_header *hdr = (const mach_header *)CurrentImageHeader();452 if (!hdr) continue;453 if (data_.current_load_cmd_count < 0) {454 // Set up for this image;455 data_.current_load_cmd_count = hdr->ncmds;456 data_.current_magic = hdr->magic;457 data_.current_filetype = hdr->filetype;458 data_.current_arch = ModuleArchFromCpuType(hdr->cputype, hdr->cpusubtype);459 switch (data_.current_magic) {460#ifdef MH_MAGIC_64461 case MH_MAGIC_64: {462 data_.current_load_cmd_addr =463 (const char *)hdr + sizeof(mach_header_64);464 break;465 }466#endif467 case MH_MAGIC: {468 data_.current_load_cmd_addr = (const char *)hdr + sizeof(mach_header);469 break;470 }471 default: {472 continue;473 }474 }475 FindUUID((const load_command *)data_.current_load_cmd_addr, hdr,476 data_.current_uuid);477 data_.current_instrumented = IsModuleInstrumented(478 (const load_command *)data_.current_load_cmd_addr, hdr);479 }480 481 while (data_.current_load_cmd_count > 0) {482 switch (data_.current_magic) {483 // data_.current_magic may be only one of MH_MAGIC, MH_MAGIC_64.484#ifdef MH_MAGIC_64485 case MH_MAGIC_64: {486 if (NextSegmentLoad<LC_SEGMENT_64, struct segment_command_64>(487 segment, segment->data_, &data_))488 return true;489 break;490 }491#endif492 case MH_MAGIC: {493 if (NextSegmentLoad<LC_SEGMENT, struct segment_command>(494 segment, segment->data_, &data_))495 return true;496 break;497 }498 }499 }500 // If we get here, no more load_cmd's in this image talk about501 // segments. Go on to the next image.502 data_.current_load_cmd_count = -1; // This will trigger loading next image503 }504 return false;505}506 507void MemoryMappingLayout::DumpListOfModules(508 InternalMmapVectorNoCtor<LoadedModule> *modules) {509 Reset();510 InternalMmapVector<char> module_name(kMaxPathLength);511 MemoryMappedSegment segment(module_name.data(), module_name.size());512 MemoryMappedSegmentData data;513 segment.data_ = &data;514 while (Next(&segment)) {515 // skip the __PAGEZERO segment, its vmsize is 0516 if (segment.filename[0] == '\0' || (segment.start == segment.end))517 continue;518 LoadedModule *cur_module = nullptr;519 if (!modules->empty() &&520 0 == internal_strcmp(segment.filename, modules->back().full_name())) {521 cur_module = &modules->back();522 } else {523 modules->push_back(LoadedModule());524 cur_module = &modules->back();525 cur_module->set(segment.filename, segment.start, segment.arch,526 segment.uuid, data_.current_instrumented);527 }528 segment.AddAddressRanges(cur_module);529 }530}531 532} // namespace __sanitizer533 534#endif // SANITIZER_APPLE535