| // Copyright 2018 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/profiler/module_cache.h" |
| |
| #include <iterator> |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/strcat.h" |
| |
| namespace base { |
| |
| namespace { |
| |
| // Supports heterogeneous comparisons on modules and addresses, for use in |
| // binary searching modules sorted by range for a contained address. |
| struct ModuleAddressCompare { |
| bool operator()(const std::unique_ptr<const ModuleCache::Module>& module, |
| uintptr_t address) const { |
| return module->GetBaseAddress() + module->GetSize() <= address; |
| } |
| |
| bool operator()( |
| uintptr_t address, |
| const std::unique_ptr<const ModuleCache::Module>& module) const { |
| return address < module->GetBaseAddress(); |
| } |
| }; |
| |
| } // namespace |
| |
| std::string TransformModuleIDToSymbolServerFormat(StringPiece module_id) { |
| std::string mangled_id(module_id); |
| // Android and Linux Chrome builds use the "breakpad" format to index their |
| // build id, so we transform the build id for these platforms. All other |
| // platforms keep their symbols indexed by the original build ID. |
| #if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_LINUX) |
| // Linux ELF module IDs are 160bit integers, which we need to mangle |
| // down to 128bit integers to match the id that Breakpad outputs. |
| // Example on version '66.0.3359.170' x64: |
| // Build-ID: "7f0715c2 86f8 b16c 10e4ad349cda3b9b 56c7a773 |
| // Debug-ID "C215077F F886 6CB1 10E4AD349CDA3B9B 0" |
| |
| if (mangled_id.size() < 32) { |
| mangled_id.resize(32, '0'); |
| } |
| |
| mangled_id = base::StrCat({mangled_id.substr(6, 2), mangled_id.substr(4, 2), |
| mangled_id.substr(2, 2), mangled_id.substr(0, 2), |
| mangled_id.substr(10, 2), mangled_id.substr(8, 2), |
| mangled_id.substr(14, 2), mangled_id.substr(12, 2), |
| mangled_id.substr(16, 16), "0"}); |
| #endif |
| return mangled_id; |
| } |
| |
| ModuleCache::ModuleCache() = default; |
| |
| ModuleCache::~ModuleCache() { |
| DCHECK_EQ(auxiliary_module_provider_, nullptr); |
| } |
| |
| const ModuleCache::Module* ModuleCache::GetModuleForAddress(uintptr_t address) { |
| if (const ModuleCache::Module* module = GetExistingModuleForAddress(address)) |
| return module; |
| |
| std::unique_ptr<const Module> new_module = CreateModuleForAddress(address); |
| if (!new_module && auxiliary_module_provider_) |
| new_module = auxiliary_module_provider_->TryCreateModuleForAddress(address); |
| if (!new_module) |
| return nullptr; |
| |
| const auto result = native_modules_.insert(std::move(new_module)); |
| // TODO(https://crbug.com/1131769): Reintroduce DCHECK(result.second) after |
| // fixing the issue that is causing it to fail. |
| return result.first->get(); |
| } |
| |
| std::vector<const ModuleCache::Module*> ModuleCache::GetModules() const { |
| std::vector<const Module*> result; |
| result.reserve(native_modules_.size()); |
| for (const std::unique_ptr<const Module>& module : native_modules_) |
| result.push_back(module.get()); |
| for (const std::unique_ptr<const Module>& module : non_native_modules_) |
| result.push_back(module.get()); |
| return result; |
| } |
| |
| void ModuleCache::UpdateNonNativeModules( |
| const std::vector<const Module*>& defunct_modules, |
| std::vector<std::unique_ptr<const Module>> new_modules) { |
| // Insert the modules to remove into a set to support O(log(n)) lookup below. |
| flat_set<const Module*> defunct_modules_set(defunct_modules.begin(), |
| defunct_modules.end()); |
| |
| // Reorder the modules to be removed to the last slots in the set, then move |
| // them to the inactive modules, then erase the moved-from modules from the |
| // set. This is a variation on the standard erase-remove idiom, which is |
| // explicitly endorsed for implementing erase behavior on flat_sets. |
| // |
| // stable_partition is O(m*log(r)) where m is the number of current modules |
| // and r is the number of modules to remove. insert and erase are both O(r). |
| auto first_module_defunct_modules = ranges::stable_partition( |
| non_native_modules_, |
| [&defunct_modules_set](const std::unique_ptr<const Module>& module) { |
| return defunct_modules_set.find(module.get()) == |
| defunct_modules_set.end(); |
| }); |
| // All modules requested to be removed should have been found. |
| DCHECK_EQ( |
| static_cast<ptrdiff_t>(defunct_modules.size()), |
| std::distance(first_module_defunct_modules, non_native_modules_.end())); |
| inactive_non_native_modules_.insert( |
| inactive_non_native_modules_.end(), |
| std::make_move_iterator(first_module_defunct_modules), |
| std::make_move_iterator(non_native_modules_.end())); |
| non_native_modules_.erase(first_module_defunct_modules, |
| non_native_modules_.end()); |
| |
| // Insert the modules to be added. This operation is O((m + a) + a*log(a)) |
| // where m is the number of current modules and a is the number of modules to |
| // be added. |
| const size_t prior_non_native_modules_size = non_native_modules_.size(); |
| non_native_modules_.insert(std::make_move_iterator(new_modules.begin()), |
| std::make_move_iterator(new_modules.end())); |
| // Every module in |new_modules| should have been moved into |
| // |non_native_modules_|. This guards against use-after-frees if |new_modules| |
| // were to contain any modules equivalent to what's already in |
| // |non_native_modules_|, in which case the module would remain in |
| // |new_modules| and be deleted on return from the function. While this |
| // scenario would be a violation of the API contract, it would present a |
| // difficult-to-track-down crash scenario. |
| CHECK_EQ(prior_non_native_modules_size + new_modules.size(), |
| non_native_modules_.size()); |
| } |
| |
| void ModuleCache::AddCustomNativeModule(std::unique_ptr<const Module> module) { |
| const bool was_inserted = native_modules_.insert(std::move(module)).second; |
| // |module| should have been inserted into |native_modules_|, indicating that |
| // there was no equivalent module already present. While this scenario would |
| // be a violation of the API contract, it would present a |
| // difficult-to-track-down crash scenario. |
| CHECK(was_inserted); |
| } |
| |
| const ModuleCache::Module* ModuleCache::GetExistingModuleForAddress( |
| uintptr_t address) const { |
| const auto non_native_module_loc = non_native_modules_.find(address); |
| if (non_native_module_loc != non_native_modules_.end()) |
| return non_native_module_loc->get(); |
| |
| const auto native_module_loc = native_modules_.find(address); |
| if (native_module_loc != native_modules_.end()) |
| return native_module_loc->get(); |
| |
| return nullptr; |
| } |
| |
| void ModuleCache::RegisterAuxiliaryModuleProvider( |
| AuxiliaryModuleProvider* auxiliary_module_provider) { |
| DCHECK(!auxiliary_module_provider_); |
| auxiliary_module_provider_ = auxiliary_module_provider; |
| } |
| |
| void ModuleCache::UnregisterAuxiliaryModuleProvider( |
| AuxiliaryModuleProvider* auxiliary_module_provider) { |
| DCHECK_EQ(auxiliary_module_provider_, auxiliary_module_provider); |
| auxiliary_module_provider_ = nullptr; |
| } |
| |
| bool ModuleCache::ModuleAndAddressCompare::operator()( |
| const std::unique_ptr<const Module>& m1, |
| const std::unique_ptr<const Module>& m2) const { |
| return m1->GetBaseAddress() < m2->GetBaseAddress(); |
| } |
| |
| bool ModuleCache::ModuleAndAddressCompare::operator()( |
| const std::unique_ptr<const Module>& m1, |
| uintptr_t address) const { |
| return m1->GetBaseAddress() + m1->GetSize() <= address; |
| } |
| |
| bool ModuleCache::ModuleAndAddressCompare::operator()( |
| uintptr_t address, |
| const std::unique_ptr<const Module>& m2) const { |
| return address < m2->GetBaseAddress(); |
| } |
| |
| } // namespace base |