diff options
Diffstat (limited to 'chromium/base/android/orderfile/orderfile_instrumentation.cc')
-rw-r--r-- | chromium/base/android/orderfile/orderfile_instrumentation.cc | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/chromium/base/android/orderfile/orderfile_instrumentation.cc b/chromium/base/android/orderfile/orderfile_instrumentation.cc new file mode 100644 index 00000000000..2b0983ff62b --- /dev/null +++ b/chromium/base/android/orderfile/orderfile_instrumentation.cc @@ -0,0 +1,361 @@ +// Copyright 2017 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#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/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/command_line.h" +#include "base/time/time.h" +#include "base/trace_event/memory_dump_manager.h" // no-presubmit-check +#include "base/trace_event/memory_dump_provider.h" // no-presubmit-check +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + +#if !BUILDFLAG(SUPPORTS_CODE_ORDERING) +#error Requires code ordering support (arm/arm64/x86/x86_64). +#endif // !BUILDFLAG(SUPPORTS_CODE_ORDERING) + +// Must be applied to all functions within this file. +#define NO_INSTRUMENT_FUNCTION __attribute__((no_instrument_function)) + +namespace base { +namespace android { +namespace orderfile { + +namespace { +// Constants used for StartDelayedDump(). +constexpr int kDelayInSeconds = 30; +constexpr int kInitialDelayInSeconds = kPhases == 1 ? kDelayInSeconds : 5; + +#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) +// This is defined in content/public/common/content_switches.h, which is not +// accessible in ::base. +constexpr const char kProcessTypeSwitch[] = "type"; +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + +// These are large overestimates, which is not an issue, as the data is +// allocated in .bss, and on linux doesn't take any actual memory when it's not +// touched. +constexpr size_t kBitfieldSize = 1 << 22; +constexpr size_t kMaxTextSizeInBytes = kBitfieldSize * (4 * 32); +constexpr size_t kMaxElements = 1 << 20; + +// Data required to log reached offsets. +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; + +// Number of unexpected addresses, that is addresses that are not within [start, +// end) bounds for the executable code. +// +// This should be exactly 0, since the start and end of .text should be known +// perfectly by the linker, but it does happen. See crbug.com/1186598. +std::atomic<int> g_unexpected_addresses; + +#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) +// Dump offsets when a memory dump is requested. Used only if +// switches::kDevtoolsInstrumentationDumping is set. +class OrderfileMemoryDumpHook : public base::trace_event::MemoryDumpProvider { + NO_INSTRUMENT_FUNCTION bool OnMemoryDump( + const base::trace_event::MemoryDumpArgs& args, + base::trace_event::ProcessMemoryDump* pmd) override { + // Disable instrumentation now to cut down on orderfile pollution. + if (!Disable()) { + return true; // A dump has already been started. + } + std::stringstream process_type_str; + Dump(base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + kProcessTypeSwitch)); + return true; // If something goes awry, a fatal error will be created + // internally. + } +}; +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + +// |RecordAddress()| adds an element to a concurrent bitset and to a concurrent +// append-only list of offsets. +// +// Ordering: +// Two consecutive calls to |RecordAddress()| from the same thread will be +// ordered in the same way in the result, as written by +// |StopAndDumpToFile()|. The result will contain exactly one instance of each +// unique offset relative to |kStartOfText| passed to |RecordAddress()|. +// +// Implementation: +// The "set" part is implemented with a bitfield, |g_offset|. The insertion +// order is recorded in |g_ordered_offsets|. +// This is not a class to make sure there isn't a static constructor, as it +// would cause issue with an instrumented static constructor calling this code. +// +// Limitations: +// - Only records offsets to addresses between |kStartOfText| and |kEndOfText|. +// - Capacity of the set is limited by |kMaxElements|. +// - Some insertions at the end of collection may be lost. + +// Records that |address| has been reached, if recording is enabled. +// To avoid infinite recursion, this *must* *never* call any instrumented +// function, unless |Disable()| is called first. +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 (UNLIKELY(address < start || address > end)) { + if (!AreAnchorsSane()) { + // Something is really wrong with the anchors, and this is likely to be + // triggered from within a static constructor, where logging is likely to + // deadlock. By crashing immediately we at least have a chance to get a + // stack trace from the system to give some clue about the nature of the + // problem. + ImmediateCrash(); + } + + // We should really crash at the first instance, but it does happen on bots, + // for a mysterious reason. Give it some leeway. Note that since we don't + // remember the caller address, if a single function is misplaced but we get + // many calls to it, then we still crash. If this is the case, add + // deduplication. + // + // Bumped to 100 temporarily as part of crbug.com/1265928 investigation. + if (g_unexpected_addresses.fetch_add(1, std::memory_order_relaxed) < 100) { + 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; + // Atomically set the corresponding bit in the array. + std::atomic<uint32_t>* element = offsets + (offset_index / 32); + // First, a racy check. This saves a CAS if the bit is already set, and + // allows the cache line to remain shared acoss CPUs in this case. + 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; + + // We were the first one to set the element, record it in the ordered + // elements list. + // Use relaxed ordering, as the value is not published, or used for + // synchronization. + 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 (UNLIKELY(insertion_index >= kMaxElements)) { + 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++) { + // |g_ordered_offsets| is initialized to 0, so a 0 in the middle of it + // indicates a case where the index was incremented, but the write is not + // visible in this thread yet. Safe to skip, also because the function at + // the start of text is never called. + auto offset = data.ordered_offsets[i].load(std::memory_order_relaxed); + if (!offset) + continue; + auto offset_str = base::StringPrintf("%" PRIuS "\n", offset); + if (file.WriteAtCurrentPos(offset_str.c_str(), + static_cast<int>(offset_str.size())) < 0) { + // If the file could be opened, but writing has failed, it's likely that + // data was partially written. Producing incomplete profiling data would + // lead to a poorly performing orderfile, but might not be otherwised + // noticed. So we crash instead. + LOG(FATAL) << "Error writing profile data"; + } + } + return true; +} + +// Stops recording, and outputs the data to |path|. +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!"; + } +} + +} // namespace + +// After a call to Disable(), any function can be called, as reentrancy into the +// instrumentation function will be mitigated. +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) { + int before = g_data_index.fetch_add(1, std::memory_order_relaxed); + if (before + 1 == kPhases) { + StopAndDumpToFile(pid, start_ns_since_epoch, ""); + return true; + } + return false; +} + +NO_INSTRUMENT_FUNCTION void StartDelayedDump() { + // Using std::thread and not using base::TimeTicks() in order to to not call + // too many base:: symbols that would pollute the reached symbol dumps. + 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(); + +#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 // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + + std::thread([pid, start_ns_since_epoch]() { + sleep(kInitialDelayInSeconds); +#if BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + SwitchToNextPhaseOrDump(pid, start_ns_since_epoch); +// Return, letting devtools tracing handle any post-startup phases. +#else + while (!SwitchToNextPhaseOrDump(pid, start_ns_since_epoch)) + sleep(kDelayInSeconds); +#endif // BUILDFLAG(DEVTOOLS_INSTRUMENTATION_DUMPING) + }) + .detach(); +} + +NO_INSTRUMENT_FUNCTION void Dump(const std::string& tag) { + // As profiling has been disabled, none of the uses of ::base symbols below + // will enter the symbol dump. + 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; +} + +} // namespace orderfile +} // namespace android +} // namespace base + +extern "C" { + +NO_INSTRUMENT_FUNCTION void __cyg_profile_func_enter_bare() { + base::android::orderfile::RecordAddress<false>( + reinterpret_cast<size_t>(__builtin_return_address(0))); +} + +} // extern "C" |