Commit ff4e4bf5 authored by Benoit L's avatar Benoit L Committed by Commit Bot

Reland "android: Don't parse /proc/self/maps to prefetch the library."

This reverts commit 733c2818.

Reason for reland: The initial change is actually an improvement, see
the linked bug.

Original change's description:
> Revert "android: Don't parse /proc/self/maps to prefetch the library."
>
> This reverts commit ef6f0fb3.
>
> Reason for revert: Regressed startup performance.
> See https://chromeperf.appspot.com/report?sid=9df0ec76b0fffc74931cc7bdcf44cd4d540f81bb34ccde3571c2211ec8360ee6&start_rev=531281&end_rev=531574
>
> Original change's description:
> > android: Don't parse /proc/self/maps to prefetch the library.
> >
> > This commit changes the behavior of the native library prefetcher to
> > rely on sentinel symbols rather than parsing /proc/self/maps.
> >
> > Behavior changes:
> > 1. This will make the code no longer prefetch .data (because we don't
> >    look for it in the mappings), neither the sections that are part of
> >    the same mapping as .text (for instance, .rodata when using
> >    ld.gold). This is intended.
> > 2. As a consequence, the UMA metric
> >    LibraryLoader.PercentageOfResidentCodeBeforePrefetch will move, as
> >    both sides of the ratio will change, and the population will change
> >    slightly as well (since code is currently not correctly ordered on
> >    ARM64 and x86_64)
> >
> > - Removes the reliance on a somewhat brittle parsing of a file
> > - Parsing /proc/self/maps costs ~50ms of CPU on a Nexus 5X (on a little
> >   core, as this is triggered from an AsyncTask)
> > - Allows to only fetch part of the native library (in a forthcoming CL)
> >
> > Rationale:
> > Change-Id: I0bb7b28af3c3bd4af5745e2ebcc1fbf283bcc0c1
> > Reviewed-on: https://chromium-review.googlesource.com/874470
> > Commit-Queue: Benoit L <lizeb@chromium.org>
> > Reviewed-by: Egor Pasko <pasko@chromium.org>
> > Reviewed-by: agrieve <agrieve@chromium.org>
> > Reviewed-by: Matthew Cary <mattcary@chromium.org>
> > Cr-Commit-Position: refs/heads/master@{#531515}
>
> TBR=pasko@chromium.org,agrieve@chromium.org,lizeb@chromium.org,mattcary@chromium.org
>
> Change-Id: I11d7cd7898d5f8d8fbebf391eba5fdaca154c08a
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Reviewed-on: https://chromium-review.googlesource.com/886321
> Reviewed-by: Benoit L <lizeb@chromium.org>
> Commit-Queue: Benoit L <lizeb@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#531858}

TBR=pasko@chromium.org,agrieve@chromium.org,lizeb@chromium.org,mattcary@chromium.org
Bug: 805883

Change-Id: Ida889088ec69c09f4dd95dd976174b5532811a53
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Reviewed-on: https://chromium-review.googlesource.com/886621
Commit-Queue: Benoit L <lizeb@chromium.org>
Reviewed-by: default avatarBenoit L <lizeb@chromium.org>
Reviewed-by: default avatarMatthew Cary <mattcary@chromium.org>
Cr-Commit-Position: refs/heads/master@{#531880}
parent ecd6abcb
...@@ -1787,10 +1787,14 @@ buildflag_header("synchronization_flags") { ...@@ -1787,10 +1787,14 @@ buildflag_header("synchronization_flags") {
buildflag_header("anchor_functions_flags") { buildflag_header("anchor_functions_flags") {
header = "anchor_functions_flags.h" header = "anchor_functions_flags.h"
header_dir = "base/android/library_loader" header_dir = "base/android/library_loader"
_supports_code_ordering = current_cpu == "arm"
# buildflag entries added to this header must also must be manually added to # buildflag entries added to this header must also must be manually added to
# tools/gn/bootstrap/bootstrap.py # tools/gn/bootstrap/bootstrap.py
flags = [ "USE_LLD=$use_lld" ] flags = [
"USE_LLD=$use_lld",
"SUPPORTS_CODE_ORDERING=$_supports_code_ordering",
]
} }
# This is the subset of files from base that should not be used with a dynamic # This is the subset of files from base that should not be used with a dynamic
......
...@@ -4,13 +4,11 @@ ...@@ -4,13 +4,11 @@
#include "base/android/library_loader/anchor_functions.h" #include "base/android/library_loader/anchor_functions.h"
#include "base/android/library_loader/anchor_functions_flags.h"
#include "base/logging.h" #include "base/logging.h"
#include "build/build_config.h" #include "build/build_config.h"
// asm() macros below don't compile on x86, and haven't been validated outside #if BUILDFLAG(SUPPORTS_CODE_ORDERING)
// ARM.
#if defined(ARCH_CPU_ARMEL)
// These functions are here to, respectively: // These functions are here to, respectively:
// 1. Check that functions are ordered // 1. Check that functions are ordered
// 2. Delimit the start of .text // 2. Delimit the start of .text
...@@ -68,20 +66,18 @@ const size_t kStartOfText = ...@@ -68,20 +66,18 @@ const size_t kStartOfText =
const size_t kEndOfText = const size_t kEndOfText =
reinterpret_cast<size_t>(dummy_function_at_the_end_of_text); reinterpret_cast<size_t>(dummy_function_at_the_end_of_text);
void CheckOrderingSanity() { bool IsOrderingSane() {
size_t dummy = reinterpret_cast<size_t>(&dummy_function_to_check_ordering);
size_t here = reinterpret_cast<size_t>(&IsOrderingSane);
// The linker usually keeps the input file ordering for symbols. // The linker usually keeps the input file ordering for symbols.
// dummy_function_to_anchor_text() should then be after // dummy_function_to_anchor_text() should then be after
// dummy_function_to_check_ordering() without ordering. // dummy_function_to_check_ordering() without ordering.
// This check is thus intended to catch the lack of ordering. // This check is thus intended to catch the lack of ordering.
CHECK_LT(kStartOfText, return kStartOfText < dummy && dummy < kEndOfText && kStartOfText < here &&
reinterpret_cast<size_t>(&dummy_function_to_check_ordering)); here < kEndOfText;
CHECK_LT(kStartOfText, kEndOfText);
CHECK_LT(kStartOfText,
reinterpret_cast<size_t>(&dummy_function_to_check_ordering));
CHECK_LT(kStartOfText, reinterpret_cast<size_t>(&CheckOrderingSanity));
CHECK_GT(kEndOfText, reinterpret_cast<size_t>(&CheckOrderingSanity));
} }
} // namespace android } // namespace android
} // namespace base } // namespace base
#endif // defined(ARCH_CPU_ARMEL)
#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
...@@ -6,11 +6,12 @@ ...@@ -6,11 +6,12 @@
#define BASE_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_ #define BASE_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_
#include <cstdint> #include <cstdint>
#include "base/android/library_loader/anchor_functions_flags.h"
#include "base/base_export.h" #include "base/base_export.h"
#include "build/build_config.h"
#if defined(ARCH_CPU_ARMEL) #if BUILDFLAG(SUPPORTS_CODE_ORDERING)
namespace base { namespace base {
namespace android { namespace android {
...@@ -18,11 +19,11 @@ namespace android { ...@@ -18,11 +19,11 @@ namespace android {
BASE_EXPORT extern const size_t kStartOfText; BASE_EXPORT extern const size_t kStartOfText;
BASE_EXPORT extern const size_t kEndOfText; BASE_EXPORT extern const size_t kEndOfText;
// Basic CHECK()s ensuring that the symbols above are correctly set. // Returns true if the ordering looks sane.
BASE_EXPORT void CheckOrderingSanity(); BASE_EXPORT bool IsOrderingSane();
} // namespace android } // namespace android
} // namespace base } // namespace base
#endif // defined(ARCH_CPU_ARMEL) #endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
#endif // BASE_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_ #endif // BASE_ANDROID_LIBRARY_LOADER_ANCHOR_FUNCTIONS_H_
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include "base/android/library_loader/library_loader_hooks.h" #include "base/android/library_loader/library_loader_hooks.h"
#include "base/android/jni_string.h" #include "base/android/jni_string.h"
#include "base/android/library_loader/anchor_functions_flags.h"
#include "base/android/library_loader/library_load_from_apk_status_codes.h" #include "base/android/library_loader/library_load_from_apk_status_codes.h"
#include "base/android/library_loader/library_prefetcher.h" #include "base/android/library_loader/library_prefetcher.h"
#include "base/at_exit.h" #include "base/at_exit.h"
...@@ -169,7 +170,12 @@ static jboolean JNI_LibraryLoader_LibraryLoaded( ...@@ -169,7 +170,12 @@ static jboolean JNI_LibraryLoader_LibraryLoaded(
const JavaParamRef<jobject>& jcaller) { const JavaParamRef<jobject>& jcaller) {
if (CommandLine::ForCurrentProcess()->HasSwitch( if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kMadviseRandomExecutableCode)) { switches::kMadviseRandomExecutableCode)) {
#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
NativeLibraryPrefetcher::MadviseRandomText(); NativeLibraryPrefetcher::MadviseRandomText();
#else
LOG(WARNING) << switches::kMadviseRandomExecutableCode
<< " is not supported on this architecture.";
#endif
} }
if (g_native_initialization_hook && !g_native_initialization_hook()) if (g_native_initialization_hook && !g_native_initialization_hook())
...@@ -189,19 +195,31 @@ void LibraryLoaderExitHook() { ...@@ -189,19 +195,31 @@ void LibraryLoaderExitHook() {
static jboolean JNI_LibraryLoader_ForkAndPrefetchNativeLibrary( static jboolean JNI_LibraryLoader_ForkAndPrefetchNativeLibrary(
JNIEnv* env, JNIEnv* env,
const JavaParamRef<jclass>& clazz) { const JavaParamRef<jclass>& clazz) {
#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
return NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary(); return NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary();
#else
return false;
#endif
} }
static jint JNI_LibraryLoader_PercentageOfResidentNativeLibraryCode( static jint JNI_LibraryLoader_PercentageOfResidentNativeLibraryCode(
JNIEnv* env, JNIEnv* env,
const JavaParamRef<jclass>& clazz) { const JavaParamRef<jclass>& clazz) {
#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
return NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode(); return NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode();
#else
return -1;
#endif
} }
static void JNI_LibraryLoader_PeriodicallyCollectResidency( static void JNI_LibraryLoader_PeriodicallyCollectResidency(
JNIEnv* env, JNIEnv* env,
const JavaParamRef<jclass>& clazz) { const JavaParamRef<jclass>& clazz) {
return NativeLibraryPrefetcher::PeriodicallyCollectResidency(); #if BUILDFLAG(SUPPORTS_CODE_ORDERING)
NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode();
#else
LOG(WARNING) << "Collecting residency is not supported.";
#endif
} }
void SetVersionNumber(const char* version_number) { void SetVersionNumber(const char* version_number) {
......
...@@ -18,12 +18,15 @@ ...@@ -18,12 +18,15 @@
#include "base/bits.h" #include "base/bits.h"
#include "base/files/file.h" #include "base/files/file.h"
#include "base/format_macros.h" #include "base/format_macros.h"
#include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/posix/eintr_wrapper.h" #include "base/posix/eintr_wrapper.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "build/build_config.h" #include "build/build_config.h"
#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
namespace base { namespace base {
namespace android { namespace android {
...@@ -32,28 +35,11 @@ namespace { ...@@ -32,28 +35,11 @@ namespace {
// Android defines the background priority to this value since at least 2009 // Android defines the background priority to this value since at least 2009
// (see Process.java). // (see Process.java).
constexpr int kBackgroundPriority = 10; constexpr int kBackgroundPriority = 10;
// Valid for all the Android architectures. // Valid for all Android architectures.
constexpr size_t kPageSize = 4096; constexpr size_t kPageSize = 4096;
constexpr char kLibchromeSuffix[] = "libchrome.so";
// "base.apk" is a suffix because the library may be loaded directly from the
// APK.
constexpr const char* kSuffixesToMatch[] = {kLibchromeSuffix, "base.apk"};
bool IsReadableAndPrivate(const base::debug::MappedMemoryRegion& region) {
return region.permissions & base::debug::MappedMemoryRegion::READ &&
region.permissions & base::debug::MappedMemoryRegion::PRIVATE;
}
bool PathMatchesSuffix(const std::string& path) { // Reads a byte per page between |start| and |end| to force it into the page
for (size_t i = 0; i < arraysize(kSuffixesToMatch); i++) { // cache.
if (EndsWith(path, kSuffixesToMatch[i], CompareCase::SENSITIVE)) {
return true;
}
}
return false;
}
// For each range, reads a byte per page to force it into the page cache.
// Heap allocations, syscalls and library functions are not allowed in this // Heap allocations, syscalls and library functions are not allowed in this
// function. // function.
// Returns true for success. // Returns true for success.
...@@ -64,47 +50,37 @@ bool PathMatchesSuffix(const std::string& path) { ...@@ -64,47 +50,37 @@ bool PathMatchesSuffix(const std::string& path) {
// for the context. // for the context.
__attribute__((no_sanitize_address)) __attribute__((no_sanitize_address))
#endif #endif
bool Prefetch(const std::vector<std::pair<uintptr_t, uintptr_t>>& ranges) { bool Prefetch(size_t start, size_t end) {
for (const auto& range : ranges) { unsigned char* start_ptr = reinterpret_cast<unsigned char*>(start);
const uintptr_t page_mask = kPageSize - 1; unsigned char* end_ptr = reinterpret_cast<unsigned char*>(end);
// If start or end is not page-aligned, parsing went wrong. It is better to
// exit with an error.
if ((range.first & page_mask) || (range.second & page_mask)) {
return false; // CHECK() is not allowed here.
}
unsigned char* start_ptr = reinterpret_cast<unsigned char*>(range.first);
unsigned char* end_ptr = reinterpret_cast<unsigned char*>(range.second);
unsigned char dummy = 0; unsigned char dummy = 0;
for (unsigned char* ptr = start_ptr; ptr < end_ptr; ptr += kPageSize) { for (unsigned char* ptr = start_ptr; ptr < end_ptr; ptr += kPageSize) {
// Volatile is required to prevent the compiler from eliminating this // Volatile is required to prevent the compiler from eliminating this
// loop. // loop.
dummy ^= *static_cast<volatile unsigned char*>(ptr); dummy ^= *static_cast<volatile unsigned char*>(ptr);
} }
}
return true; return true;
} }
// Populate the per-page residency for |range| in |residency|. If successful, // Populates the per-page residency between |start| and |end| in |residency|. If
// |residency| has the size of |range| in pages. // successful, |residency| has the size of |end| - |start| in pages.
// Returns true for success. // Returns true for success.
bool MincoreOnRange(const NativeLibraryPrefetcher::AddressRange& range, bool Mincore(size_t start, size_t end, std::vector<unsigned char>* residency) {
std::vector<unsigned char>* residency) { if (start % kPageSize || end % kPageSize)
if (range.first % kPageSize || range.second % kPageSize)
return false; return false;
size_t size = range.second - range.first; size_t size = end - start;
size_t size_in_pages = size / kPageSize; size_t size_in_pages = size / kPageSize;
if (residency->size() != size_in_pages) if (residency->size() != size_in_pages)
residency->resize(size_in_pages); residency->resize(size_in_pages);
int err = HANDLE_EINTR( int err = HANDLE_EINTR(
mincore(reinterpret_cast<void*>(range.first), size, &(*residency)[0])); mincore(reinterpret_cast<void*>(start), size, &(*residency)[0]));
PLOG_IF(ERROR, err) << "mincore() failed"; PLOG_IF(ERROR, err) << "mincore() failed";
return !err; return !err;
} }
#if defined(ARCH_CPU_ARMEL)
// Returns the start and end of .text, aligned to the lower and upper page // Returns the start and end of .text, aligned to the lower and upper page
// boundaries, respectively. // boundaries, respectively.
NativeLibraryPrefetcher::AddressRange GetTextRange() { std::pair<size_t, size_t> GetTextRange() {
// |kStartOftext| may not be at the beginning of a page, since .plt can be // |kStartOftext| may not be at the beginning of a page, since .plt can be
// before it, yet in the same mapping for instance. // before it, yet in the same mapping for instance.
size_t start_page = kStartOfText - kStartOfText % kPageSize; size_t start_page = kStartOfText - kStartOfText % kPageSize;
...@@ -126,7 +102,8 @@ struct TimestampAndResidency { ...@@ -126,7 +102,8 @@ struct TimestampAndResidency {
}; };
// Returns true for success. // Returns true for success.
bool CollectResidency(const NativeLibraryPrefetcher::AddressRange& range, bool CollectResidency(size_t start,
size_t end,
std::vector<TimestampAndResidency>* data) { std::vector<TimestampAndResidency>* data) {
// Not using base::TimeTicks() to not call too many base:: symbol that would // Not using base::TimeTicks() to not call too many base:: symbol that would
// pollute the reached symbols dumps. // pollute the reached symbols dumps.
...@@ -138,14 +115,15 @@ bool CollectResidency(const NativeLibraryPrefetcher::AddressRange& range, ...@@ -138,14 +115,15 @@ bool CollectResidency(const NativeLibraryPrefetcher::AddressRange& range,
uint64_t now = uint64_t now =
static_cast<uint64_t>(ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec; static_cast<uint64_t>(ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec;
std::vector<unsigned char> residency; std::vector<unsigned char> residency;
if (!MincoreOnRange(range, &residency)) if (!Mincore(start, end, &residency))
return false; return false;
data->emplace_back(now, std::move(residency)); data->emplace_back(now, std::move(residency));
return true; return true;
} }
void DumpResidency(const NativeLibraryPrefetcher::AddressRange& range, void DumpResidency(size_t start,
size_t end,
std::unique_ptr<std::vector<TimestampAndResidency>> data) { std::unique_ptr<std::vector<TimestampAndResidency>> data) {
auto path = base::FilePath( auto path = base::FilePath(
base::StringPrintf("/data/local/tmp/chrome/residency-%d.txt", getpid())); base::StringPrintf("/data/local/tmp/chrome/residency-%d.txt", getpid()));
...@@ -158,12 +136,11 @@ void DumpResidency(const NativeLibraryPrefetcher::AddressRange& range, ...@@ -158,12 +136,11 @@ void DumpResidency(const NativeLibraryPrefetcher::AddressRange& range,
} }
// First line: start-end of text range. // First line: start-end of text range.
CheckOrderingSanity(); CHECK(IsOrderingSane());
CHECK_LT(range.first, kStartOfText); CHECK_LT(start, kStartOfText);
CHECK_LT(kEndOfText, range.second); CHECK_LT(kEndOfText, end);
auto start_end = auto start_end = base::StringPrintf("%" PRIuS " %" PRIuS "\n",
base::StringPrintf("%" PRIuS " %" PRIuS "\n", kStartOfText - range.first, kStartOfText - start, kEndOfText - start);
kEndOfText - range.first);
file.WriteAtCurrentPos(start_end.c_str(), start_end.size()); file.WriteAtCurrentPos(start_end.c_str(), start_end.size());
for (const auto& data_point : *data) { for (const auto& data_point : *data) {
...@@ -179,60 +156,8 @@ void DumpResidency(const NativeLibraryPrefetcher::AddressRange& range, ...@@ -179,60 +156,8 @@ void DumpResidency(const NativeLibraryPrefetcher::AddressRange& range,
file.WriteAtCurrentPos(&dump[0], dump.size()); file.WriteAtCurrentPos(&dump[0], dump.size());
} }
} }
#endif // defined(ARCH_CPU_ARMEL)
} // namespace } // namespace
// static
bool NativeLibraryPrefetcher::IsGoodToPrefetch(
const base::debug::MappedMemoryRegion& region) {
return PathMatchesSuffix(region.path) &&
IsReadableAndPrivate(region); // .text and .data mappings are private.
}
// static
void NativeLibraryPrefetcher::FilterLibchromeRangesOnlyIfPossible(
const std::vector<base::debug::MappedMemoryRegion>& regions,
std::vector<AddressRange>* ranges) {
bool has_libchrome_region = false;
for (const base::debug::MappedMemoryRegion& region : regions) {
if (EndsWith(region.path, kLibchromeSuffix, CompareCase::SENSITIVE)) {
has_libchrome_region = true;
break;
}
}
for (const base::debug::MappedMemoryRegion& region : regions) {
if (has_libchrome_region &&
!EndsWith(region.path, kLibchromeSuffix, CompareCase::SENSITIVE)) {
continue;
}
ranges->push_back(std::make_pair(region.start, region.end));
}
}
// static
bool NativeLibraryPrefetcher::FindRanges(std::vector<AddressRange>* ranges) {
// All code (including in the forked process) relies on this assumption.
if (sysconf(_SC_PAGESIZE) != static_cast<long>(kPageSize))
return false;
std::string proc_maps;
if (!base::debug::ReadProcMaps(&proc_maps))
return false;
std::vector<base::debug::MappedMemoryRegion> regions;
if (!base::debug::ParseProcMaps(proc_maps, &regions))
return false;
std::vector<base::debug::MappedMemoryRegion> regions_to_prefetch;
for (const auto& region : regions) {
if (IsGoodToPrefetch(region)) {
regions_to_prefetch.push_back(region);
}
}
FilterLibchromeRangesOnlyIfPossible(regions_to_prefetch, ranges);
return true;
}
// static // static
bool NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary() { bool NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary() {
// Avoid forking with cygprofile instrumentation because the latter performs // Avoid forking with cygprofile instrumentation because the latter performs
...@@ -241,20 +166,23 @@ bool NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary() { ...@@ -241,20 +166,23 @@ bool NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary() {
return false; return false;
#endif #endif
if (!IsOrderingSane()) {
LOG(WARNING) << "Incorrect code ordering";
return false;
}
// Looking for ranges is done before the fork, to avoid syscalls and/or memory // 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 // 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 // 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 // lock (unless special care is taken in a pre-fork handler), including being
// able to call malloc(). // able to call malloc().
std::vector<AddressRange> ranges; const auto& range = GetTextRange();
if (!FindRanges(&ranges))
return false;
pid_t pid = fork(); pid_t pid = fork();
if (pid == 0) { if (pid == 0) {
setpriority(PRIO_PROCESS, 0, kBackgroundPriority); setpriority(PRIO_PROCESS, 0, kBackgroundPriority);
// _exit() doesn't call the atexit() handlers. // _exit() doesn't call the atexit() handlers.
_exit(Prefetch(ranges) ? 0 : 1); _exit(Prefetch(range.first, range.second) ? 0 : 1);
} else { } else {
if (pid < 0) { if (pid < 0) {
return false; return false;
...@@ -271,20 +199,18 @@ bool NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary() { ...@@ -271,20 +199,18 @@ bool NativeLibraryPrefetcher::ForkAndPrefetchNativeLibrary() {
} }
// static // static
int NativeLibraryPrefetcher::PercentageOfResidentCode( int NativeLibraryPrefetcher::PercentageOfResidentCode(size_t start,
const std::vector<AddressRange>& ranges) { size_t end) {
size_t total_pages = 0; size_t total_pages = 0;
size_t resident_pages = 0; size_t resident_pages = 0;
for (const auto& range : ranges) {
std::vector<unsigned char> residency; std::vector<unsigned char> residency;
bool ok = MincoreOnRange(range, &residency); bool ok = Mincore(start, end, &residency);
if (!ok) if (!ok)
return -1; return -1;
total_pages += residency.size(); total_pages += residency.size();
resident_pages += std::count_if(residency.begin(), residency.end(), resident_pages += std::count_if(residency.begin(), residency.end(),
[](unsigned char x) { return x & 1; }); [](unsigned char x) { return x & 1; });
}
if (total_pages == 0) if (total_pages == 0)
return -1; return -1;
return static_cast<int>((100 * resident_pages) / total_pages); return static_cast<int>((100 * resident_pages) / total_pages);
...@@ -292,44 +218,39 @@ int NativeLibraryPrefetcher::PercentageOfResidentCode( ...@@ -292,44 +218,39 @@ int NativeLibraryPrefetcher::PercentageOfResidentCode(
// static // static
int NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode() { int NativeLibraryPrefetcher::PercentageOfResidentNativeLibraryCode() {
std::vector<AddressRange> ranges; if (!IsOrderingSane()) {
if (!FindRanges(&ranges)) LOG(WARNING) << "Incorrect code ordering";
return -1; return -1;
return PercentageOfResidentCode(ranges); }
const auto& range = GetTextRange();
return PercentageOfResidentCode(range.first, range.second);
} }
// static // static
void NativeLibraryPrefetcher::PeriodicallyCollectResidency() { void NativeLibraryPrefetcher::PeriodicallyCollectResidency() {
#if defined(ARCH_CPU_ARMEL)
CHECK_EQ(static_cast<long>(kPageSize), sysconf(_SC_PAGESIZE)); CHECK_EQ(static_cast<long>(kPageSize), sysconf(_SC_PAGESIZE));
const auto& range = GetTextRange(); const auto& range = GetTextRange();
auto data = std::make_unique<std::vector<TimestampAndResidency>>(); auto data = std::make_unique<std::vector<TimestampAndResidency>>();
for (int i = 0; i < 60; ++i) { for (int i = 0; i < 60; ++i) {
if (!CollectResidency(range, data.get())) if (!CollectResidency(range.first, range.second, data.get()))
return; return;
usleep(2e5); usleep(2e5);
} }
DumpResidency(range, std::move(data)); DumpResidency(range.first, range.second, std::move(data));
#else
CHECK(false) << "Only supported on ARM";
#endif
} }
// static // static
void NativeLibraryPrefetcher::MadviseRandomText() { void NativeLibraryPrefetcher::MadviseRandomText() {
#if defined(ARCH_CPU_ARMEL) CHECK(IsOrderingSane());
CheckOrderingSanity();
const auto& range = GetTextRange(); const auto& range = GetTextRange();
size_t size = range.second - range.first; size_t size = range.second - range.first;
int err = madvise(reinterpret_cast<void*>(range.first), size, MADV_RANDOM); int err = madvise(reinterpret_cast<void*>(range.first), size, MADV_RANDOM);
if (err) { if (err) {
PLOG(ERROR) << "madvise() failed"; PLOG(ERROR) << "madvise() failed";
} }
#else
CHECK(false) << "Only supported on ARM.";
#endif // defined(ARCH_CPU_ARMEL)
} }
} // namespace android } // namespace android
} // namespace base } // namespace base
#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
...@@ -8,33 +8,32 @@ ...@@ -8,33 +8,32 @@
#include <jni.h> #include <jni.h>
#include <stdint.h> #include <stdint.h>
#include <string>
#include <utility>
#include <vector>
#include "base/debug/proc_maps_linux.h" #include "base/android/library_loader/anchor_functions_flags.h"
#include "base/base_export.h"
#include "base/gtest_prod_util.h" #include "base/gtest_prod_util.h"
#include "base/macros.h" #include "base/macros.h"
#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
namespace base { namespace base {
namespace android { namespace android {
// Forks and waits for a process prefetching the native library. This is done in // Forks and waits for a process prefetching the native library. This is done in
// a forked process for the following reasons: // a forked process for the following reasons:
// - Isolating the main process from mistakes in the parsing. If the parsing // - Isolating the main process from mistakes in getting the address range, only
// returns an incorrect address, only the forked process will crash. // crashing the forked process in case of mistake.
// - Not inflating the memory used by the main process uselessly, which could // - Not inflating the memory used by the main process uselessly, which could
// increase its likelihood to be killed. // increase its likelihood to be killed.
// The forked process has background priority and, since it is not declared to // The forked process has background priority and, since it is not declared to
// the Android runtime, can be killed at any time, which is not an issue here. // the Android runtime, can be killed at any time, which is not an issue here.
class BASE_EXPORT NativeLibraryPrefetcher { class BASE_EXPORT NativeLibraryPrefetcher {
public: public:
using AddressRange = std::pair<uintptr_t, uintptr_t>; // Finds the executable code range, forks a low priority process pre-fetching
// it wait()s for the process to exit or die.
// Finds the ranges matching the native library, forks a low priority
// process pre-fetching these ranges and wait()s for it.
// Returns true for success. // Returns true for success.
static bool ForkAndPrefetchNativeLibrary(); static bool ForkAndPrefetchNativeLibrary();
// Returns the percentage of the native library code currently resident in // Returns the percentage of the native library code currently resident in
// memory, or -1 in case of error. // memory, or -1 in case of error.
static int PercentageOfResidentNativeLibraryCode(); static int PercentageOfResidentNativeLibraryCode();
...@@ -47,38 +46,12 @@ class BASE_EXPORT NativeLibraryPrefetcher { ...@@ -47,38 +46,12 @@ class BASE_EXPORT NativeLibraryPrefetcher {
static void MadviseRandomText(); static void MadviseRandomText();
private: private:
// Returns true if the region matches native code or data. // Returns the percentage of [start, end] currently resident in
static bool IsGoodToPrefetch(const base::debug::MappedMemoryRegion& region);
// Filters the regions to keep only libchrome ranges if possible.
static void FilterLibchromeRangesOnlyIfPossible(
const std::vector<base::debug::MappedMemoryRegion>& regions,
std::vector<AddressRange>* ranges);
// Finds the ranges matching the native library in /proc/self/maps.
// Returns true for success.
static bool FindRanges(std::vector<AddressRange>* ranges);
// Returns the percentage of the given address ranges currently resident in
// memory, or -1 in case of error. // memory, or -1 in case of error.
static int PercentageOfResidentCode(const std::vector<AddressRange>& ranges); static int PercentageOfResidentCode(size_t start, size_t end);
FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
TestIsGoodToPrefetchNoRange);
FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
TestIsGoodToPrefetchUnreadableRange);
FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
TestIsGoodToPrefetchSkipSharedRange);
FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
TestIsGoodToPrefetchLibchromeRange);
FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
TestIsGoodToPrefetchBaseApkRange);
FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
TestFilterLibchromeRangesOnlyIfPossibleNoLibchrome);
FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
TestFilterLibchromeRangesOnlyIfPossibleHasLibchrome);
FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest, FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
TestPercentageOfResidentCode); TestPercentageOfResidentCode);
FRIEND_TEST_ALL_PREFIXES(NativeLibraryPrefetcherTest,
TestPercentageOfResidentCodeTwoRegions);
DISALLOW_IMPLICIT_CONSTRUCTORS(NativeLibraryPrefetcher); DISALLOW_IMPLICIT_CONSTRUCTORS(NativeLibraryPrefetcher);
}; };
...@@ -86,4 +59,6 @@ class BASE_EXPORT NativeLibraryPrefetcher { ...@@ -86,4 +59,6 @@ class BASE_EXPORT NativeLibraryPrefetcher {
} // namespace android } // namespace android
} // namespace base } // namespace base
#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
#endif // BASE_ANDROID_LIBRARY_LOADER_LIBRARY_PREFETCHER_H_ #endif // BASE_ANDROID_LIBRARY_LOADER_LIBRARY_PREFETCHER_H_
...@@ -7,96 +7,15 @@ ...@@ -7,96 +7,15 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <string> #include "base/android/library_loader/anchor_functions_flags.h"
#include <vector>
#include "base/debug/proc_maps_linux.h"
#include "base/memory/shared_memory.h" #include "base/memory/shared_memory.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
namespace base { namespace base {
namespace android { namespace android {
namespace {
const uint8_t kRead = base::debug::MappedMemoryRegion::READ;
const uint8_t kReadPrivate = base::debug::MappedMemoryRegion::READ |
base::debug::MappedMemoryRegion::PRIVATE;
const uint8_t kExecutePrivate = base::debug::MappedMemoryRegion::EXECUTE |
base::debug::MappedMemoryRegion::PRIVATE;
} // namespace
TEST(NativeLibraryPrefetcherTest, TestIsGoodToPrefetchNoRange) {
const base::debug::MappedMemoryRegion regions[4] = {
base::debug::MappedMemoryRegion{0x4000, 0x5000, 10, 0, kReadPrivate, ""},
base::debug::MappedMemoryRegion{0x4000, 0x5000, 10, 0, kReadPrivate,
"foo"},
base::debug::MappedMemoryRegion{0x4000, 0x5000, 10, 0, kReadPrivate,
"foobar.apk"},
base::debug::MappedMemoryRegion{0x4000, 0x5000, 10, 0, kReadPrivate,
"libchromium.so"}};
for (int i = 0; i < 4; ++i) {
ASSERT_FALSE(NativeLibraryPrefetcher::IsGoodToPrefetch(regions[i]));
}
}
TEST(NativeLibraryPrefetcherTest, TestIsGoodToPrefetchUnreadableRange) {
const base::debug::MappedMemoryRegion region = {
0x4000, 0x5000, 10, 0, kExecutePrivate, "base.apk"};
ASSERT_FALSE(NativeLibraryPrefetcher::IsGoodToPrefetch(region));
}
TEST(NativeLibraryPrefetcherTest, TestIsGoodToPrefetchSkipSharedRange) {
const base::debug::MappedMemoryRegion region = {
0x4000, 0x5000, 10, 0, kRead, "base.apk"};
ASSERT_FALSE(NativeLibraryPrefetcher::IsGoodToPrefetch(region));
}
TEST(NativeLibraryPrefetcherTest, TestIsGoodToPrefetchLibchromeRange) {
const base::debug::MappedMemoryRegion region = {
0x4000, 0x5000, 10, 0, kReadPrivate, "libchrome.so"};
ASSERT_TRUE(NativeLibraryPrefetcher::IsGoodToPrefetch(region));
}
TEST(NativeLibraryPrefetcherTest, TestIsGoodToPrefetchBaseApkRange) {
const base::debug::MappedMemoryRegion region = {
0x4000, 0x5000, 10, 0, kReadPrivate, "base.apk"};
ASSERT_TRUE(NativeLibraryPrefetcher::IsGoodToPrefetch(region));
}
TEST(NativeLibraryPrefetcherTest,
TestFilterLibchromeRangesOnlyIfPossibleNoLibchrome) {
std::vector<base::debug::MappedMemoryRegion> regions;
regions.push_back(base::debug::MappedMemoryRegion{0x1, 0x2, 0, 0,
kReadPrivate, "base.apk"});
regions.push_back(base::debug::MappedMemoryRegion{0x3, 0x4, 0, 0,
kReadPrivate, "base.apk"});
std::vector<NativeLibraryPrefetcher::AddressRange> ranges;
NativeLibraryPrefetcher::FilterLibchromeRangesOnlyIfPossible(regions,
&ranges);
EXPECT_EQ(ranges.size(), 2U);
EXPECT_EQ(ranges[0].first, 0x1U);
EXPECT_EQ(ranges[0].second, 0x2U);
EXPECT_EQ(ranges[1].first, 0x3U);
EXPECT_EQ(ranges[1].second, 0x4U);
}
TEST(NativeLibraryPrefetcherTest,
TestFilterLibchromeRangesOnlyIfPossibleHasLibchrome) {
std::vector<base::debug::MappedMemoryRegion> regions;
regions.push_back(base::debug::MappedMemoryRegion{0x1, 0x2, 0, 0,
kReadPrivate, "base.apk"});
regions.push_back(base::debug::MappedMemoryRegion{
0x6, 0x7, 0, 0, kReadPrivate, "libchrome.so"});
regions.push_back(base::debug::MappedMemoryRegion{0x3, 0x4, 0, 0,
kReadPrivate, "base.apk"});
std::vector<NativeLibraryPrefetcher::AddressRange> ranges;
NativeLibraryPrefetcher::FilterLibchromeRangesOnlyIfPossible(regions,
&ranges);
EXPECT_EQ(ranges.size(), 1U);
EXPECT_EQ(ranges[0].first, 0x6U);
EXPECT_EQ(ranges[0].second, 0x7U);
}
// Fails with ASAN, crbug.com/570423. // Fails with ASAN, crbug.com/570423.
#if !defined(ADDRESS_SANITIZER) #if !defined(ADDRESS_SANITIZER)
namespace { namespace {
...@@ -108,55 +27,20 @@ TEST(NativeLibraryPrefetcherTest, TestPercentageOfResidentCode) { ...@@ -108,55 +27,20 @@ TEST(NativeLibraryPrefetcherTest, TestPercentageOfResidentCode) {
base::SharedMemory shared_mem; base::SharedMemory shared_mem;
ASSERT_TRUE(shared_mem.CreateAndMapAnonymous(length)); ASSERT_TRUE(shared_mem.CreateAndMapAnonymous(length));
void* address = shared_mem.memory(); void* address = shared_mem.memory();
size_t start = reinterpret_cast<size_t>(address);
std::vector<NativeLibraryPrefetcher::AddressRange> ranges = { size_t end = start + length;
{reinterpret_cast<uintptr_t>(address),
reinterpret_cast<uintptr_t>(address) + length}};
// Remove everything. // Remove everything.
ASSERT_EQ(0, madvise(address, length, MADV_DONTNEED)); ASSERT_EQ(0, madvise(address, length, MADV_DONTNEED));
// TODO(lizeb): If flaky, mock mincore(). EXPECT_EQ(0, NativeLibraryPrefetcher::PercentageOfResidentCode(start, end));
EXPECT_EQ(0, NativeLibraryPrefetcher::PercentageOfResidentCode(ranges));
// Get everything back. // Get everything back.
ASSERT_EQ(0, mlock(address, length)); ASSERT_EQ(0, mlock(address, length));
EXPECT_EQ(100, NativeLibraryPrefetcher::PercentageOfResidentCode(ranges)); EXPECT_EQ(100, NativeLibraryPrefetcher::PercentageOfResidentCode(start, end));
munlock(address, length);
}
TEST(NativeLibraryPrefetcherTest, TestPercentageOfResidentCodeTwoRegions) {
size_t length = 4 * kPageSize;
base::SharedMemory shared_mem;
ASSERT_TRUE(shared_mem.CreateAndMapAnonymous(length));
void* address = shared_mem.memory();
size_t length2 = 8 * kPageSize;
base::SharedMemory shared_mem2;
ASSERT_TRUE(shared_mem2.CreateAndMapAnonymous(length2));
void* address2 = shared_mem2.memory();
std::vector<NativeLibraryPrefetcher::AddressRange> ranges = {
{reinterpret_cast<uintptr_t>(address),
reinterpret_cast<uintptr_t>(address) + length},
{reinterpret_cast<uintptr_t>(address2),
reinterpret_cast<uintptr_t>(address2) + length2}};
// Remove everything.
ASSERT_EQ(0, madvise(address, length, MADV_DONTNEED));
ASSERT_EQ(0, madvise(address2, length, MADV_DONTNEED));
// TODO(lizeb): If flaky, mock mincore().
EXPECT_EQ(0, NativeLibraryPrefetcher::PercentageOfResidentCode(ranges));
// Get back the first range.
ASSERT_EQ(0, mlock(address, length));
EXPECT_EQ(33, NativeLibraryPrefetcher::PercentageOfResidentCode(ranges));
// The second one.
ASSERT_EQ(0, mlock(address2, length2));
EXPECT_EQ(100, NativeLibraryPrefetcher::PercentageOfResidentCode(ranges));
munlock(address, length); munlock(address, length);
munlock(address2, length);
} }
#endif // !defined(ADDRESS_SANITIZER) #endif // !defined(ADDRESS_SANITIZER)
} // namespace android } // namespace android
} // namespace base } // namespace base
#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)
...@@ -161,7 +161,7 @@ void Disable() { ...@@ -161,7 +161,7 @@ void Disable() {
void SanityChecks() { void SanityChecks() {
CHECK_LT(base::android::kEndOfText - base::android::kStartOfText, CHECK_LT(base::android::kEndOfText - base::android::kStartOfText,
kMaxTextSizeInBytes); kMaxTextSizeInBytes);
base::android::CheckOrderingSanity(); CHECK(base::android::IsOrderingSane());
} }
bool SwitchToNextPhaseOrDump(int pid, uint64_t start_ns_since_epoch) { bool SwitchToNextPhaseOrDump(int pid, uint64_t start_ns_since_epoch) {
......
...@@ -227,7 +227,7 @@ def build_gn_with_ninja_manually(tempdir, options): ...@@ -227,7 +227,7 @@ def build_gn_with_ninja_manually(tempdir, options):
write_buildflag_header_manually( write_buildflag_header_manually(
root_gen_dir, 'base/android/library_loader.h', root_gen_dir, 'base/android/library_loader.h',
{'USE_LLD': 'false'}) {'USE_LLD': 'false', 'SUPPORTS_CODE_ORDERING': 'false'})
write_gn_ninja(os.path.join(tempdir, 'build.ninja'), write_gn_ninja(os.path.join(tempdir, 'build.ninja'),
root_gen_dir, options) root_gen_dir, options)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment