//===-- sanitizer_procmaps_common.cpp -------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // Information about the process mappings (common parts). //===----------------------------------------------------------------------===// #include "sanitizer_platform.h" #if SANITIZER_FREEBSD || SANITIZER_LINUX || SANITIZER_NETBSD || \ SANITIZER_SOLARIS #include "sanitizer_common.h" #include "sanitizer_placement_new.h" #include "sanitizer_procmaps.h" namespace __sanitizer { static ProcSelfMapsBuff cached_proc_self_maps; static StaticSpinMutex cache_lock; static int TranslateDigit(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return -1; } // Parse a number and promote 'p' up to the first non-digit character. static uptr ParseNumber(const char **p, int base) { uptr n = 0; int d; CHECK(base >= 2 && base <= 16); while ((d = TranslateDigit(**p)) >= 0 && d < base) { n = n * base + d; (*p)++; } return n; } bool IsDecimal(char c) { int d = TranslateDigit(c); return d >= 0 && d < 10; } uptr ParseDecimal(const char **p) { return ParseNumber(p, 10); } bool IsHex(char c) { int d = TranslateDigit(c); return d >= 0 && d < 16; } uptr ParseHex(const char **p) { return ParseNumber(p, 16); } void MemoryMappedSegment::AddAddressRanges(LoadedModule *module) { // data_ should be unused on this platform CHECK(!data_); module->addAddressRange(start, end, IsExecutable(), IsWritable()); } MemoryMappingLayout::MemoryMappingLayout(bool cache_enabled) { // FIXME: in the future we may want to cache the mappings on demand only. if (cache_enabled) CacheMemoryMappings(); // Read maps after the cache update to capture the maps/unmaps happening in // the process of updating. ReadProcMaps(&data_.proc_self_maps); if (cache_enabled && data_.proc_self_maps.mmaped_size == 0) LoadFromCache(); Reset(); } bool MemoryMappingLayout::Error() const { return data_.current == nullptr; } MemoryMappingLayout::~MemoryMappingLayout() { // Only unmap the buffer if it is different from the cached one. Otherwise // it will be unmapped when the cache is refreshed. if (data_.proc_self_maps.data != cached_proc_self_maps.data) UnmapOrDie(data_.proc_self_maps.data, data_.proc_self_maps.mmaped_size); } void MemoryMappingLayout::Reset() { data_.current = data_.proc_self_maps.data; } // static void MemoryMappingLayout::CacheMemoryMappings() { ProcSelfMapsBuff new_proc_self_maps; ReadProcMaps(&new_proc_self_maps); // Don't invalidate the cache if the mappings are unavailable. if (new_proc_self_maps.mmaped_size == 0) return; SpinMutexLock l(&cache_lock); if (cached_proc_self_maps.mmaped_size) UnmapOrDie(cached_proc_self_maps.data, cached_proc_self_maps.mmaped_size); cached_proc_self_maps = new_proc_self_maps; } void MemoryMappingLayout::LoadFromCache() { SpinMutexLock l(&cache_lock); if (cached_proc_self_maps.data) data_.proc_self_maps = cached_proc_self_maps; } void MemoryMappingLayout::DumpListOfModules( InternalMmapVectorNoCtor *modules) { Reset(); InternalMmapVector module_name(kMaxPathLength); MemoryMappedSegment segment(module_name.data(), module_name.size()); for (uptr i = 0; Next(&segment); i++) { const char *cur_name = segment.filename; if (cur_name[0] == '\0') continue; // Don't subtract 'cur_beg' from the first entry: // * If a binary is compiled w/o -pie, then the first entry in // process maps is likely the binary itself (all dynamic libs // are mapped higher in address space). For such a binary, // instruction offset in binary coincides with the actual // instruction address in virtual memory (as code section // is mapped to a fixed memory range). // * If a binary is compiled with -pie, all the modules are // mapped high at address space (in particular, higher than // shadow memory of the tool), so the module can't be the // first entry. uptr base_address = (i ? segment.start : 0) - segment.offset; LoadedModule cur_module; cur_module.set(cur_name, base_address); segment.AddAddressRanges(&cur_module); modules->push_back(cur_module); } } #if SANITIZER_LINUX || SANITIZER_ANDROID || SANITIZER_SOLARIS void GetMemoryProfile(fill_profile_f cb, uptr *stats) { char *smaps = nullptr; uptr smaps_cap = 0; uptr smaps_len = 0; if (!ReadFileToBuffer("/proc/self/smaps", &smaps, &smaps_cap, &smaps_len)) return; ParseUnixMemoryProfile(cb, stats, smaps, smaps_len); UnmapOrDie(smaps, smaps_cap); } void ParseUnixMemoryProfile(fill_profile_f cb, uptr *stats, char *smaps, uptr smaps_len) { uptr start = 0; bool file = false; const char *pos = smaps; char *end = smaps + smaps_len; if (smaps_len < 2) return; // The following parsing can crash on almost every line // in the case of malformed/truncated input. // Fixing that is hard b/c e.g. ParseDecimal does not // even accept end of the buffer and assumes well-formed input. // So instead we patch end of the input a bit, // it does not affect well-formed complete inputs. *--end = 0; *--end = '\n'; while (pos < end) { if (IsHex(pos[0])) { start = ParseHex(&pos); for (; *pos != '/' && *pos > '\n'; pos++) {} file = *pos == '/'; } else if (internal_strncmp(pos, "Rss:", 4) == 0) { while (pos < end && !IsDecimal(*pos)) pos++; uptr rss = ParseDecimal(&pos) * 1024; cb(start, rss, file, stats); } while (*pos++ != '\n') {} } } #endif } // namespace __sanitizer #endif