| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/android/library_loader/library_prefetcher.h" |
| |
| #include <sys/mman.h> |
| #include <sys/resource.h> |
| #include <sys/wait.h> |
| #include <unistd.h> |
| #include <algorithm> |
| #include <atomic> |
| #include <cstdlib> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/android/library_loader/anchor_functions.h" |
| #include "base/android/orderfile/orderfile_buildflags.h" |
| #include "base/bits.h" |
| #include "base/files/file.h" |
| #include "base/format_macros.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.h" |
| |
| #if BUILDFLAG(ORDERFILE_INSTRUMENTATION) |
| #include "base/android/orderfile/orderfile_instrumentation.h" |
| #include "starboard/types.h" |
| #endif |
| |
| #if BUILDFLAG(SUPPORTS_CODE_ORDERING) |
| |
| namespace base { |
| namespace android { |
| |
| namespace { |
| |
| // Android defines the background priority to this value since at least 2009 |
| // (see Process.java). |
| constexpr int kBackgroundPriority = 10; |
| // Valid for all Android architectures. |
| constexpr size_t kPageSize = 4096; |
| |
| // Reads a byte per page between |start| and |end| to force it into the page |
| // cache. |
| // Heap allocations, syscalls and library functions are not allowed in this |
| // function. |
| // Returns true for success. |
| #if defined(ADDRESS_SANITIZER) |
| // Disable AddressSanitizer instrumentation for this function. It is touching |
| // memory that hasn't been allocated by the app, though the addresses are |
| // valid. Furthermore, this takes place in a child process. See crbug.com/653372 |
| // for the context. |
| __attribute__((no_sanitize_address)) |
| #endif |
| void Prefetch(size_t start, size_t end) { |
| unsigned char* start_ptr = reinterpret_cast<unsigned char*>(start); |
| unsigned char* end_ptr = reinterpret_cast<unsigned char*>(end); |
| unsigned char dummy = 0; |
| for (unsigned char* ptr = start_ptr; ptr < end_ptr; ptr += kPageSize) { |
| // Volatile is required to prevent the compiler from eliminating this |
| // loop. |
| dummy ^= *static_cast<volatile unsigned char*>(ptr); |
| } |
| } |
| |
| // Populates the per-page residency between |start| and |end| in |residency|. If |
| // successful, |residency| has the size of |end| - |start| in pages. |
| // Returns true for success. |
| bool Mincore(size_t start, size_t end, std::vector<unsigned char>* residency) { |
| if (start % kPageSize || end % kPageSize) |
| return false; |
| size_t size = end - start; |
| size_t size_in_pages = size / kPageSize; |
| if (residency->size() != size_in_pages) |
| residency->resize(size_in_pages); |
| int err = HANDLE_EINTR( |
| mincore(reinterpret_cast<void*>(start), size, &(*residency)[0])); |
| PLOG_IF(ERROR, err) << "mincore() failed"; |
| return !err; |
| } |
| |
| // Returns the start and end of .text, aligned to the lower and upper page |
| // boundaries, respectively. |
| std::pair<size_t, size_t> GetTextRange() { |
| // |kStartOfText| may not be at the beginning of a page, since .plt can be |
| // before it, yet in the same mapping for instance. |
| size_t start_page = kStartOfText - kStartOfText % kPageSize; |
| // Set the end to the page on which the beginning of the last symbol is. The |
| // actual symbol may spill into the next page by a few bytes, but this is |
| // outside of the executable code range anyway. |
| size_t end_page = base::bits::Align(kEndOfText, kPageSize); |
| return {start_page, end_page}; |
| } |
| |
| // Returns the start and end pages of the unordered section of .text, aligned to |
| // lower and upper page boundaries, respectively. |
| std::pair<size_t, size_t> GetOrderedTextRange() { |
| size_t start_page = kStartOfOrderedText - kStartOfOrderedText % kPageSize; |
| // kEndOfUnorderedText is not considered ordered, but the byte immediately |
| // before is considered ordered and so can not be contained in the start page. |
| size_t end_page = base::bits::Align(kEndOfOrderedText, kPageSize); |
| return {start_page, end_page}; |
| } |
| |
| // Calls madvise(advice) on the specified range. Does nothing if the range is |
| // empty. |
| void MadviseOnRange(const std::pair<size_t, size_t>& range, int advice) { |
| if (range.first >= range.second) { |
| return; |
| } |
| size_t size = range.second - range.first; |
| int err = madvise(reinterpret_cast<void*>(range.first), size, advice); |
| if (err) { |
| PLOG(ERROR) << "madvise() failed"; |
| } |
| } |
| |
| // Timestamp in ns since Unix Epoch, and residency, as returned by mincore(). |
| struct TimestampAndResidency { |
| uint64_t timestamp_nanos; |
| std::vector<unsigned char> residency; |
| |
| TimestampAndResidency(uint64_t timestamp_nanos, |
| std::vector<unsigned char>&& residency) |
| : timestamp_nanos(timestamp_nanos), residency(residency) {} |
| }; |
| |
| // Returns true for success. |
| bool CollectResidency(size_t start, |
| size_t end, |
| std::vector<TimestampAndResidency>* data) { |
| // Not using base::TimeTicks() to not call too many base:: symbol that would |
| // pollute the reached symbols dumps. |
| struct timespec ts; |
| if (HANDLE_EINTR(clock_gettime(CLOCK_MONOTONIC, &ts))) { |
| PLOG(ERROR) << "Cannot get the time."; |
| return false; |
| } |
| uint64_t now = |
| static_cast<uint64_t>(ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec; |
| std::vector<unsigned char> residency; |
| if (!Mincore(start, end, &residency)) |
| return false; |
| |
| data->emplace_back(now, std::move(residency)); |
| return true; |
| } |
| |
| void DumpResidency(size_t start, |
| size_t end, |
| std::unique_ptr<std::vector<TimestampAndResidency>> data) { |
| auto path = base::FilePath( |
| base::StringPrintf("/data/local/tmp/chrome/residency-%d.txt", getpid())); |
| auto file = |
| base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| if (!file.IsValid()) { |
| PLOG(ERROR) << "Cannot open file to dump the residency data " |
| << path.value(); |
| return; |
| } |
| |
| // First line: start-end of text range. |
| CHECK(IsOrderingSane()); |
| CHECK_LT(start, kStartOfText); |
| CHECK_LT(kEndOfText, end); |
| auto start_end = base::StringPrintf("%" PRIuS " %" PRIuS "\n", |
| kStartOfText - start, kEndOfText - start); |
| file.WriteAtCurrentPos(start_end.c_str(), start_end.size()); |
| |
| for (const auto& data_point : *data) { |
| auto timestamp = |
| base::StringPrintf("%" PRIu64 " ", data_point.timestamp_nanos); |
| file.WriteAtCurrentPos(timestamp.c_str(), timestamp.size()); |
| |
| std::vector<char> dump; |
| dump.reserve(data_point.residency.size() + 1); |
| for (auto c : data_point.residency) |
| dump.push_back(c ? '1' : '0'); |
| dump[dump.size() - 1] = '\n'; |
| file.WriteAtCurrentPos(&dump[0], dump.size()); |
| } |
| } |
| |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| // Used for "LibraryLoader.PrefetchDetailedStatus". |
| enum class PrefetchStatus { |
| kSuccess = 0, |
| kWrongOrdering = 1, |
| kForkFailed = 2, |
| kChildProcessCrashed = 3, |
| kChildProcessKilled = 4, |
| kMaxValue = kChildProcessKilled |
| }; |
| |
| PrefetchStatus ForkAndPrefetch(bool ordered_only) { |
| if (!IsOrderingSane()) { |
| LOG(WARNING) << "Incorrect code ordering"; |
| return PrefetchStatus::kWrongOrdering; |
| } |
| |
| // Looking for ranges is done before the fork, to avoid syscalls and/or memory |
| // allocations in the forked process. The child process inherits the lock |
| // state of its parent thread. It cannot rely on being able to acquire any |
| // lock (unless special care is taken in a pre-fork handler), including being |
| // able to call malloc(). |
| // |
| // Always prefetch the ordered section first, as it's reached early during |
| // startup, and not necessarily located at the beginning of .text. |
| std::vector<std::pair<size_t, size_t>> ranges = {GetOrderedTextRange()}; |
| if (!ordered_only) |
| ranges.push_back(GetTextRange()); |
| |
| pid_t pid = fork(); |
| if (pid == 0) { |
| setpriority(PRIO_PROCESS, 0, kBackgroundPriority); |
| // _exit() doesn't call the atexit() handlers. |
| for (const auto& range : ranges) { |
| Prefetch(range.first, range.second); |
| } |
| _exit(EXIT_SUCCESS); |
| } else { |
| if (pid < 0) { |
| return PrefetchStatus::kForkFailed; |
| } |
| int status; |
| const pid_t result = HANDLE_EINTR(waitpid(pid, &status, 0)); |
| if (result == pid) { |
| if (WIFEXITED(status)) |
| return PrefetchStatus::kSuccess; |
| if (WIFSIGNALED(status)) { |
| int signal = WTERMSIG(status); |
| switch (signal) { |
| case SIGSEGV: |
| case SIGBUS: |
| return PrefetchStatus::kChildProcessCrashed; |
| break; |
| case SIGKILL: |
| case SIGTERM: |
| default: |
| return PrefetchStatus::kChildProcessKilled; |
| } |
| } |
| } |
| // Should not happen. Per man waitpid(2), errors are: |
| // - EINTR: handled. |
| // - ECHILD if the process doesn't have an unwaited-for child with this PID. |
| // - EINVAL. |
| return PrefetchStatus::kChildProcessKilled; |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| void NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary(bool ordered_only) { |
| #if BUILDFLAG(ORDERFILE_INSTRUMENTATION) |
| // Avoid forking with orderfile instrumentation because the child process |
| // would create a dump as well. |
| return; |
| #endif |
| |
| PrefetchStatus status = ForkAndPrefetch(ordered_only); |
| UMA_HISTOGRAM_BOOLEAN("LibraryLoader.PrefetchStatus", |
| status == PrefetchStatus::kSuccess); |
| UMA_HISTOGRAM_ENUMERATION("LibraryLoader.PrefetchDetailedStatus", status); |
| if (status != PrefetchStatus::kSuccess) { |
| LOG(WARNING) << "Cannot prefetch the library. status = " |
| << static_cast<int>(status); |
| } |
| } |
| |
| // static |
| int NativeLibraryPrefetcher::PercentageOfResidentCode(size_t start, |
| size_t end) { |
| size_t total_pages = 0; |
| size_t resident_pages = 0; |
| |
| std::vector<unsigned char> residency; |
| bool ok = Mincore(start, end, &residency); |
| if (!ok) |
| return -1; |
| total_pages += residency.size(); |
| resident_pages += std::count_if(residency.begin(), residency.end(), |
| [](unsigned char x) { return x & 1; }); |
| if (total_pages == 0) |
| return -1; |
| return static_cast<int>((100 * resident_pages) / total_pages); |
| } |
| |
| // static |
| int NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode() { |
| if (!IsOrderingSane()) { |
| LOG(WARNING) << "Incorrect code ordering"; |
| return -1; |
| } |
| const auto& range = GetTextRange(); |
| return PercentageOfResidentCode(range.first, range.second); |
| } |
| |
| // static |
| void NativeLibraryPrefetcher::PeriodicallyCollectResidency() { |
| CHECK_EQ(static_cast<long>(kPageSize), sysconf(_SC_PAGESIZE)); |
| |
| const auto& range = GetTextRange(); |
| auto data = std::make_unique<std::vector<TimestampAndResidency>>(); |
| for (int i = 0; i < 60; ++i) { |
| if (!CollectResidency(range.first, range.second, data.get())) |
| return; |
| usleep(2e5); |
| } |
| DumpResidency(range.first, range.second, std::move(data)); |
| } |
| |
| // static |
| void NativeLibraryPrefetcher::MadviseForOrderfile() { |
| CHECK(IsOrderingSane()); |
| LOG(WARNING) << "Performing experimental madvise from orderfile information"; |
| // First MADV_RANDOM on all of text, then turn the ordered text range back to |
| // normal. The ordered range may be placed anywhere within .text. |
| MadviseOnRange(GetTextRange(), MADV_RANDOM); |
| MadviseOnRange(GetOrderedTextRange(), MADV_NORMAL); |
| } |
| |
| } // namespace android |
| } // namespace base |
| #endif // BUILDFLAG(SUPPORTS_CODE_ORDERING) |