| 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(c: **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, base: 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, base: 16); |
| 64 | } |
| 65 | |
| 66 | void MemoryMappedSegment::AddAddressRanges(LoadedModule *module) { |
| 67 | // data_ should be unused on this platform |
| 68 | CHECK(!data_); |
| 69 | module->addAddressRange(beg: start, end, executable: IsExecutable(), writable: 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(proc_maps: &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(addr: data_.proc_self_maps.data, size: 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(proc_maps: &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(addr: cached_proc_self_maps.data, size: 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: &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(module_name: cur_name, base_address); |
| 143 | segment.AddAddressRanges(module: &cur_module); |
| 144 | modules->push_back(element: cur_module); |
| 145 | } |
| 146 | } |
| 147 | |
| 148 | #if SANITIZER_LINUX || SANITIZER_ANDROID || SANITIZER_SOLARIS |
| 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(file_name: "/proc/self/smaps" , buff: &smaps, buff_size: &smaps_cap, read_len: &smaps_len)) |
| 154 | return; |
| 155 | ParseUnixMemoryProfile(cb, stats, smaps, smaps_len); |
| 156 | UnmapOrDie(addr: smaps, size: 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(c: pos[0])) { |
| 177 | start = ParseHex(p: &pos); |
| 178 | for (; *pos != '/' && *pos > '\n'; pos++) {} |
| 179 | file = *pos == '/'; |
| 180 | } else if (internal_strncmp(s1: pos, s2: "Rss:" , n: 4) == 0) { |
| 181 | while (pos < end && !IsDecimal(c: *pos)) pos++; |
| 182 | uptr = ParseDecimal(p: &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 | |