1 //===-- sanitizer_procmaps_common.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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // Information about the process mappings (common parts). 10 //===----------------------------------------------------------------------===// 11 12 #include "sanitizer_platform.h" 13 14 #if SANITIZER_FREEBSD || SANITIZER_LINUX || SANITIZER_NETBSD || \ 15 SANITIZER_SOLARIS 16 17 #include "sanitizer_common.h" 18 #include "sanitizer_placement_new.h" 19 #include "sanitizer_procmaps.h" 20 21 namespace __sanitizer { 22 23 static ProcSelfMapsBuff cached_proc_self_maps; 24 static StaticSpinMutex cache_lock; 25 26 static int TranslateDigit(char c) { 27 if (c >= '0' && c <= '9') 28 return c - '0'; 29 if (c >= 'a' && c <= 'f') 30 return c - 'a' + 10; 31 if (c >= 'A' && c <= 'F') 32 return c - 'A' + 10; 33 return -1; 34 } 35 36 // Parse a number and promote 'p' up to the first non-digit character. 37 static uptr ParseNumber(const char **p, int base) { 38 uptr n = 0; 39 int d; 40 CHECK(base >= 2 && base <= 16); 41 while ((d = TranslateDigit(**p)) >= 0 && d < base) { 42 n = n * base + d; 43 (*p)++; 44 } 45 return n; 46 } 47 48 bool IsDecimal(char c) { 49 int d = TranslateDigit(c); 50 return d >= 0 && d < 10; 51 } 52 53 uptr ParseDecimal(const char **p) { 54 return ParseNumber(p, 10); 55 } 56 57 bool IsHex(char c) { 58 int d = TranslateDigit(c); 59 return d >= 0 && d < 16; 60 } 61 62 uptr ParseHex(const char **p) { 63 return ParseNumber(p, 16); 64 } 65 66 void MemoryMappedSegment::AddAddressRanges(LoadedModule *module) { 67 // data_ should be unused on this platform 68 CHECK(!data_); 69 module->addAddressRange(start, end, IsExecutable(), IsWritable()); 70 } 71 72 MemoryMappingLayout::MemoryMappingLayout(bool cache_enabled) { 73 // FIXME: in the future we may want to cache the mappings on demand only. 74 if (cache_enabled) 75 CacheMemoryMappings(); 76 77 // Read maps after the cache update to capture the maps/unmaps happening in 78 // the process of updating. 79 ReadProcMaps(&data_.proc_self_maps); 80 if (cache_enabled && data_.proc_self_maps.mmaped_size == 0) 81 LoadFromCache(); 82 83 Reset(); 84 } 85 86 bool MemoryMappingLayout::Error() const { 87 return data_.current == nullptr; 88 } 89 90 MemoryMappingLayout::~MemoryMappingLayout() { 91 // Only unmap the buffer if it is different from the cached one. Otherwise 92 // it will be unmapped when the cache is refreshed. 93 if (data_.proc_self_maps.data != cached_proc_self_maps.data) 94 UnmapOrDie(data_.proc_self_maps.data, data_.proc_self_maps.mmaped_size); 95 } 96 97 void MemoryMappingLayout::Reset() { 98 data_.current = data_.proc_self_maps.data; 99 } 100 101 // static 102 void MemoryMappingLayout::CacheMemoryMappings() { 103 ProcSelfMapsBuff new_proc_self_maps; 104 ReadProcMaps(&new_proc_self_maps); 105 // Don't invalidate the cache if the mappings are unavailable. 106 if (new_proc_self_maps.mmaped_size == 0) 107 return; 108 SpinMutexLock l(&cache_lock); 109 if (cached_proc_self_maps.mmaped_size) 110 UnmapOrDie(cached_proc_self_maps.data, cached_proc_self_maps.mmaped_size); 111 cached_proc_self_maps = new_proc_self_maps; 112 } 113 114 void MemoryMappingLayout::LoadFromCache() { 115 SpinMutexLock l(&cache_lock); 116 if (cached_proc_self_maps.data) 117 data_.proc_self_maps = cached_proc_self_maps; 118 } 119 120 void MemoryMappingLayout::DumpListOfModules( 121 InternalMmapVectorNoCtor<LoadedModule> *modules) { 122 Reset(); 123 InternalMmapVector<char> module_name(kMaxPathLength); 124 MemoryMappedSegment segment(module_name.data(), module_name.size()); 125 for (uptr i = 0; Next(&segment); i++) { 126 const char *cur_name = segment.filename; 127 if (cur_name[0] == '\0') 128 continue; 129 // Don't subtract 'cur_beg' from the first entry: 130 // * If a binary is compiled w/o -pie, then the first entry in 131 // process maps is likely the binary itself (all dynamic libs 132 // are mapped higher in address space). For such a binary, 133 // instruction offset in binary coincides with the actual 134 // instruction address in virtual memory (as code section 135 // is mapped to a fixed memory range). 136 // * If a binary is compiled with -pie, all the modules are 137 // mapped high at address space (in particular, higher than 138 // shadow memory of the tool), so the module can't be the 139 // first entry. 140 uptr base_address = (i ? segment.start : 0) - segment.offset; 141 LoadedModule cur_module; 142 cur_module.set(cur_name, base_address); 143 segment.AddAddressRanges(&cur_module); 144 modules->push_back(cur_module); 145 } 146 } 147 148 #if SANITIZER_LINUX || SANITIZER_ANDROID || SANITIZER_SOLARIS || SANITIZER_NETBSD 149 void GetMemoryProfile(fill_profile_f cb, uptr *stats) { 150 char *smaps = nullptr; 151 uptr smaps_cap = 0; 152 uptr smaps_len = 0; 153 if (!ReadFileToBuffer("/proc/self/smaps", &smaps, &smaps_cap, &smaps_len)) 154 return; 155 ParseUnixMemoryProfile(cb, stats, smaps, smaps_len); 156 UnmapOrDie(smaps, smaps_cap); 157 } 158 159 void ParseUnixMemoryProfile(fill_profile_f cb, uptr *stats, char *smaps, 160 uptr smaps_len) { 161 uptr start = 0; 162 bool file = false; 163 const char *pos = smaps; 164 char *end = smaps + smaps_len; 165 if (smaps_len < 2) 166 return; 167 // The following parsing can crash on almost every line 168 // in the case of malformed/truncated input. 169 // Fixing that is hard b/c e.g. ParseDecimal does not 170 // even accept end of the buffer and assumes well-formed input. 171 // So instead we patch end of the input a bit, 172 // it does not affect well-formed complete inputs. 173 *--end = 0; 174 *--end = '\n'; 175 while (pos < end) { 176 if (IsHex(pos[0])) { 177 start = ParseHex(&pos); 178 for (; *pos != '/' && *pos > '\n'; pos++) {} 179 file = *pos == '/'; 180 } else if (internal_strncmp(pos, "Rss:", 4) == 0) { 181 while (pos < end && !IsDecimal(*pos)) pos++; 182 uptr rss = ParseDecimal(&pos) * 1024; 183 cb(start, rss, file, stats); 184 } 185 while (*pos++ != '\n') {} 186 } 187 } 188 #endif 189 190 } // namespace __sanitizer 191 192 #endif 193