| 1 | //===-- sanitizer_procmaps_test.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 | // This file is a part of ThreadSanitizer/AddressSanitizer runtime. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | #if !defined(_WIN32) // There are no /proc/maps on Windows. |
| 13 | |
| 14 | # include "sanitizer_common/sanitizer_procmaps.h" |
| 15 | |
| 16 | # include <stdlib.h> |
| 17 | # include <string.h> |
| 18 | |
| 19 | # include <vector> |
| 20 | |
| 21 | # include "gtest/gtest.h" |
| 22 | |
| 23 | static void noop() {} |
| 24 | extern const char *argv0; |
| 25 | |
| 26 | namespace __sanitizer { |
| 27 | |
| 28 | # if SANITIZER_LINUX && !SANITIZER_ANDROID |
| 29 | TEST(MemoryMappingLayout, CodeRange) { |
| 30 | uptr start, end; |
| 31 | bool res = GetCodeRangeForFile(module: "[vdso]" , start: &start, end: &end); |
| 32 | EXPECT_EQ(res, true); |
| 33 | EXPECT_GT(start, 0U); |
| 34 | EXPECT_LT(start, end); |
| 35 | } |
| 36 | # endif |
| 37 | |
| 38 | TEST(MemoryMappingLayout, DumpListOfModules) { |
| 39 | const char *last_slash = strrchr(s: argv0, c: '/'); |
| 40 | const char *binary_name = last_slash ? last_slash + 1 : argv0; |
| 41 | MemoryMappingLayout memory_mapping(false); |
| 42 | const uptr kMaxModules = 100; |
| 43 | InternalMmapVector<LoadedModule> modules; |
| 44 | modules.reserve(new_size: kMaxModules); |
| 45 | memory_mapping.DumpListOfModules(modules: &modules); |
| 46 | EXPECT_GT(modules.size(), 0U); |
| 47 | bool found = false; |
| 48 | for (uptr i = 0; i < modules.size(); ++i) { |
| 49 | if (modules[i].containsAddress(address: (uptr)&noop)) { |
| 50 | // Verify that the module name is sane. |
| 51 | if (strstr(haystack: modules[i].full_name(), needle: binary_name) != 0) |
| 52 | found = true; |
| 53 | } |
| 54 | modules[i].clear(); |
| 55 | } |
| 56 | EXPECT_TRUE(found); |
| 57 | } |
| 58 | |
| 59 | TEST(MemoryMapping, LoadedModuleArchAndUUID) { |
| 60 | if (SANITIZER_APPLE) { |
| 61 | MemoryMappingLayout memory_mapping(false); |
| 62 | const uptr kMaxModules = 100; |
| 63 | InternalMmapVector<LoadedModule> modules; |
| 64 | modules.reserve(new_size: kMaxModules); |
| 65 | memory_mapping.DumpListOfModules(modules: &modules); |
| 66 | for (uptr i = 0; i < modules.size(); ++i) { |
| 67 | ModuleArch arch = modules[i].arch(); |
| 68 | // Darwin unit tests are only run on i386/x86_64/x86_64h/arm64. |
| 69 | if (SANITIZER_WORDSIZE == 32) { |
| 70 | EXPECT_EQ(arch, kModuleArchI386); |
| 71 | } else if (SANITIZER_WORDSIZE == 64) { |
| 72 | EXPECT_TRUE(arch == kModuleArchX86_64 || arch == kModuleArchX86_64H || |
| 73 | arch == kModuleArchARM64); |
| 74 | } |
| 75 | const u8 *uuid = modules[i].uuid(); |
| 76 | u8 null_uuid[kModuleUUIDSize] = {0}; |
| 77 | EXPECT_NE(memcmp(s1: null_uuid, s2: uuid, n: kModuleUUIDSize), 0); |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | # if (SANITIZER_LINUX || SANITIZER_NETBSD || SANITIZER_SOLARIS) && \ |
| 83 | defined(_LP64) |
| 84 | const char *const parse_unix_input = R"( |
| 85 | 7fb9862f1000-7fb9862f3000 rw-p 00000000 00:00 0 |
| 86 | Size: 8 kB |
| 87 | Rss: 4 kB |
| 88 | 7fb9864ae000-7fb9864b1000 r--p 001ba000 fd:01 22413919 /lib/x86_64-linux-gnu/libc-2.32.so |
| 89 | Size: 12 kB |
| 90 | Rss: 12 kB |
| 91 | )" ; |
| 92 | |
| 93 | TEST(MemoryMapping, ParseUnixMemoryProfile) { |
| 94 | struct entry { |
| 95 | uptr p; |
| 96 | uptr rss; |
| 97 | bool file; |
| 98 | }; |
| 99 | typedef std::vector<entry> entries_t; |
| 100 | entries_t entries; |
| 101 | std::vector<char> input(parse_unix_input, |
| 102 | parse_unix_input + strlen(parse_unix_input)); |
| 103 | ParseUnixMemoryProfile( |
| 104 | [](uptr p, uptr rss, bool file, uptr *mem) { |
| 105 | reinterpret_cast<entries_t *>(mem)->push_back({p, rss, file}); |
| 106 | }, |
| 107 | reinterpret_cast<uptr *>(&entries), &input[0], input.size()); |
| 108 | EXPECT_EQ(entries.size(), 2ul); |
| 109 | EXPECT_EQ(entries[0].p, 0x7fb9862f1000ul); |
| 110 | EXPECT_EQ(entries[0].rss, 4ul << 10); |
| 111 | EXPECT_EQ(entries[0].file, false); |
| 112 | EXPECT_EQ(entries[1].p, 0x7fb9864ae000ul); |
| 113 | EXPECT_EQ(entries[1].rss, 12ul << 10); |
| 114 | EXPECT_EQ(entries[1].file, true); |
| 115 | } |
| 116 | |
| 117 | TEST(MemoryMapping, ParseUnixMemoryProfileTruncated) { |
| 118 | // ParseUnixMemoryProfile used to crash on truncated inputs. |
| 119 | // This test allocates 2 pages, protects the second one |
| 120 | // and places the input at the very end of the first page |
| 121 | // to test for over-reads. |
| 122 | uptr page = GetPageSizeCached(); |
| 123 | char *mem = static_cast<char *>( |
| 124 | MmapOrDie(2 * page, "ParseUnixMemoryProfileTruncated" )); |
| 125 | EXPECT_TRUE(MprotectNoAccess(reinterpret_cast<uptr>(mem + page), page)); |
| 126 | const uptr len = strlen(parse_unix_input); |
| 127 | for (uptr i = 0; i < len; i++) { |
| 128 | char *smaps = mem + page - len + i; |
| 129 | memcpy(smaps, parse_unix_input, len - i); |
| 130 | ParseUnixMemoryProfile([](uptr p, uptr rss, bool file, uptr *mem) {}, |
| 131 | nullptr, smaps, len - i); |
| 132 | } |
| 133 | UnmapOrDie(mem, 2 * page); |
| 134 | } |
| 135 | # endif |
| 136 | |
| 137 | } // namespace __sanitizer |
| 138 | #endif // !defined(_WIN32) |
| 139 | |