1 /*
2 * Copyright (C) 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "gtest/gtest.h"
18
19 #include "sys/mman.h"
20
21 #include <cstdint>
22 #include <cstdio>
23 #include <cstring> // strncmp
24 #include <memory>
25 #include <string>
26 #include <tuple>
27
28 #include "berberis/base/maps_snapshot.h"
29
30 namespace berberis {
31
32 namespace {
33
Foo()34 void Foo() {};
35
TEST(MapsSnapshot,Basic)36 TEST(MapsSnapshot, Basic) {
37 auto* maps_snapshot = MapsSnapshot::GetInstance();
38
39 maps_snapshot->ClearForTesting();
40
41 // No mappings can be found before snapshot is taken by Update().
42 auto no_mappings_result = maps_snapshot->FindMappedObjectName(reinterpret_cast<uintptr_t>(&Foo));
43 ASSERT_FALSE(no_mappings_result.has_value());
44
45 maps_snapshot->Update();
46
47 auto result = maps_snapshot->FindMappedObjectName(reinterpret_cast<uintptr_t>(&Foo));
48 ASSERT_TRUE(result.has_value());
49 ASSERT_FALSE(result.value().empty());
50 }
51
TEST(MapsSnapshot,AnonymousMapping)52 TEST(MapsSnapshot, AnonymousMapping) {
53 auto* maps_snapshot = MapsSnapshot::GetInstance();
54
55 void* addr = mmap(nullptr, 4096, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
56 ASSERT_NE(addr, MAP_FAILED);
57 maps_snapshot->Update();
58 auto result = maps_snapshot->FindMappedObjectName(reinterpret_cast<uintptr_t>(addr));
59 munmap(addr, 4096);
60
61 ASSERT_TRUE(result.has_value());
62 ASSERT_TRUE(result.value().empty());
63 }
64
GetAddressOfFirstMappingWithSubstring(std::string substr)65 std::tuple<uintptr_t, std::string> GetAddressOfFirstMappingWithSubstring(std::string substr) {
66 std::unique_ptr<FILE, decltype(&fclose)> maps_file(fopen("/proc/self/maps", "r"), fclose);
67 if (maps_file == nullptr) {
68 ADD_FAILURE() << "Cannot open /proc/self/maps";
69 return {0, ""};
70 }
71
72 char line[512], pathname[256];
73 uintptr_t start;
74 while (fgets(line, sizeof(line), maps_file.get())) {
75 // Maximum string size 255 so that we have space for the terminating '\0'.
76 int match_count = sscanf(
77 line, "%" SCNxPTR "-%*" SCNxPTR " %*s %*lx %*x:%*x %*lu%*[ ]%255s", &start, pathname);
78 if (match_count == 2) {
79 std::string current_pathname(pathname);
80 if (current_pathname.find(substr) != current_pathname.npos) {
81 return {start, current_pathname};
82 }
83 }
84 }
85 ADD_FAILURE() << "Cannot find " << substr << " in /proc/self/maps";
86 return {0, ""};
87 }
88
TEST(MapsSnapshot,ExactFilenameMatch)89 TEST(MapsSnapshot, ExactFilenameMatch) {
90 auto* maps_snapshot = MapsSnapshot::GetInstance();
91
92 // Take some object that must be mapped already and is unlikely to be suddenly unmapped. "libc.so"
93 // may have a version suffix like "libc-2.19.so", which would make parsing too challenging for
94 // what this test requires. We don't want to search just for "libc" either since it's likely to
95 // match an unrelated library. "libc++.so" is taken from the local build
96 // (out/host/linux-x86/lib64/libc++.so) so we should be able to find it.
97 auto [addr, pathname] = GetAddressOfFirstMappingWithSubstring("libc++.so");
98 ASSERT_GT(addr, 0u);
99
100 maps_snapshot->Update();
101 auto result = maps_snapshot->FindMappedObjectName(reinterpret_cast<uintptr_t>(addr));
102
103 ASSERT_TRUE(result.has_value());
104 // MapsSnapshot only stores first 255 symbols plus terminating null.
105 ASSERT_TRUE(strncmp(result.value().c_str(), pathname.c_str(), 255) == 0);
106 }
107
108 } // namespace
109
110 } // namespace berberis
111