1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/android/library_loader/library_prefetcher.h"
6
7 #include <stddef.h>
8 #include <sys/mman.h>
9 #include <sys/resource.h>
10 #include <sys/wait.h>
11 #include <unistd.h>
12 #include <algorithm>
13 #include <utility>
14 #include <vector>
15
16 #include "base/macros.h"
17 #include "base/posix/eintr_wrapper.h"
18 #include "base/strings/string_util.h"
19 #include "build/build_config.h"
20
21 namespace base {
22 namespace android {
23
24 namespace {
25
26 // Android defines the background priority to this value since at least 2009
27 // (see Process.java).
28 const int kBackgroundPriority = 10;
29 // Valid for all the Android architectures.
30 const size_t kPageSize = 4096;
31 const char* kLibchromeSuffix = "libchrome.so";
32 // "base.apk" is a suffix because the library may be loaded directly from the
33 // APK.
34 const char* kSuffixesToMatch[] = {kLibchromeSuffix, "base.apk"};
35
IsReadableAndPrivate(const base::debug::MappedMemoryRegion & region)36 bool IsReadableAndPrivate(const base::debug::MappedMemoryRegion& region) {
37 return region.permissions & base::debug::MappedMemoryRegion::READ &&
38 region.permissions & base::debug::MappedMemoryRegion::PRIVATE;
39 }
40
PathMatchesSuffix(const std::string & path)41 bool PathMatchesSuffix(const std::string& path) {
42 for (size_t i = 0; i < arraysize(kSuffixesToMatch); i++) {
43 if (EndsWith(path, kSuffixesToMatch[i], CompareCase::SENSITIVE)) {
44 return true;
45 }
46 }
47 return false;
48 }
49
50 // For each range, reads a byte per page to force it into the page cache.
51 // Heap allocations, syscalls and library functions are not allowed in this
52 // function.
53 // Returns true for success.
54 #if defined(ADDRESS_SANITIZER)
55 // Disable AddressSanitizer instrumentation for this function. It is touching
56 // memory that hasn't been allocated by the app, though the addresses are
57 // valid. Furthermore, this takes place in a child process. See crbug.com/653372
58 // for the context.
59 __attribute__((no_sanitize_address))
60 #endif
Prefetch(const std::vector<std::pair<uintptr_t,uintptr_t>> & ranges)61 bool Prefetch(const std::vector<std::pair<uintptr_t, uintptr_t>>& ranges) {
62 for (const auto& range : ranges) {
63 const uintptr_t page_mask = kPageSize - 1;
64 // If start or end is not page-aligned, parsing went wrong. It is better to
65 // exit with an error.
66 if ((range.first & page_mask) || (range.second & page_mask)) {
67 return false; // CHECK() is not allowed here.
68 }
69 unsigned char* start_ptr = reinterpret_cast<unsigned char*>(range.first);
70 unsigned char* end_ptr = reinterpret_cast<unsigned char*>(range.second);
71 unsigned char dummy = 0;
72 for (unsigned char* ptr = start_ptr; ptr < end_ptr; ptr += kPageSize) {
73 // Volatile is required to prevent the compiler from eliminating this
74 // loop.
75 dummy ^= *static_cast<volatile unsigned char*>(ptr);
76 }
77 }
78 return true;
79 }
80
81 } // namespace
82
83 // static
IsGoodToPrefetch(const base::debug::MappedMemoryRegion & region)84 bool NativeLibraryPrefetcher::IsGoodToPrefetch(
85 const base::debug::MappedMemoryRegion& region) {
86 return PathMatchesSuffix(region.path) &&
87 IsReadableAndPrivate(region); // .text and .data mappings are private.
88 }
89
90 // static
FilterLibchromeRangesOnlyIfPossible(const std::vector<base::debug::MappedMemoryRegion> & regions,std::vector<AddressRange> * ranges)91 void NativeLibraryPrefetcher::FilterLibchromeRangesOnlyIfPossible(
92 const std::vector<base::debug::MappedMemoryRegion>& regions,
93 std::vector<AddressRange>* ranges) {
94 bool has_libchrome_region = false;
95 for (const base::debug::MappedMemoryRegion& region : regions) {
96 if (EndsWith(region.path, kLibchromeSuffix, CompareCase::SENSITIVE)) {
97 has_libchrome_region = true;
98 break;
99 }
100 }
101 for (const base::debug::MappedMemoryRegion& region : regions) {
102 if (has_libchrome_region &&
103 !EndsWith(region.path, kLibchromeSuffix, CompareCase::SENSITIVE)) {
104 continue;
105 }
106 ranges->push_back(std::make_pair(region.start, region.end));
107 }
108 }
109
110 // static
FindRanges(std::vector<AddressRange> * ranges)111 bool NativeLibraryPrefetcher::FindRanges(std::vector<AddressRange>* ranges) {
112 std::string proc_maps;
113 if (!base::debug::ReadProcMaps(&proc_maps))
114 return false;
115 std::vector<base::debug::MappedMemoryRegion> regions;
116 if (!base::debug::ParseProcMaps(proc_maps, ®ions))
117 return false;
118
119 std::vector<base::debug::MappedMemoryRegion> regions_to_prefetch;
120 for (const auto& region : regions) {
121 if (IsGoodToPrefetch(region)) {
122 regions_to_prefetch.push_back(region);
123 }
124 }
125
126 FilterLibchromeRangesOnlyIfPossible(regions_to_prefetch, ranges);
127 return true;
128 }
129
130 // static
ForkAndPrefetchNativeLibrary()131 bool NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary() {
132 // Avoid forking with cygprofile instrumentation because the latter performs
133 // memory allocations.
134 #if defined(CYGPROFILE_INSTRUMENTATION)
135 return false;
136 #endif
137
138 // Looking for ranges is done before the fork, to avoid syscalls and/or memory
139 // allocations in the forked process. The child process inherits the lock
140 // state of its parent thread. It cannot rely on being able to acquire any
141 // lock (unless special care is taken in a pre-fork handler), including being
142 // able to call malloc().
143 std::vector<AddressRange> ranges;
144 if (!FindRanges(&ranges))
145 return false;
146
147 pid_t pid = fork();
148 if (pid == 0) {
149 setpriority(PRIO_PROCESS, 0, kBackgroundPriority);
150 // _exit() doesn't call the atexit() handlers.
151 _exit(Prefetch(ranges) ? 0 : 1);
152 } else {
153 if (pid < 0) {
154 return false;
155 }
156 int status;
157 const pid_t result = HANDLE_EINTR(waitpid(pid, &status, 0));
158 if (result == pid) {
159 if (WIFEXITED(status)) {
160 return WEXITSTATUS(status) == 0;
161 }
162 }
163 return false;
164 }
165 }
166
167 // static
PercentageOfResidentCode(const std::vector<AddressRange> & ranges)168 int NativeLibraryPrefetcher::PercentageOfResidentCode(
169 const std::vector<AddressRange>& ranges) {
170 size_t total_pages = 0;
171 size_t resident_pages = 0;
172 const uintptr_t page_mask = kPageSize - 1;
173
174 for (const auto& range : ranges) {
175 if (range.first & page_mask || range.second & page_mask)
176 return -1;
177 size_t length = range.second - range.first;
178 size_t pages = length / kPageSize;
179 total_pages += pages;
180 std::vector<unsigned char> is_page_resident(pages);
181 int err = mincore(reinterpret_cast<void*>(range.first), length,
182 &is_page_resident[0]);
183 DPCHECK(!err);
184 if (err)
185 return -1;
186 resident_pages +=
187 std::count_if(is_page_resident.begin(), is_page_resident.end(),
188 [](unsigned char x) { return x & 1; });
189 }
190 if (total_pages == 0)
191 return -1;
192 return static_cast<int>((100 * resident_pages) / total_pages);
193 }
194
195 // static
PercentageOfResidentNativeLibraryCode()196 int NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode() {
197 std::vector<AddressRange> ranges;
198 if (!FindRanges(&ranges))
199 return -1;
200 return PercentageOfResidentCode(ranges);
201 }
202
203 } // namespace android
204 } // namespace base
205