910e62b5创建于 1月15日历史提交
// 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)