#ifdef UNSAFE_BUFFERS_BUILD
#pragma allow_unsafe_buffers
#endif
#include "base/android/orderfile/orderfile_instrumentation.h"
#include <time.h>
#include <unistd.h>
#include <atomic>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
#include "base/android/library_loader/anchor_functions.h"
#include "base/android/orderfile/orderfile_buildflags.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/format_macros.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
#include <sstream>
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_provider.h"
#endif
#if !BUILDFLAG(SUPPORTS_CODE_ORDERING)
#error Requires code ordering support (arm/arm64/x86/x86_64).
#endif
#define NO_INSTRUMENT_FUNCTION __attribute__((no_instrument_function))
namespace base::android::orderfile {
namespace {
constexpr int kDelayInSeconds = 30;
constexpr int kInitialDelayInSeconds = kPhases == 1 ? kDelayInSeconds : 5;
constexpr const char kProcessTypeSwitch[] = "type";
constexpr size_t kBitfieldSize = 1 << 22;
constexpr size_t kMaxTextSizeInBytes = kBitfieldSize * (4 * 32);
constexpr size_t kMaxElements = 1 << 20;
struct LogData {
std::atomic<uint32_t> offsets[kBitfieldSize];
std::atomic<size_t> ordered_offsets[kMaxElements];
std::atomic<size_t> index;
};
LogData g_data[kPhases];
std::atomic<int> g_data_index;
std::atomic<int> g_unexpected_addresses;
#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
class OrderfileMemoryDumpHook : public base::trace_event::MemoryDumpProvider {
NO_INSTRUMENT_FUNCTION bool OnMemoryDump(
const base::trace_event::MemoryDumpArgs& args,
base::trace_event::ProcessMemoryDump* pmd) override {
if (!Disable()) {
return true;
}
std::stringstream process_type_str;
Dump(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kProcessTypeSwitch));
return true;
}
};
#endif
template <bool for_testing>
__attribute__((always_inline, no_instrument_function)) void RecordAddress(
size_t address) {
int index = g_data_index.load(std::memory_order_relaxed);
if (index >= kPhases) {
return;
}
const size_t start =
for_testing ? kStartOfTextForTesting : base::android::kStartOfText;
const size_t end =
for_testing ? kEndOfTextForTesting : base::android::kEndOfText;
if (address < start || address > end) [[unlikely]] {
if (!AreAnchorsSane()) {
ImmediateCrash();
}
if (g_unexpected_addresses.fetch_add(1, std::memory_order_relaxed) < 10) {
return;
}
Disable();
LOG(FATAL) << "Too many unexpected addresses! start = " << std::hex << start
<< " end = " << end << " address = " << address;
}
size_t offset = address - start;
static_assert(sizeof(int) == 4,
"Collection and processing code assumes that sizeof(int) == 4");
size_t offset_index = offset / 4;
auto* offsets = g_data[index].offsets;
std::atomic<uint32_t>* element = offsets + (offset_index / 32);
uint32_t value = element->load(std::memory_order_relaxed);
uint32_t mask = 1 << (offset_index % 32);
if (value & mask) {
return;
}
auto before = element->fetch_or(mask, std::memory_order_relaxed);
if (before & mask) {
return;
}
auto* ordered_offsets = g_data[index].ordered_offsets;
auto& ordered_offsets_index = g_data[index].index;
size_t insertion_index =
ordered_offsets_index.fetch_add(1, std::memory_order_relaxed);
if (insertion_index >= kMaxElements) [[unlikely]] {
Disable();
LOG(FATAL) << "Too many reached offsets";
}
ordered_offsets[insertion_index].store(offset, std::memory_order_relaxed);
}
NO_INSTRUMENT_FUNCTION bool DumpToFile(const base::FilePath& path,
const LogData& data) {
auto file =
base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
if (!file.IsValid()) {
PLOG(ERROR) << "Could not open " << path;
return false;
}
if (data.index == 0) {
LOG(ERROR) << "No entries to dump";
return false;
}
size_t count = data.index - 1;
for (size_t i = 0; i < count; i++) {
auto offset = data.ordered_offsets[i].load(std::memory_order_relaxed);
if (!offset) {
continue;
}
auto offset_str = base::StringPrintf("%" PRIuS "\n", offset);
if (!file.WriteAtCurrentPosAndCheck(base::as_byte_span(offset_str))) {
LOG(FATAL) << "Error writing profile data";
}
}
return true;
}
NO_INSTRUMENT_FUNCTION void StopAndDumpToFile(int pid,
uint64_t start_ns_since_epoch,
const std::string& tag) {
Disable();
for (int phase = 0; phase < kPhases; phase++) {
std::string tag_str;
if (!tag.empty()) {
tag_str = base::StringPrintf("%s-", tag.c_str());
}
auto path = base::StringPrintf(
"/data/local/tmp/chrome/orderfile/profile-hitmap-%s%d-%" PRIu64
".txt_%d",
tag_str.c_str(), pid, start_ns_since_epoch, phase);
if (!DumpToFile(base::FilePath(path), g_data[phase])) {
LOG(ERROR) << "Problem with dump " << phase << " (" << tag << ")";
}
}
int unexpected_addresses =
g_unexpected_addresses.load(std::memory_order_relaxed);
if (unexpected_addresses != 0) {
LOG(WARNING) << "Got " << unexpected_addresses << " unexpected addresses!";
}
}
}
NO_INSTRUMENT_FUNCTION bool Disable() {
auto old_phase = g_data_index.exchange(kPhases, std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_seq_cst);
return old_phase != kPhases;
}
NO_INSTRUMENT_FUNCTION void SanityChecks() {
CHECK_LT(base::android::kEndOfText - base::android::kStartOfText,
kMaxTextSizeInBytes);
CHECK(base::android::IsOrderingSane());
}
NO_INSTRUMENT_FUNCTION bool SwitchToNextPhaseOrDump(
int pid,
uint64_t start_ns_since_epoch,
const std::string& tag) {
int before = g_data_index.fetch_add(1, std::memory_order_relaxed);
if (before + 1 == kPhases) {
StopAndDumpToFile(pid, start_ns_since_epoch, tag);
return true;
}
return false;
}
NO_INSTRUMENT_FUNCTION void StartDelayedDump() {
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts)) {
PLOG(FATAL) << "clock_gettime.";
}
uint64_t start_ns_since_epoch =
static_cast<uint64_t>(ts.tv_sec) * 1000 * 1000 * 1000 + ts.tv_nsec;
int pid = getpid();
std::string tag = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kProcessTypeSwitch);
#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
static auto* g_orderfile_memory_dump_hook = new OrderfileMemoryDumpHook();
base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider(
g_orderfile_memory_dump_hook, "Orderfile", nullptr);
#endif
std::thread([pid, start_ns_since_epoch, tag] {
sleep(kInitialDelayInSeconds);
#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING)
SwitchToNextPhaseOrDump(pid, start_ns_since_epoch, tag);
#else
while (!SwitchToNextPhaseOrDump(pid, start_ns_since_epoch, tag))
sleep(kDelayInSeconds);
#endif
}).detach();
}
NO_INSTRUMENT_FUNCTION void Dump(const std::string& tag) {
StopAndDumpToFile(
getpid(), (base::Time::Now() - base::Time::UnixEpoch()).InNanoseconds(),
tag);
}
NO_INSTRUMENT_FUNCTION void ResetForTesting() {
Disable();
g_data_index = 0;
for (int i = 0; i < kPhases; i++) {
memset(reinterpret_cast<uint32_t*>(g_data[i].offsets), 0,
sizeof(uint32_t) * kBitfieldSize);
memset(reinterpret_cast<uint32_t*>(g_data[i].ordered_offsets), 0,
sizeof(uint32_t) * kMaxElements);
g_data[i].index.store(0);
}
g_unexpected_addresses.store(0, std::memory_order_relaxed);
}
NO_INSTRUMENT_FUNCTION void RecordAddressForTesting(size_t address) {
return RecordAddress<true>(address);
}
NO_INSTRUMENT_FUNCTION std::vector<size_t> GetOrderedOffsetsForTesting() {
std::vector<size_t> result;
size_t max_index = g_data[0].index.load(std::memory_order_relaxed);
for (size_t i = 0; i < max_index; ++i) {
auto value = g_data[0].ordered_offsets[i].load(std::memory_order_relaxed);
if (value) {
result.push_back(value);
}
}
return result;
}
}
extern "C" {
NO_INSTRUMENT_FUNCTION void __cyg_profile_func_enter_bare() {
base::android::orderfile::RecordAddress<false>(
reinterpret_cast<size_t>(__builtin_return_address(0)));
}
}