blob: 110acadb64a9f8cff388c73036d8245f4e246760 [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif
#include "base/android/library_loader/library_prefetcher.h"
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <algorithm>
#include <array>
#include <cerrno>
#include <csignal>
#include <cstddef>
#include <string>
#include "base/android/library_loader/anchor_functions.h"
#include "base/android/orderfile/orderfile_buildflags.h"
#include "base/bits.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/features.h"
#include "base/logging.h"
#include "base/memory/page_size.h"
#include "base/metrics/histogram_functions.h"
#include "base/posix/eintr_wrapper.h"
#include "base/threading/platform_thread.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#if BUILDFLAG(ORDERFILE_INSTRUMENTATION)
#include "base/android/orderfile/orderfile_instrumentation.h" // nogncheck
#endif
#if BUILDFLAG(SUPPORTS_CODE_ORDERING)
namespace base {
namespace android {
namespace {
#if !BUILDFLAG(ORDERFILE_INSTRUMENTATION)
// The binary is aligned to a minimum 16K page size on AArch64 Android, else 4K.
// This might not match base::GetPageSize(), the booted Kernel's page size.
#if defined(ARCH_CPU_ARM64)
constexpr size_t kBinaryPageSize = 16384;
#else
constexpr size_t kBinaryPageSize = 4096;
#endif
// 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);
[[maybe_unused]] unsigned char dummy = 0;
// It's possible that using kBinaryPageSize instead of page_size (or some
// other value) is a bit arbitrary for the read stepping here. In practice,
// disk readahead is greater than either, so probably doesn't matter too much.
for (unsigned char* ptr = start_ptr; ptr < end_ptr; ptr += kBinaryPageSize) {
// Volatile is required to prevent the compiler from eliminating this
// loop.
dummy ^= *static_cast<volatile unsigned char*>(ptr);
}
}
// Call madvise from |start| to |end| in chunks of |target_length| (rounded up
// to a whole page). |target_length| can be 0 to madvise the entire range in a
// single call. |start| must already be aligned to a page.
int MadviseRange(size_t start, size_t end, int advice, size_t target_length) {
if (target_length == 0) {
target_length = end - start;
}
target_length = bits::AlignUp(target_length, base::GetPageSize());
for (size_t current = start; current < end; current += target_length) {
// madvise will round `end - current` up to a page size if required.
size_t length = std::min(target_length, end - current);
if (madvise(reinterpret_cast<void*>(current), length, advice) != 0) {
return errno;
}
}
return 0;
}
struct Section {
static Section CreateAligned(std::string_view name,
size_t start_anchor,
size_t end_anchor) {
return Section{
.name = name,
.start = start_anchor - start_anchor % kBinaryPageSize,
.end = bits::AlignUp(end_anchor, kBinaryPageSize),
};
}
std::string_view name;
size_t start;
size_t end;
};
// These values were used in the past for recording
// "LibraryLoader.PrefetchDetailedStatus".
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused. See PrefetchStatus in enums.xml.
enum class PrefetchStatus {
kSuccess = 0,
kWrongOrdering = 1,
kForkFailed = 2,
kChildProcessCrashed = 3,
kChildProcessKilled = 4,
kMadviseFailed = 5,
kMaxValue = kMadviseFailed,
};
PrefetchStatus PrefetchWithMadvise(base::span<const Section> sections,
int madvise_advice,
size_t madvise_length) {
TRACE_EVENT("startup", "LibraryPrefetcher::PrefetchWithMadvise");
for (const auto& section : sections) {
int result = MadviseRange(section.start, section.end, madvise_advice,
madvise_length);
if (result != 0) {
PLOG(WARNING) << "madvise failed for " << section.name << " section";
return PrefetchStatus::kMadviseFailed;
}
}
return PrefetchStatus::kSuccess;
}
PrefetchStatus PrefetchWithFork(base::span<const Section> sections) {
TRACE_EVENT("startup", "LibraryPrefetcher::PrefetchWithFork");
base::TimeTicks fork_start_time = base::TimeTicks::Now();
pid_t pid = fork();
if (pid == 0) {
// Android defines the background priority to this value since at least 2009
// (see Process.java).
constexpr int kBackgroundPriority = 10;
setpriority(PRIO_PROCESS, 0, kBackgroundPriority);
for (const auto& section : sections) {
Prefetch(section.start, section.end);
}
// _exit() doesn't call the atexit() handlers.
_exit(EXIT_SUCCESS);
} else {
base::UmaHistogramCustomMicrosecondsTimes(
"Android.LibraryLoader.Prefetch.ForkDuration",
base::TimeTicks::Now() - fork_start_time, base::Microseconds(1),
base::Seconds(1), 50);
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;
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;
}
}
PrefetchStatus PrefetchWithForkOrMadvise() {
if (!IsOrderingSane()) {
LOG(WARNING) << "Incorrect code ordering";
return PrefetchStatus::kWrongOrdering;
}
const std::array<const Section, 2> sections{
// Fetch the ordered section first.
Section::CreateAligned("ordered", kStartOfOrderedText, kEndOfOrderedText),
Section::CreateAligned("text", kStartOfText, kEndOfText),
};
if (base::FeatureList::IsEnabled(features::kLibraryPrefetcherMadvise)) {
// MADV_POPULATE_READ was an alternative considered. The differences being:
// - It can be used reliably on the entire range at once.
// - It takes longer than multiple WILLNEEDs of moderate (~64K) length.
// - It grows attributed RSS aggressively. (WILLNEED doesn't.)
// - It is not as widely supported as WILLNEED.
// - (At time of writing) it is not already allowlisted in our seccomp BPF.
constexpr int madvise_advice = MADV_WILLNEED;
// The man page for madvise(2) expressly supports using madvise(0, 0, ...)
// as a way to probe support, but it still trips -Wnonnull.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnonnull"
bool supported = madvise(nullptr, 0, madvise_advice) == 0;
#pragma clang diagnostic pop
if (!supported) {
// PLOG immediately after check so that nothing overwrites errno.
PLOG(WARNING) << "madvise not supported for library prefetch";
}
base::UmaHistogramBoolean("Android.LibraryLoader.Prefetch.Madvise",
supported);
if (supported) {
base::ThreadType old_thread_type =
base::PlatformThread::GetCurrentThreadType();
base::PlatformThread::SetCurrentThreadType(base::ThreadType::kBackground);
PrefetchStatus status =
PrefetchWithMadvise(sections, madvise_advice,
features::kLibraryPrefetcherMadviseLength.Get());
base::PlatformThread::SetCurrentThreadType(old_thread_type);
return status;
}
if (!features::kLibraryPrefetcherMadviseFallback.Get()) {
return PrefetchStatus::kMadviseFailed;
}
LOG(WARNING) << "falling back to fork-based library prefetch";
}
return PrefetchWithFork(sections);
}
#endif // !BUILDFLAG(ORDERFILE_INSTRUMENTATION)
} // namespace
// static
void NativeLibraryPrefetcher::PrefetchNativeLibrary() {
#if BUILDFLAG(ORDERFILE_INSTRUMENTATION)
// Avoid forking with orderfile instrumentation because the child process
// would create a dump as well.
return;
#else
base::TimeTicks start_time = base::TimeTicks::Now();
PrefetchStatus status = PrefetchWithForkOrMadvise();
base::UmaHistogramMediumTimes("Android.LibraryLoader.Prefetch.Duration",
base::TimeTicks::Now() - start_time);
base::UmaHistogramEnumeration("Android.LibraryLoader.Prefetch.Status",
status);
if (status != PrefetchStatus::kSuccess) {
LOG(WARNING) << "Cannot prefetch the library. status = "
<< static_cast<int>(status);
}
#endif // BUILDFLAG(ORDERFILE_INSTRUMENTATION)
}
} // namespace android
} // namespace base
#endif // BUILDFLAG(SUPPORTS_CODE_ORDERING)