diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-06 12:48:11 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-10-13 09:33:43 +0000 |
commit | 7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3 (patch) | |
tree | fa14ba0ca8d2683ba2efdabd246dc9b18a1229c6 /chromium/base/profiler | |
parent | 79b4f909db1049fca459c07cca55af56a9b54fe3 (diff) | |
download | qtwebengine-chromium-7b5b123ac58f58ffde0f4f6e488bcd09aa4decd3.tar.gz |
BASELINE: Update Chromium to 84.0.4147.141
Change-Id: Ib85eb4cfa1cbe2b2b81e5022c8cad5c493969535
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/base/profiler')
39 files changed, 1245 insertions, 555 deletions
diff --git a/chromium/base/profiler/metadata_recorder.cc b/chromium/base/profiler/metadata_recorder.cc index fd171d4dd6e..a6e3cc22dde 100644 --- a/chromium/base/profiler/metadata_recorder.cc +++ b/chromium/base/profiler/metadata_recorder.cc @@ -8,6 +8,20 @@ namespace base { +const size_t MetadataRecorder::MAX_METADATA_COUNT; + +MetadataRecorder::Item::Item(uint64_t name_hash, + Optional<int64_t> key, + int64_t value) + : name_hash(name_hash), key(key), value(value) {} + +MetadataRecorder::Item::Item() : name_hash(0), value(0) {} + +MetadataRecorder::Item::Item(const Item& other) = default; + +MetadataRecorder::Item& MetadataRecorder::Item::Item::operator=( + const Item& other) = default; + MetadataRecorder::ItemInternal::ItemInternal() = default; MetadataRecorder::ItemInternal::~ItemInternal() = default; @@ -90,30 +104,22 @@ void MetadataRecorder::Remove(uint64_t name_hash, Optional<int64_t> key) { } } -MetadataRecorder::ScopedGetItems::ScopedGetItems( +MetadataRecorder::MetadataProvider::MetadataProvider( MetadataRecorder* metadata_recorder) : metadata_recorder_(metadata_recorder), - auto_lock_(&metadata_recorder->read_lock_) {} - -MetadataRecorder::ScopedGetItems::~ScopedGetItems() {} - -// This function is marked as NO_THREAD_SAFETY_ANALYSIS because the analyzer -// doesn't understand that the lock is acquired in the constructor initializer -// list and can therefore be safely released here. -size_t MetadataRecorder::ScopedGetItems::GetItems( - ProfileBuilder::MetadataItemArray* const items) NO_THREAD_SAFETY_ANALYSIS { - size_t item_count = metadata_recorder_->GetItems(items); - auto_lock_.Release(); - return item_count; -} + auto_lock_(metadata_recorder->read_lock_) {} + +MetadataRecorder::MetadataProvider::~MetadataProvider() = default; -std::unique_ptr<ProfileBuilder::MetadataProvider> -MetadataRecorder::CreateMetadataProvider() { - return std::make_unique<MetadataRecorder::ScopedGetItems>(this); +size_t MetadataRecorder::MetadataProvider::GetItems( + ItemArray* const items) const { + // Assertion is only necessary so that thread annotations recognize that + // |read_lock_| is acquired. + metadata_recorder_->read_lock_.AssertAcquired(); + return metadata_recorder_->GetItems(items); } -size_t MetadataRecorder::GetItems( - ProfileBuilder::MetadataItemArray* const items) const { +size_t MetadataRecorder::GetItems(ItemArray* const items) const { read_lock_.AssertAcquired(); // If a writer adds a new item after this load, it will be ignored. We do @@ -134,7 +140,7 @@ size_t MetadataRecorder::GetItems( // Because we wait until |is_active| is set to consider an item active and // that field is always set last, we ignore half-created items. if (item.is_active.load(std::memory_order_acquire)) { - (*items)[write_index++] = ProfileBuilder::MetadataItem{ + (*items)[write_index++] = Item{ item.name_hash, item.key, item.value.load(std::memory_order_relaxed)}; } } @@ -143,8 +149,7 @@ size_t MetadataRecorder::GetItems( } size_t MetadataRecorder::TryReclaimInactiveSlots(size_t item_slots_used) { - const size_t remaining_slots = - ProfileBuilder::MAX_METADATA_COUNT - item_slots_used; + const size_t remaining_slots = MAX_METADATA_COUNT - item_slots_used; if (inactive_item_count_ == 0 || inactive_item_count_ < remaining_slots) { // This reclaiming threshold has a few nice properties: diff --git a/chromium/base/profiler/metadata_recorder.h b/chromium/base/profiler/metadata_recorder.h index d85a3d978c4..6e120dd40b7 100644 --- a/chromium/base/profiler/metadata_recorder.h +++ b/chromium/base/profiler/metadata_recorder.h @@ -10,7 +10,6 @@ #include <utility> #include "base/optional.h" -#include "base/profiler/profile_builder.h" #include "base/synchronization/lock.h" #include "base/thread_annotations.h" @@ -54,11 +53,11 @@ namespace base { // allows readers to preallocate the data structure that we pass back // the metadata in. // -// C) We shouldn't guard writes with a lock that also guards reads. It can take -// ~30us from the time that the sampling thread requests that a thread be -// suspended and the time that it actually happens. If all metadata writes -// block their thread during that time, we're very likely to block all Chrome -// threads for an additional 30us per sample. +// C) We shouldn't guard writes with a lock that also guards reads, since the +// read lock is held from the time that the sampling thread requests that a +// thread be suspended up to the time that the thread is resumed. If all +// metadata writes block their thread during that time, we're very likely to +// block all Chrome threads. // // Ramifications: // @@ -94,8 +93,8 @@ namespace base { // // - No thread is using the recorder. // -// - A single writer is writing into the recorder without a simultaneous -// read. The write will succeed. +// - A single writer is writing into the recorder without a simultaneous read. +// The write will succeed. // // - A reader is reading from the recorder without a simultaneous write. The // read will succeed. @@ -128,6 +127,23 @@ class BASE_EXPORT MetadataRecorder { MetadataRecorder(const MetadataRecorder&) = delete; MetadataRecorder& operator=(const MetadataRecorder&) = delete; + struct BASE_EXPORT Item { + Item(uint64_t name_hash, Optional<int64_t> key, int64_t value); + Item(); + + Item(const Item& other); + Item& operator=(const Item& other); + + // The hash of the metadata name, as produced by HashMetricName(). + uint64_t name_hash; + // The key if specified when setting the item. + Optional<int64_t> key; + // The value of the metadata item. + int64_t value; + }; + static constexpr size_t MAX_METADATA_COUNT = 50; + typedef std::array<Item, MAX_METADATA_COUNT> ItemArray; + // Sets a value for a (|name_hash|, |key|) pair, overwriting any value // previously set for the pair. Nullopt keys are treated as just another key // state for the purpose of associating values. @@ -137,62 +153,49 @@ class BASE_EXPORT MetadataRecorder { // effect if such an item does not exist. void Remove(uint64_t name_hash, Optional<int64_t> key); - // Creates a MetadataProvider object for the recorder, which acquires the - // necessary exclusive read lock and provides access to the recorder's items - // via its GetItems() function. Reclaiming of inactive slots in the recorder - // can't occur while this object lives, so it should be created as soon before - // it's needed as possible. Calling GetItems() releases the lock held by the - // object and can therefore only be called once during the object's lifetime. + // An object that provides access to a MetadataRecorder's items and holds the + // necessary exclusive read lock until the object is destroyed. Reclaiming of + // inactive slots in the recorder can't occur while this object lives, so it + // should be created as soon before it's needed as possible and released as + // soon as possible. // - // This object should be created *before* suspending the target - // thread. Otherwise, that thread might be suspended while reclaiming inactive - // slots and holding the read lock, which would cause the sampling thread to - // deadlock. + // This object should be created *before* suspending the target thread and + // destroyed after resuming the target thread. Otherwise, that thread might be + // suspended while reclaiming inactive slots and holding the read lock, which + // would cause the sampling thread to deadlock. // // Example usage: // // MetadataRecorder r; - // base::ProfileBuilder::MetadataItemArray arr; + // base::MetadataRecorder::ItemArray arr; // size_t item_count; // ... // { - // auto get_items = r.CreateMetadataProvider(); - // item_count = get_items.GetItems(arr); + // MetadtaRecorder::MetadataProvider provider; + // item_count = provider.GetItems(arr); // } - std::unique_ptr<ProfileBuilder::MetadataProvider> CreateMetadataProvider(); - - private: - // An object that provides access to a MetadataRecorder's items and holds the - // necessary exclusive read lock until either GetItems() is called or the - // object is destroyed. - // - // For usage and more details, see CreateMetadataProvider(). - class SCOPED_LOCKABLE ScopedGetItems - : public ProfileBuilder::MetadataProvider { + class SCOPED_LOCKABLE BASE_EXPORT MetadataProvider { public: // Acquires an exclusive read lock on the metadata recorder which is held - // until either GetItems() is called or the object is destroyed. - ScopedGetItems(MetadataRecorder* metadata_recorder) - EXCLUSIVE_LOCK_FUNCTION(metadata_recorder->read_lock_); - ~ScopedGetItems() override UNLOCK_FUNCTION(metadata_recorder_->read_lock_); - ScopedGetItems(const ScopedGetItems&) = delete; - ScopedGetItems& operator=(const ScopedGetItems&) = delete; + // until the object is destroyed. + explicit MetadataProvider(MetadataRecorder* metadata_recorder) + EXCLUSIVE_LOCK_FUNCTION(metadata_recorder_->read_lock_); + ~MetadataProvider() UNLOCK_FUNCTION(); + MetadataProvider(const MetadataProvider&) = delete; + MetadataProvider& operator=(const MetadataProvider&) = delete; // Retrieves the first |available_slots| items in the metadata recorder and // copies them into |items|, returning the number of metadata items that // were copied. To ensure that all items can be copied, |available slots| // should be greater than or equal to |MAX_METADATA_COUNT|. - // - // This function releases the lock held by the object and can therefore only - // be called once during the object's lifetime. - size_t GetItems(ProfileBuilder::MetadataItemArray* const items) override - EXCLUSIVE_LOCKS_REQUIRED(metadata_recorder_->read_lock_); + size_t GetItems(ItemArray* const items) const; private: const MetadataRecorder* const metadata_recorder_; - base::ReleasableAutoLock auto_lock_; + base::AutoLock auto_lock_; }; + private: // TODO(charliea): Support large quantities of metadata efficiently. struct ItemInternal { ItemInternal(); @@ -228,17 +231,14 @@ class BASE_EXPORT MetadataRecorder { // after the reclamation. size_t TryReclaimInactiveSlots(size_t item_slots_used) EXCLUSIVE_LOCKS_REQUIRED(write_lock_) LOCKS_EXCLUDED(read_lock_); - // Also protected by read_lock_, but current thread annotation limitations - // prevent us from using thread annotations with locks acquired through - // Lock::Try(). Updates item_slots_used_ to reflect the new item count and - // returns the number of item slots used after the reclamation. + // Updates item_slots_used_ to reflect the new item count and returns the + // number of item slots used after the reclamation. size_t ReclaimInactiveSlots(size_t item_slots_used) - EXCLUSIVE_LOCKS_REQUIRED(write_lock_); + EXCLUSIVE_LOCKS_REQUIRED(write_lock_) + EXCLUSIVE_LOCKS_REQUIRED(read_lock_); - // Protected by read_lock_, but current thread annotation limitations - // prevent us from using thread annotations with locks acquired through - // Lock::Try(). - size_t GetItems(ProfileBuilder::MetadataItemArray* const items) const; + size_t GetItems(ItemArray* const items) const + EXCLUSIVE_LOCKS_REQUIRED(read_lock_); // Metadata items that the recorder has seen. Rather than implementing the // metadata recorder as a dense array, we implement it as a sparse array where @@ -248,7 +248,7 @@ class BASE_EXPORT MetadataRecorder { // // For the rationale behind this design (along with others considered), see // https://docs.google.com/document/d/18shLhVwuFbLl_jKZxCmOfRB98FmNHdKl0yZZZ3aEO4U/edit#. - std::array<ItemInternal, ProfileBuilder::MAX_METADATA_COUNT> items_; + std::array<ItemInternal, MAX_METADATA_COUNT> items_; // The number of item slots used in the metadata map. // @@ -267,11 +267,6 @@ class BASE_EXPORT MetadataRecorder { // A lock that guards against a reader trying to read items_ while inactive // slots are being reclaimed. - // - // Note that we can't enforce that this lock is properly acquired through - // thread annotations because thread annotations doesn't understand that - // ScopedGetItems::GetItems() can only be called between ScopedGetItems's - // constructor and destructor. base::Lock read_lock_; }; diff --git a/chromium/base/profiler/metadata_recorder_unittest.cc b/chromium/base/profiler/metadata_recorder_unittest.cc index 79abb21a0de..4aff812a044 100644 --- a/chromium/base/profiler/metadata_recorder_unittest.cc +++ b/chromium/base/profiler/metadata_recorder_unittest.cc @@ -11,21 +11,22 @@ namespace base { -bool operator==(const ProfileBuilder::MetadataItem& lhs, - const ProfileBuilder::MetadataItem& rhs) { +bool operator==(const MetadataRecorder::Item& lhs, + const MetadataRecorder::Item& rhs) { return lhs.name_hash == rhs.name_hash && lhs.value == rhs.value; } -bool operator<(const ProfileBuilder::MetadataItem& lhs, - const ProfileBuilder::MetadataItem& rhs) { +bool operator<(const MetadataRecorder::Item& lhs, + const MetadataRecorder::Item& rhs) { return lhs.name_hash < rhs.name_hash; } TEST(MetadataRecorderTest, GetItems_Empty) { MetadataRecorder recorder; - ProfileBuilder::MetadataItemArray items; + MetadataRecorder::ItemArray items; - size_t item_count = recorder.CreateMetadataProvider()->GetItems(&items); + size_t item_count = + MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); EXPECT_EQ(0u, item_count); } @@ -35,10 +36,10 @@ TEST(MetadataRecorderTest, Set_NewNameHash) { recorder.Set(10, nullopt, 20); - ProfileBuilder::MetadataItemArray items; + MetadataRecorder::ItemArray items; size_t item_count; { - item_count = recorder.CreateMetadataProvider()->GetItems(&items); + item_count = MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); ASSERT_EQ(1u, item_count); EXPECT_EQ(10u, items[0].name_hash); EXPECT_FALSE(items[0].key.has_value()); @@ -48,7 +49,7 @@ TEST(MetadataRecorderTest, Set_NewNameHash) { recorder.Set(20, nullopt, 30); { - item_count = recorder.CreateMetadataProvider()->GetItems(&items); + item_count = MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); ASSERT_EQ(2u, item_count); EXPECT_EQ(20u, items[1].name_hash); EXPECT_FALSE(items[1].key.has_value()); @@ -61,8 +62,9 @@ TEST(MetadataRecorderTest, Set_ExistingNameNash) { recorder.Set(10, nullopt, 20); recorder.Set(10, nullopt, 30); - ProfileBuilder::MetadataItemArray items; - size_t item_count = recorder.CreateMetadataProvider()->GetItems(&items); + MetadataRecorder::ItemArray items; + size_t item_count = + MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); ASSERT_EQ(1u, item_count); EXPECT_EQ(10u, items[0].name_hash); EXPECT_FALSE(items[0].key.has_value()); @@ -71,10 +73,10 @@ TEST(MetadataRecorderTest, Set_ExistingNameNash) { TEST(MetadataRecorderTest, Set_ReAddRemovedNameNash) { MetadataRecorder recorder; - ProfileBuilder::MetadataItemArray items; - std::vector<ProfileBuilder::MetadataItem> expected; + MetadataRecorder::ItemArray items; + std::vector<MetadataRecorder::Item> expected; for (size_t i = 0; i < items.size(); ++i) { - expected.push_back(ProfileBuilder::MetadataItem{i, nullopt, 0}); + expected.push_back(MetadataRecorder::Item{i, nullopt, 0}); recorder.Set(i, nullopt, 0); } @@ -85,14 +87,15 @@ TEST(MetadataRecorderTest, Set_ReAddRemovedNameNash) { recorder.Remove(3, nullopt); recorder.Set(3, nullopt, 0); - size_t item_count = recorder.CreateMetadataProvider()->GetItems(&items); + size_t item_count = + MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); EXPECT_EQ(items.size(), item_count); EXPECT_THAT(expected, ::testing::UnorderedElementsAreArray(items)); } TEST(MetadataRecorderTest, Set_AddPastMaxCount) { MetadataRecorder recorder; - ProfileBuilder::MetadataItemArray items; + MetadataRecorder::ItemArray items; for (size_t i = 0; i < items.size(); ++i) { recorder.Set(i, nullopt, 0); } @@ -106,10 +109,10 @@ TEST(MetadataRecorderTest, Set_NulloptKeyIsIndependentOfNonNulloptKey) { recorder.Set(10, 100, 20); - ProfileBuilder::MetadataItemArray items; + MetadataRecorder::ItemArray items; size_t item_count; { - item_count = recorder.CreateMetadataProvider()->GetItems(&items); + item_count = MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); ASSERT_EQ(1u, item_count); EXPECT_EQ(10u, items[0].name_hash); ASSERT_TRUE(items[0].key.has_value()); @@ -120,7 +123,7 @@ TEST(MetadataRecorderTest, Set_NulloptKeyIsIndependentOfNonNulloptKey) { recorder.Set(10, nullopt, 30); { - item_count = recorder.CreateMetadataProvider()->GetItems(&items); + item_count = MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); ASSERT_EQ(2u, item_count); EXPECT_EQ(10u, items[0].name_hash); @@ -141,8 +144,9 @@ TEST(MetadataRecorderTest, Remove) { recorder.Set(50, nullopt, 60); recorder.Remove(30, nullopt); - ProfileBuilder::MetadataItemArray items; - size_t item_count = recorder.CreateMetadataProvider()->GetItems(&items); + MetadataRecorder::ItemArray items; + size_t item_count = + MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); ASSERT_EQ(2u, item_count); EXPECT_EQ(10u, items[0].name_hash); EXPECT_FALSE(items[0].key.has_value()); @@ -157,8 +161,9 @@ TEST(MetadataRecorderTest, Remove_DoesntExist) { recorder.Set(10, nullopt, 20); recorder.Remove(20, nullopt); - ProfileBuilder::MetadataItemArray items; - size_t item_count = recorder.CreateMetadataProvider()->GetItems(&items); + MetadataRecorder::ItemArray items; + size_t item_count = + MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); ASSERT_EQ(1u, item_count); EXPECT_EQ(10u, items[0].name_hash); EXPECT_FALSE(items[0].key.has_value()); @@ -173,8 +178,9 @@ TEST(MetadataRecorderTest, Remove_NulloptKeyIsIndependentOfNonNulloptKey) { recorder.Remove(10, nullopt); - ProfileBuilder::MetadataItemArray items; - size_t item_count = recorder.CreateMetadataProvider()->GetItems(&items); + MetadataRecorder::ItemArray items; + size_t item_count = + MetadataRecorder::MetadataProvider(&recorder).GetItems(&items); ASSERT_EQ(1u, item_count); EXPECT_EQ(10u, items[0].name_hash); ASSERT_TRUE(items[0].key.has_value()); @@ -185,34 +191,34 @@ TEST(MetadataRecorderTest, Remove_NulloptKeyIsIndependentOfNonNulloptKey) { TEST(MetadataRecorderTest, ReclaimInactiveSlots) { MetadataRecorder recorder; - std::set<ProfileBuilder::MetadataItem> items_set; + std::set<MetadataRecorder::Item> items_set; // Fill up the metadata map. - for (size_t i = 0; i < ProfileBuilder::MAX_METADATA_COUNT; ++i) { + for (size_t i = 0; i < MetadataRecorder::MAX_METADATA_COUNT; ++i) { recorder.Set(i, nullopt, i); - items_set.insert(ProfileBuilder::MetadataItem{i, nullopt, i}); + items_set.insert(MetadataRecorder::Item{i, nullopt, i}); } // Remove every fourth entry to fragment the data. size_t entries_removed = 0; - for (size_t i = 3; i < ProfileBuilder::MAX_METADATA_COUNT; i += 4) { + for (size_t i = 3; i < MetadataRecorder::MAX_METADATA_COUNT; i += 4) { recorder.Remove(i, nullopt); ++entries_removed; - items_set.erase(ProfileBuilder::MetadataItem{i, nullopt, i}); + items_set.erase(MetadataRecorder::Item{i, nullopt, i}); } // Ensure that the inactive slots are reclaimed to make room for more entries. for (size_t i = 1; i <= entries_removed; ++i) { recorder.Set(i * 100, nullopt, i * 100); - items_set.insert(ProfileBuilder::MetadataItem{i * 100, nullopt, i * 100}); + items_set.insert(MetadataRecorder::Item{i * 100, nullopt, i * 100}); } - ProfileBuilder::MetadataItemArray items_arr; + MetadataRecorder::ItemArray items_arr; std::copy(items_set.begin(), items_set.end(), items_arr.begin()); - ProfileBuilder::MetadataItemArray recorder_items; + MetadataRecorder::ItemArray recorder_items; size_t recorder_item_count = - recorder.CreateMetadataProvider()->GetItems(&recorder_items); - EXPECT_EQ(recorder_item_count, ProfileBuilder::MAX_METADATA_COUNT); + MetadataRecorder::MetadataProvider(&recorder).GetItems(&recorder_items); + EXPECT_EQ(recorder_item_count, MetadataRecorder::MAX_METADATA_COUNT); EXPECT_THAT(recorder_items, ::testing::UnorderedElementsAreArray(items_arr)); } @@ -220,7 +226,7 @@ TEST(MetadataRecorderTest, MetadataSlotsUsedUmaHistogram) { MetadataRecorder recorder; HistogramTester histogram_tester; - for (size_t i = 0; i < ProfileBuilder::MAX_METADATA_COUNT; ++i) { + for (size_t i = 0; i < MetadataRecorder::MAX_METADATA_COUNT; ++i) { recorder.Set(i * 10, nullopt, i * 100); } diff --git a/chromium/base/profiler/module_cache_mac.cc b/chromium/base/profiler/module_cache_mac.cc index 2d895b7baef..30568aa2f8d 100644 --- a/chromium/base/profiler/module_cache_mac.cc +++ b/chromium/base/profiler/module_cache_mac.cc @@ -6,69 +6,108 @@ #include <dlfcn.h> #include <mach-o/getsect.h> +#include <string.h> #include <uuid/uuid.h> #include "base/strings/string_number_conversions.h" +#include "build/build_config.h" namespace base { namespace { -// Returns the unique build ID for a module loaded at |module_addr|. Returns the -// empty string if the function fails to get the build ID. +#if defined(ARCH_CPU_64_BITS) +using MachHeaderType = mach_header_64; +using SegmentCommandType = segment_command_64; +constexpr uint32_t kMachHeaderMagic = MH_MAGIC_64; +constexpr uint32_t kSegmentCommand = LC_SEGMENT_64; +#else +using MachHeaderType = mach_header; +using SegmentCommandType = segment_command; +constexpr uint32_t kMachHeaderMagic = MH_MAGIC; +constexpr uint32_t kSegmentCommand = LC_SEGMENT; +#endif + +// Returns the unique build ID and text segment size for a module loaded at +// |module_addr|. Returns the empty string and 0 if the function fails to get +// the build ID or size. // // Build IDs are created by the concatenation of the module's GUID (Windows) / // UUID (Mac) and an "age" field that indicates how many times that GUID/UUID // has been reused. In Windows binaries, the "age" field is present in the // module header, but on the Mac, UUIDs are never reused and so the "age" value // appended to the UUID is always 0. -std::string GetUniqueId(const void* module_addr) { - const mach_header_64* mach_header = - reinterpret_cast<const mach_header_64*>(module_addr); - DCHECK_EQ(MH_MAGIC_64, mach_header->magic); - - size_t offset = sizeof(mach_header_64); - size_t offset_limit = sizeof(mach_header_64) + mach_header->sizeofcmds; +void GetUniqueIdAndTextSize(const void* module_addr, + std::string* unique_id, + size_t* text_size) { + const MachHeaderType* mach_header = + reinterpret_cast<const MachHeaderType*>(module_addr); + DCHECK_EQ(mach_header->magic, kMachHeaderMagic); + + size_t offset = sizeof(MachHeaderType); + size_t offset_limit = sizeof(MachHeaderType) + mach_header->sizeofcmds; + bool found_uuid = false; + bool found_text_size = false; for (uint32_t i = 0; i < mach_header->ncmds; ++i) { - if (offset + sizeof(load_command) >= offset_limit) - return std::string(); + if (offset + sizeof(load_command) >= offset_limit) { + unique_id->clear(); + *text_size = 0; + return; + } - const load_command* current_cmd = reinterpret_cast<const load_command*>( + const load_command* load_cmd = reinterpret_cast<const load_command*>( reinterpret_cast<const uint8_t*>(mach_header) + offset); - if (offset + current_cmd->cmdsize > offset_limit) { + if (offset + load_cmd->cmdsize > offset_limit) { // This command runs off the end of the command list. This is malformed. - return std::string(); + unique_id->clear(); + *text_size = 0; + return; } - if (current_cmd->cmd == LC_UUID) { - if (current_cmd->cmdsize < sizeof(uuid_command)) { + if (load_cmd->cmd == LC_UUID) { + if (load_cmd->cmdsize < sizeof(uuid_command)) { // This "UUID command" is too small. This is malformed. - return std::string(); + unique_id->clear(); + } else { + const uuid_command* uuid_cmd = + reinterpret_cast<const uuid_command*>(load_cmd); + static_assert(sizeof(uuid_cmd->uuid) == sizeof(uuid_t), + "UUID field of UUID command should be 16 bytes."); + // The ID comprises the UUID concatenated with the Mac's "age" value + // which is always 0. + unique_id->assign(HexEncode(&uuid_cmd->uuid, sizeof(uuid_cmd->uuid)) + + "0"); } - - const uuid_command* uuid_cmd = - reinterpret_cast<const uuid_command*>(current_cmd); - static_assert(sizeof(uuid_cmd->uuid) == sizeof(uuid_t), - "UUID field of UUID command should be 16 bytes."); - // The ID is comprised of the UUID concatenated with the Mac's "age" value - // which is always 0. - return HexEncode(&uuid_cmd->uuid, sizeof(uuid_cmd->uuid)) + "0"; + if (found_text_size) + return; + found_uuid = true; + } else if (load_cmd->cmd == kSegmentCommand) { + const SegmentCommandType* segment_cmd = + reinterpret_cast<const SegmentCommandType*>(load_cmd); + if (strncmp(segment_cmd->segname, SEG_TEXT, + sizeof(segment_cmd->segname)) == 0) { + *text_size = segment_cmd->vmsize; + // Compare result with library function call, which is slower than this + // code. + unsigned long text_size_from_libmacho; + DCHECK(getsegmentdata(mach_header, SEG_TEXT, &text_size_from_libmacho)); + DCHECK_EQ(*text_size, text_size_from_libmacho); + } + if (found_uuid) + return; + found_text_size = true; } - offset += current_cmd->cmdsize; + offset += load_cmd->cmdsize; } - return std::string(); -} -// Returns the size of the _TEXT segment of the module loaded at |module_addr|. -size_t GetModuleTextSize(const void* module_addr) { - const mach_header_64* mach_header = - reinterpret_cast<const mach_header_64*>(module_addr); - DCHECK_EQ(MH_MAGIC_64, mach_header->magic); - unsigned long module_size; - getsegmentdata(mach_header, SEG_TEXT, &module_size); - return module_size; + if (!found_uuid) { + unique_id->clear(); + } + if (!found_text_size) { + *text_size = 0; + } } } // namespace @@ -77,9 +116,9 @@ class MacModule : public ModuleCache::Module { public: MacModule(const Dl_info& dl_info) : base_address_(reinterpret_cast<uintptr_t>(dl_info.dli_fbase)), - id_(GetUniqueId(dl_info.dli_fbase)), - debug_basename_(FilePath(dl_info.dli_fname).BaseName()), - size_(GetModuleTextSize(dl_info.dli_fbase)) {} + debug_basename_(FilePath(dl_info.dli_fname).BaseName()) { + GetUniqueIdAndTextSize(dl_info.dli_fbase, &id_, &size_); + } MacModule(const MacModule&) = delete; MacModule& operator=(const MacModule&) = delete; diff --git a/chromium/base/profiler/module_cache_stub.cc b/chromium/base/profiler/module_cache_stub.cc deleted file mode 100644 index 2c9231ad1e6..00000000000 --- a/chromium/base/profiler/module_cache_stub.cc +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// 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" - -namespace base { - -// static -std::unique_ptr<const ModuleCache::Module> ModuleCache::CreateModuleForAddress( - uintptr_t address) { - return nullptr; -} - -} // namespace base diff --git a/chromium/base/profiler/native_unwinder_android.cc b/chromium/base/profiler/native_unwinder_android.cc index d5f9cfeec4e..fa06494aee2 100644 --- a/chromium/base/profiler/native_unwinder_android.cc +++ b/chromium/base/profiler/native_unwinder_android.cc @@ -4,25 +4,226 @@ #include "base/profiler/native_unwinder_android.h" +#include <string> +#include <vector> + +#include <sys/mman.h> + +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Elf.h" +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Maps.h" +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Memory.h" +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Regs.h" + +#include "base/memory/ptr_util.h" #include "base/profiler/module_cache.h" #include "base/profiler/native_unwinder.h" #include "base/profiler/profile_builder.h" +#include "base/profiler/unwindstack_internal_android.h" +#include "build/build_config.h" + +#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/MachineArm.h" +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/RegsArm.h" +#elif defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_64_BITS) +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/MachineArm64.h" +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/RegsArm64.h" +#endif // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) namespace base { +namespace { + +class AndroidModule : public ModuleCache::Module { + public: + AndroidModule(unwindstack::MapInfo* map_info) + : start_(map_info->start), + size_(map_info->end - map_info->start), + build_id_(map_info->GetBuildID()), + name_(map_info->name) {} + ~AndroidModule() override = default; + + uintptr_t GetBaseAddress() const override { return start_; } + + std::string GetId() const override { return build_id_; } + + FilePath GetDebugBasename() const override { return FilePath(name_); } + + // Gets the size of the module. + size_t GetSize() const override { return size_; } + + // True if this is a native module. + bool IsNative() const override { return true; } + + const uintptr_t start_; + const size_t size_; + const std::string build_id_; + const std::string name_; +}; + +std::unique_ptr<unwindstack::Regs> CreateFromRegisterContext( + RegisterContext* thread_context) { +#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) + return WrapUnique<unwindstack::Regs>(unwindstack::RegsArm::Read( + reinterpret_cast<void*>(&thread_context->arm_r0))); +#elif defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_64_BITS) + return WrapUnique<unwindstack::Regs>(unwindstack::RegsArm64::Read( + reinterpret_cast<void*>(&thread_context->regs[0]))); +#else // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) + NOTREACHED(); + return nullptr; +#endif // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) +} + +void CopyToRegisterContext(unwindstack::Regs* regs, + RegisterContext* thread_context) { +#if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) + memcpy(reinterpret_cast<void*>(&thread_context->arm_r0), regs->RawData(), + unwindstack::ARM_REG_LAST * sizeof(uint32_t)); +#elif defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_64_BITS) + memcpy(reinterpret_cast<void*>(&thread_context->regs[0]), regs->RawData(), + unwindstack::ARM64_REG_LAST * sizeof(uint32_t)); +#else // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) + NOTREACHED(); +#endif // #if defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_32_BITS) +} + +} // namespace + +// static +std::unique_ptr<unwindstack::Maps> NativeUnwinderAndroid::CreateMaps() { + auto maps = std::make_unique<unwindstack::LocalMaps>(); + if (maps->Parse()) + return maps; + return nullptr; +} + +// static +std::unique_ptr<unwindstack::Memory> +NativeUnwinderAndroid::CreateProcessMemory() { + return std::make_unique<unwindstack::MemoryLocal>(); +} + +void NativeUnwinderAndroid::AddInitialModulesFromMaps( + const unwindstack::Maps& memory_regions_map, + ModuleCache* module_cache) { + for (const auto& region : memory_regions_map) { + // Only add executable regions. + if (!(region->flags & PROT_EXEC)) + continue; + module_cache->AddCustomNativeModule( + std::make_unique<AndroidModule>(region.get())); + } +} + +NativeUnwinderAndroid::NativeUnwinderAndroid( + unwindstack::Maps* memory_regions_map, + unwindstack::Memory* process_memory, + uintptr_t exclude_module_with_base_address) + : memory_regions_map_(memory_regions_map), + process_memory_(process_memory), + exclude_module_with_base_address_(exclude_module_with_base_address) {} + +NativeUnwinderAndroid::~NativeUnwinderAndroid() = default; + +void NativeUnwinderAndroid::AddInitialModules(ModuleCache* module_cache) { + AddInitialModulesFromMaps(*memory_regions_map_, module_cache); +} bool NativeUnwinderAndroid::CanUnwindFrom(const Frame& current_frame) const { - return false; + return current_frame.module && current_frame.module->IsNative() && + current_frame.module->GetBaseAddress() != + exclude_module_with_base_address_; } UnwindResult NativeUnwinderAndroid::TryUnwind(RegisterContext* thread_context, uintptr_t stack_top, ModuleCache* module_cache, std::vector<Frame>* stack) const { - return UnwindResult::ABORTED; + auto regs = CreateFromRegisterContext(thread_context); + DCHECK(regs); + unwindstack::ArchEnum arch = regs->Arch(); + + do { + uint64_t cur_pc = regs->pc(); + uint64_t cur_sp = regs->sp(); + unwindstack::MapInfo* map_info = memory_regions_map_->Find(cur_pc); + if (map_info == nullptr || + map_info->flags & unwindstack::MAPS_FLAGS_DEVICE_MAP) { + break; + } + + unwindstack::Elf* elf = + map_info->GetElf({process_memory_, [](unwindstack::Memory*) {}}, arch); + if (!elf->valid()) + break; + + UnwindStackMemoryAndroid stack_memory(cur_sp, stack_top); + uintptr_t rel_pc = elf->GetRelPc(cur_pc, map_info); + bool finished = false; + bool stepped = + elf->Step(rel_pc, rel_pc, regs.get(), &stack_memory, &finished); + if (stepped && finished) + return UnwindResult::COMPLETED; + + if (!stepped) { + // Stepping failed. Try unwinding using return address. + if (stack->size() == 1) { + if (!regs->SetPcFromReturnAddress(&stack_memory)) + return UnwindResult::ABORTED; + } else { + break; + } + } + + // If the pc and sp didn't change, then consider everything stopped. + if (cur_pc == regs->pc() && cur_sp == regs->sp()) + return UnwindResult::ABORTED; + + // Exclusive range of expected stack pointer values after the unwind. + struct { + uintptr_t start; + uintptr_t end; + } expected_stack_pointer_range = {cur_sp, stack_top}; + if (regs->sp() < expected_stack_pointer_range.start || + regs->sp() >= expected_stack_pointer_range.end) { + return UnwindResult::ABORTED; + } + + if (regs->dex_pc() != 0) { + // Add a frame to represent the dex file. + EmitDexFrame(regs->dex_pc(), module_cache, stack); + + // Clear the dex pc so that we don't repeat this frame later. + regs->set_dex_pc(0); + } + + // Add the frame to |stack|. + const ModuleCache::Module* module = + module_cache->GetModuleForAddress(regs->pc()); + stack->emplace_back(regs->pc(), module); + } while (CanUnwindFrom(stack->back())); + + // Restore registers necessary for further unwinding in |thread_context|. + CopyToRegisterContext(regs.get(), thread_context); + return UnwindResult::UNRECOGNIZED_FRAME; } -std::unique_ptr<Unwinder> CreateNativeUnwinder(ModuleCache* module_cache) { - return std::make_unique<NativeUnwinderAndroid>(); +void NativeUnwinderAndroid::EmitDexFrame(uintptr_t dex_pc, + ModuleCache* module_cache, + std::vector<Frame>* stack) const { + const ModuleCache::Module* module = module_cache->GetModuleForAddress(dex_pc); + if (!module) { + // The region containing |dex_pc| may not be in |module_cache| since it's + // usually not executable (.dex file). Since non-executable regions + // are used much less commonly, it's lazily added here instead of from + // AddInitialModules(). + unwindstack::MapInfo* map_info = memory_regions_map_->Find(dex_pc); + if (map_info) { + auto new_module = std::make_unique<AndroidModule>(map_info); + module = new_module.get(); + module_cache->AddCustomNativeModule(std::move(new_module)); + } + } + stack->emplace_back(dex_pc, module); } } // namespace base diff --git a/chromium/base/profiler/native_unwinder_android.h b/chromium/base/profiler/native_unwinder_android.h index 16f1b7b39aa..926a581b32a 100644 --- a/chromium/base/profiler/native_unwinder_android.h +++ b/chromium/base/profiler/native_unwinder_android.h @@ -7,25 +7,54 @@ #include "base/profiler/unwinder.h" +namespace unwindstack { +class Maps; +class Memory; +} // namespace unwindstack + namespace base { // Native unwinder implementation for Android, using libunwindstack. -// -// TODO(charliea): Implement this class. -// See: https://crbug.com/989102 class NativeUnwinderAndroid : public Unwinder { public: - NativeUnwinderAndroid() = default; + // Creates maps object from /proc/self/maps for use by NativeUnwinderAndroid. + // Since this is an expensive call, the maps object should be re-used across + // all profiles in a process. + static std::unique_ptr<unwindstack::Maps> CreateMaps(); + static std::unique_ptr<unwindstack::Memory> CreateProcessMemory(); + // Adds modules found from executable loaded memory regions to |module_cache|. + static void AddInitialModulesFromMaps( + const unwindstack::Maps& memory_regions_map, + ModuleCache* module_cache); + + // |exclude_module_with_base_address| is used to exclude a specific module + // and let another unwinder take control. TryUnwind() will exit with + // UNRECOGNIZED_FRAME and CanUnwindFrom() will return false when a frame is + // encountered in that module. + NativeUnwinderAndroid(unwindstack::Maps* memory_regions_map, + unwindstack::Memory* process_memory, + uintptr_t exclude_module_with_base_address = 0); + ~NativeUnwinderAndroid() override; NativeUnwinderAndroid(const NativeUnwinderAndroid&) = delete; NativeUnwinderAndroid& operator=(const NativeUnwinderAndroid&) = delete; // Unwinder + void AddInitialModules(ModuleCache* module_cache) override; bool CanUnwindFrom(const Frame& current_frame) const override; UnwindResult TryUnwind(RegisterContext* thread_context, uintptr_t stack_top, ModuleCache* module_cache, std::vector<Frame>* stack) const override; + + private: + void EmitDexFrame(uintptr_t dex_pc, + ModuleCache* module_cache, + std::vector<Frame>* stack) const; + + unwindstack::Maps* const memory_regions_map_; + unwindstack::Memory* const process_memory_; + const uintptr_t exclude_module_with_base_address_; }; } // namespace base diff --git a/chromium/base/profiler/native_unwinder_android_unittest.cc b/chromium/base/profiler/native_unwinder_android_unittest.cc new file mode 100644 index 00000000000..236ab3ae6fc --- /dev/null +++ b/chromium/base/profiler/native_unwinder_android_unittest.cc @@ -0,0 +1,365 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/profiler/native_unwinder_android.h" + +#include <string.h> + +#include "base/android/build_info.h" +#include "base/android/jni_android.h" +#include "base/base_profiler_test_support_jni_headers/TestSupport_jni.h" +#include "base/bind.h" +#include "base/profiler/register_context.h" +#include "base/profiler/stack_buffer.h" +#include "base/profiler/stack_copier_signal.h" +#include "base/profiler/stack_sampling_profiler_test_util.h" +#include "base/profiler/thread_delegate_posix.h" +#include "base/profiler/unwindstack_internal_android.h" +#include "base/test/bind_test_util.h" +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +extern char __executable_start; + +namespace base { + +class TestStackCopierDelegate : public StackCopier::Delegate { + public: + void OnStackCopy() override {} +}; + +std::vector<Frame> CaptureScenario( + UnwindScenario* scenario, + ModuleCache* module_cache, + OnceCallback<void(RegisterContext*, uintptr_t, std::vector<Frame>*)> + unwind_callback) { + std::vector<Frame> sample; + WithTargetThread( + scenario, + BindLambdaForTesting( + [&](SamplingProfilerThreadToken target_thread_token) { + auto stack_copier = std::make_unique<StackCopierSignal>( + std::make_unique<ThreadDelegatePosix>(target_thread_token)); + std::unique_ptr<StackBuffer> stack_buffer = + StackSampler::CreateStackBuffer(); + + RegisterContext thread_context; + uintptr_t stack_top; + TimeTicks timestamp; + TestStackCopierDelegate delegate; + bool success = + stack_copier->CopyStack(stack_buffer.get(), &stack_top, + ×tamp, &thread_context, &delegate); + ASSERT_TRUE(success); + + sample.emplace_back( + RegisterContextInstructionPointer(&thread_context), + module_cache->GetModuleForAddress( + RegisterContextInstructionPointer(&thread_context))); + + std::move(unwind_callback).Run(&thread_context, stack_top, &sample); + })); + + return sample; +} + +// Checks that the expected information is present in sampled frames. +TEST(NativeUnwinderAndroidTest, PlainFunction) { + UnwindScenario scenario(BindRepeating(&CallWithPlainFunction)); + + std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); + std::unique_ptr<unwindstack::Memory> memory = + NativeUnwinderAndroid::CreateProcessMemory(); + auto unwinder = + std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); + + ModuleCache module_cache; + unwinder->AddInitialModules(&module_cache); + std::vector<Frame> sample = + CaptureScenario(&scenario, &module_cache, + BindLambdaForTesting([&](RegisterContext* thread_context, + uintptr_t stack_top, + std::vector<Frame>* sample) { + ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); + UnwindResult result = unwinder->TryUnwind( + thread_context, stack_top, &module_cache, sample); + EXPECT_EQ(UnwindResult::COMPLETED, result); + })); + + // Check that all the modules are valid. + for (const auto& frame : sample) + EXPECT_NE(nullptr, frame.module); + + // The stack should contain a full unwind. + ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), + scenario.GetSetupFunctionAddressRange(), + scenario.GetOuterFunctionAddressRange()}); +} + +// Checks that the unwinder handles stacks containing dynamically-allocated +// stack memory. +TEST(NativeUnwinderAndroidTest, Alloca) { + UnwindScenario scenario(BindRepeating(&CallWithAlloca)); + + std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); + std::unique_ptr<unwindstack::Memory> memory = + NativeUnwinderAndroid::CreateProcessMemory(); + auto unwinder = + std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); + + ModuleCache module_cache; + unwinder->AddInitialModules(&module_cache); + std::vector<Frame> sample = + CaptureScenario(&scenario, &module_cache, + BindLambdaForTesting([&](RegisterContext* thread_context, + uintptr_t stack_top, + std::vector<Frame>* sample) { + ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); + UnwindResult result = unwinder->TryUnwind( + thread_context, stack_top, &module_cache, sample); + EXPECT_EQ(UnwindResult::COMPLETED, result); + })); + + // Check that all the modules are valid. + for (const auto& frame : sample) + EXPECT_NE(nullptr, frame.module); + + // The stack should contain a full unwind. + ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), + scenario.GetSetupFunctionAddressRange(), + scenario.GetOuterFunctionAddressRange()}); +} + +// Checks that a stack that runs through another library produces a stack with +// the expected functions. +TEST(NativeUnwinderAndroidTest, OtherLibrary) { + NativeLibrary other_library = LoadOtherLibrary(); + UnwindScenario scenario( + BindRepeating(&CallThroughOtherLibrary, Unretained(other_library))); + + std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); + std::unique_ptr<unwindstack::Memory> memory = + NativeUnwinderAndroid::CreateProcessMemory(); + auto unwinder = + std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); + + ModuleCache module_cache; + unwinder->AddInitialModules(&module_cache); + std::vector<Frame> sample = + CaptureScenario(&scenario, &module_cache, + BindLambdaForTesting([&](RegisterContext* thread_context, + uintptr_t stack_top, + std::vector<Frame>* sample) { + ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); + UnwindResult result = unwinder->TryUnwind( + thread_context, stack_top, &module_cache, sample); + EXPECT_EQ(UnwindResult::COMPLETED, result); + })); + + // The stack should contain a full unwind. + ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), + scenario.GetSetupFunctionAddressRange(), + scenario.GetOuterFunctionAddressRange()}); +} + +// Check that unwinding is interrupted for excluded modules. +TEST(NativeUnwinderAndroidTest, ExcludeOtherLibrary) { + NativeLibrary other_library = LoadOtherLibrary(); + UnwindScenario scenario( + BindRepeating(&CallThroughOtherLibrary, Unretained(other_library))); + + std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); + std::unique_ptr<unwindstack::Memory> memory = + NativeUnwinderAndroid::CreateProcessMemory(); + ModuleCache module_cache; + NativeUnwinderAndroid::AddInitialModulesFromMaps(*maps, &module_cache); + + auto unwinder = std::make_unique<NativeUnwinderAndroid>( + maps.get(), memory.get(), + module_cache.GetModuleForAddress(GetAddressInOtherLibrary(other_library)) + ->GetBaseAddress()); + std::vector<Frame> sample = + CaptureScenario(&scenario, &module_cache, + BindLambdaForTesting([&](RegisterContext* thread_context, + uintptr_t stack_top, + std::vector<Frame>* sample) { + ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); + EXPECT_EQ(UnwindResult::UNRECOGNIZED_FRAME, + unwinder->TryUnwind(thread_context, stack_top, + &module_cache, sample)); + EXPECT_FALSE(unwinder->CanUnwindFrom(sample->back())); + })); + + ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange()}); + ExpectStackDoesNotContain(sample, {scenario.GetSetupFunctionAddressRange(), + scenario.GetOuterFunctionAddressRange()}); +} + +// Check that unwinding can be resumed after an incomplete unwind. +TEST(NativeUnwinderAndroidTest, ResumeUnwinding) { + NativeLibrary other_library = LoadOtherLibrary(); + UnwindScenario scenario( + BindRepeating(&CallThroughOtherLibrary, Unretained(other_library))); + + std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); + std::unique_ptr<unwindstack::Memory> memory = + NativeUnwinderAndroid::CreateProcessMemory(); + ModuleCache module_cache; + NativeUnwinderAndroid::AddInitialModulesFromMaps(*maps, &module_cache); + + // Several unwinders are used to unwind different portion of the stack. This + // tests that NativeUnwinderAndroid can pick up from a state in the middle of + // the stack. This emulates having NativeUnwinderAndroid work with other + // unwinders, but doesn't reproduce what happens in production. + auto unwinder_for_all = + std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); + auto unwinder_for_native = std::make_unique<NativeUnwinderAndroid>( + maps.get(), memory.get(), + reinterpret_cast<uintptr_t>(&__executable_start)); + auto unwinder_for_chrome = std::make_unique<NativeUnwinderAndroid>( + maps.get(), memory.get(), + module_cache.GetModuleForAddress(GetAddressInOtherLibrary(other_library)) + ->GetBaseAddress()); + + std::vector<Frame> sample = CaptureScenario( + &scenario, &module_cache, + BindLambdaForTesting([&](RegisterContext* thread_context, + uintptr_t stack_top, + std::vector<Frame>* sample) { + // |unwinder_for_native| unwinds through native frames, but stops at + // chrome frames. It might not contain SampleAddressRange. + ASSERT_TRUE(unwinder_for_native->CanUnwindFrom(sample->back())); + EXPECT_EQ(UnwindResult::UNRECOGNIZED_FRAME, + unwinder_for_native->TryUnwind(thread_context, stack_top, + &module_cache, sample)); + EXPECT_FALSE(unwinder_for_native->CanUnwindFrom(sample->back())); + + ExpectStackDoesNotContain(*sample, + {scenario.GetSetupFunctionAddressRange(), + scenario.GetOuterFunctionAddressRange()}); + size_t prior_stack_size = sample->size(); + + // |unwinder_for_chrome| unwinds through Chrome frames, but stops at + // |other_library|. It won't contain SetupFunctionAddressRange. + ASSERT_TRUE(unwinder_for_chrome->CanUnwindFrom(sample->back())); + EXPECT_EQ(UnwindResult::UNRECOGNIZED_FRAME, + unwinder_for_chrome->TryUnwind(thread_context, stack_top, + &module_cache, sample)); + EXPECT_FALSE(unwinder_for_chrome->CanUnwindFrom(sample->back())); + EXPECT_LT(prior_stack_size, sample->size()); + ExpectStackContains(*sample, {scenario.GetWaitForSampleAddressRange()}); + ExpectStackDoesNotContain(*sample, + {scenario.GetSetupFunctionAddressRange(), + scenario.GetOuterFunctionAddressRange()}); + + // |unwinder_for_all| should complete unwinding through all frames. + ASSERT_TRUE(unwinder_for_all->CanUnwindFrom(sample->back())); + EXPECT_EQ(UnwindResult::COMPLETED, + unwinder_for_all->TryUnwind(thread_context, stack_top, + &module_cache, sample)); + })); + + // The stack should contain a full unwind. + ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), + scenario.GetSetupFunctionAddressRange(), + scenario.GetOuterFunctionAddressRange()}); +} + +struct JavaTestSupportParams { + OnceClosure wait_for_sample; + FunctionAddressRange range; +}; + +void JNI_TestSupport_InvokeCallbackFunction(JNIEnv* env, jlong context) { + const void* start_program_counter = GetProgramCounter(); + + JavaTestSupportParams* params = + reinterpret_cast<JavaTestSupportParams*>(context); + if (!params->wait_for_sample.is_null()) + std::move(params->wait_for_sample).Run(); + + // Volatile to prevent a tail call to GetProgramCounter(). + const void* volatile end_program_counter = GetProgramCounter(); + + params->range = {start_program_counter, end_program_counter}; +} + +// Checks that java frames can be unwound through. +// Disabled, see: https://crbug.com/1076997 +TEST(NativeUnwinderAndroidTest, DISABLED_JavaFunction) { + auto* build_info = base::android::BuildInfo::GetInstance(); + // Due to varying availability of compiled java unwind tables, unwinding is + // only expected to succeed on > SDK_VERSION_MARSHMALLOW. + bool can_always_unwind = + build_info->sdk_int() > base::android::SDK_VERSION_MARSHMALLOW; + + UnwindScenario scenario(BindLambdaForTesting([](OnceClosure wait_for_sample) { + JNIEnv* env = base::android::AttachCurrentThread(); + JavaTestSupportParams params{std::move(wait_for_sample), {}}; + base::Java_TestSupport_callWithJavaFunction( + env, reinterpret_cast<uintptr_t>(¶ms)); + return params.range; + })); + + std::unique_ptr<unwindstack::Maps> maps = NativeUnwinderAndroid::CreateMaps(); + std::unique_ptr<unwindstack::Memory> memory = + NativeUnwinderAndroid::CreateProcessMemory(); + auto unwinder = + std::make_unique<NativeUnwinderAndroid>(maps.get(), memory.get(), 0); + + ModuleCache module_cache; + unwinder->AddInitialModules(&module_cache); + std::vector<Frame> sample = + CaptureScenario(&scenario, &module_cache, + BindLambdaForTesting([&](RegisterContext* thread_context, + uintptr_t stack_top, + std::vector<Frame>* sample) { + ASSERT_TRUE(unwinder->CanUnwindFrom(sample->back())); + UnwindResult result = unwinder->TryUnwind( + thread_context, stack_top, &module_cache, sample); + if (can_always_unwind) + EXPECT_EQ(UnwindResult::COMPLETED, result); + })); + + // Check that all the modules are valid. + for (const auto& frame : sample) + EXPECT_NE(nullptr, frame.module); + + // The stack should contain a full unwind. + if (can_always_unwind) { + ExpectStackContains(sample, {scenario.GetWaitForSampleAddressRange(), + scenario.GetSetupFunctionAddressRange(), + scenario.GetOuterFunctionAddressRange()}); + } +} + +TEST(NativeUnwinderAndroidTest, UnwindStackMemoryTest) { + std::vector<uint8_t> input = {1, 2, 3, 4, 5}; + uintptr_t begin = reinterpret_cast<uintptr_t>(input.data()); + uintptr_t end = reinterpret_cast<uintptr_t>(input.data() + input.size()); + UnwindStackMemoryAndroid memory(begin, end); + + const auto check_read_fails = [&](uintptr_t addr, size_t size) { + std::vector<uint8_t> output(size); + EXPECT_EQ(0U, memory.Read(addr, output.data(), size)); + }; + const auto check_read_succeeds = [&](uintptr_t addr, size_t size) { + std::vector<uint8_t> output(size); + EXPECT_EQ(size, memory.Read(addr, output.data(), size)); + EXPECT_EQ( + 0, memcmp(reinterpret_cast<const uint8_t*>(addr), output.data(), size)); + }; + + check_read_fails(begin - 1, 1); + check_read_fails(begin - 1, 2); + check_read_fails(end, 1); + check_read_fails(end, 2); + check_read_fails(end - 1, 2); + + check_read_succeeds(begin, 1); + check_read_succeeds(begin, 5); + check_read_succeeds(end - 1, 1); +} + +} // namespace base diff --git a/chromium/base/profiler/native_unwinder_mac.cc b/chromium/base/profiler/native_unwinder_mac.cc index 5e4da9ddefc..51717e1b5f2 100644 --- a/chromium/base/profiler/native_unwinder_mac.cc +++ b/chromium/base/profiler/native_unwinder_mac.cc @@ -9,7 +9,8 @@ #include <mach/vm_map.h> #include <sys/ptrace.h> -#include "base/logging.h" +#include "base/check_op.h" +#include "base/notreached.h" #include "base/profiler/module_cache.h" #include "base/profiler/native_unwinder.h" #include "base/profiler/profile_builder.h" diff --git a/chromium/base/profiler/native_unwinder_win.cc b/chromium/base/profiler/native_unwinder_win.cc index e056045ed67..344b0901122 100644 --- a/chromium/base/profiler/native_unwinder_win.cc +++ b/chromium/base/profiler/native_unwinder_win.cc @@ -6,6 +6,8 @@ #include <winnt.h> +#include "base/check_op.h" +#include "base/notreached.h" #include "base/profiler/native_unwinder.h" #include "base/profiler/win32_stack_frame_unwinder.h" diff --git a/chromium/base/profiler/profile_builder.cc b/chromium/base/profiler/profile_builder.cc deleted file mode 100644 index 55d8dc18914..00000000000 --- a/chromium/base/profiler/profile_builder.cc +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "base/profiler/profile_builder.h" - -namespace base { - -const size_t ProfileBuilder::MAX_METADATA_COUNT; - -ProfileBuilder::MetadataItem::MetadataItem(uint64_t name_hash, - Optional<int64_t> key, - int64_t value) - : name_hash(name_hash), key(key), value(value) {} - -ProfileBuilder::MetadataItem::MetadataItem() : name_hash(0), value(0) {} - -ProfileBuilder::MetadataItem::MetadataItem(const MetadataItem& other) = default; - -ProfileBuilder::MetadataItem& ProfileBuilder::MetadataItem::MetadataItem:: -operator=(const MetadataItem& other) = default; - -} // namespace base diff --git a/chromium/base/profiler/profile_builder.h b/chromium/base/profiler/profile_builder.h index 0c82b8f8b87..0454d8c2e47 100644 --- a/chromium/base/profiler/profile_builder.h +++ b/chromium/base/profiler/profile_builder.h @@ -8,8 +8,10 @@ #include <memory> #include "base/base_export.h" +#include "base/macros.h" #include "base/optional.h" #include "base/profiler/frame.h" +#include "base/profiler/metadata_recorder.h" #include "base/profiler/module_cache.h" #include "base/time/time.h" @@ -27,39 +29,14 @@ class BASE_EXPORT ProfileBuilder { // up modules from addresses. virtual ModuleCache* GetModuleCache() = 0; - struct BASE_EXPORT MetadataItem { - MetadataItem(uint64_t name_hash, Optional<int64_t> key, int64_t value); - MetadataItem(); - - MetadataItem(const MetadataItem& other); - MetadataItem& operator=(const MetadataItem& other); - - // The hash of the metadata name, as produced by HashMetricName(). - uint64_t name_hash; - // The key if specified when setting the item. - Optional<int64_t> key; - // The value of the metadata item. - int64_t value; - }; - - static constexpr size_t MAX_METADATA_COUNT = 50; - typedef std::array<MetadataItem, MAX_METADATA_COUNT> MetadataItemArray; - - class MetadataProvider { - public: - MetadataProvider() = default; - virtual ~MetadataProvider() = default; - - virtual size_t GetItems(ProfileBuilder::MetadataItemArray* const items) = 0; - }; - // Records metadata to be associated with the current sample. To avoid // deadlock on locks taken by the suspended profiled thread, implementations // of this method must not execute any code that could take a lock, including // heap allocation or use of CHECK/DCHECK/LOG statements. Generally // implementations should simply atomically copy metadata state to be // associated with the sample. - virtual void RecordMetadata(MetadataProvider* metadata_provider) {} + virtual void RecordMetadata( + const MetadataRecorder::MetadataProvider& metadata_provider) {} // Applies the specified metadata |item| to samples collected in the range // [period_start, period_end), iff the profile already captured execution that @@ -67,9 +44,10 @@ class BASE_EXPORT ProfileBuilder { // towards samples in the middle of the period, at the expense of excluding // periods overlapping the start or end of the profile. |period_end| must be // <= TimeTicks::Now(). - virtual void ApplyMetadataRetrospectively(TimeTicks period_start, - TimeTicks period_end, - const MetadataItem& item) {} + virtual void ApplyMetadataRetrospectively( + TimeTicks period_start, + TimeTicks period_end, + const MetadataRecorder::Item& item) {} // Records a new set of frames. Invoked when sampling a sample completes. virtual void OnSampleCompleted(std::vector<Frame> frames, diff --git a/chromium/base/profiler/register_context.h b/chromium/base/profiler/register_context.h index 8ced8dae433..14380a52784 100644 --- a/chromium/base/profiler/register_context.h +++ b/chromium/base/profiler/register_context.h @@ -101,7 +101,7 @@ inline uintptr_t& RegisterContextFramePointer(mcontext_t* context) { } inline uintptr_t& RegisterContextInstructionPointer(mcontext_t* context) { - return AsUintPtr(&context->arm_ip); + return AsUintPtr(&context->arm_pc); } #elif defined(ARCH_CPU_ARM_FAMILY) && defined(ARCH_CPU_64_BITS) diff --git a/chromium/base/profiler/sample_metadata.cc b/chromium/base/profiler/sample_metadata.cc index ac246f1041a..8bb48d2c00b 100644 --- a/chromium/base/profiler/sample_metadata.cc +++ b/chromium/base/profiler/sample_metadata.cc @@ -10,36 +10,39 @@ namespace base { -ScopedSampleMetadata::ScopedSampleMetadata(StringPiece name, int64_t value) - : name_hash_(HashMetricName(name)) { +SampleMetadata::SampleMetadata(StringPiece name) + : name_hash_(HashMetricName(name)) {} + +void SampleMetadata::Set(int64_t value) { GetSampleMetadataRecorder()->Set(name_hash_, nullopt, value); } -ScopedSampleMetadata::ScopedSampleMetadata(StringPiece name, - int64_t key, - int64_t value) - : name_hash_(HashMetricName(name)), key_(key) { +void SampleMetadata::Set(int64_t key, int64_t value) { GetSampleMetadataRecorder()->Set(name_hash_, key, value); } -ScopedSampleMetadata::~ScopedSampleMetadata() { - GetSampleMetadataRecorder()->Remove(name_hash_, key_); +void SampleMetadata::Remove() { + GetSampleMetadataRecorder()->Remove(name_hash_, nullopt); } -void SetSampleMetadata(StringPiece name, int64_t value) { - GetSampleMetadataRecorder()->Set(HashMetricName(name), nullopt, value); +void SampleMetadata::Remove(int64_t key) { + GetSampleMetadataRecorder()->Remove(name_hash_, key); } -void SetSampleMetadata(StringPiece name, int64_t key, int64_t value) { - GetSampleMetadataRecorder()->Set(HashMetricName(name), key, value); +ScopedSampleMetadata::ScopedSampleMetadata(StringPiece name, int64_t value) + : name_hash_(HashMetricName(name)) { + GetSampleMetadataRecorder()->Set(name_hash_, nullopt, value); } -void RemoveSampleMetadata(StringPiece name) { - GetSampleMetadataRecorder()->Remove(HashMetricName(name), nullopt); +ScopedSampleMetadata::ScopedSampleMetadata(StringPiece name, + int64_t key, + int64_t value) + : name_hash_(HashMetricName(name)), key_(key) { + GetSampleMetadataRecorder()->Set(name_hash_, key, value); } -void RemoveSampleMetadata(StringPiece name, int64_t key) { - GetSampleMetadataRecorder()->Remove(HashMetricName(name), key); +ScopedSampleMetadata::~ScopedSampleMetadata() { + GetSampleMetadataRecorder()->Remove(name_hash_, key_); } // This function is friended by StackSamplingProfiler so must live directly in diff --git a/chromium/base/profiler/sample_metadata.h b/chromium/base/profiler/sample_metadata.h index a372fbafc75..4530e6ba18d 100644 --- a/chromium/base/profiler/sample_metadata.h +++ b/chromium/base/profiler/sample_metadata.h @@ -25,13 +25,15 @@ // For example: // // void DidStartLoad() { -// base::SetSampleMetadata("Renderer.IsLoading", 1); +// is_loading_metadata_.Set(1); // } // // void DidFinishLoad() { -// base::RemoveSampleMetadata("Renderer.IsLoading"); +// is_loading_metadata_.Remove(); // } // +// base::SampleMetadata is_loading_metadata_; +// // Alternatively, ScopedSampleMetadata can be used to ensure that the metadata // is removed correctly. // @@ -51,6 +53,49 @@ namespace base { +class BASE_EXPORT SampleMetadata { + public: + // Set the metadata value associated with |name|. + explicit SampleMetadata(StringPiece name); + + SampleMetadata(const SampleMetadata&) = default; + ~SampleMetadata() = default; + + SampleMetadata& operator=(const SampleMetadata&) = delete; + + // Set the metadata value associated with |name| in the process-global stack + // sampling profiler metadata, overwriting any previous value set for that + // |name|. + void Set(int64_t value); + + // Set the metadata value associated with the pair (|name|, |key|) in the + // process-global stack sampling profiler metadata, overwriting any previous + // value set for that (|name|, |key|) pair. This constructor allows the + // metadata to be associated with an additional user-defined key. One might + // supply a key based on the frame id, for example, to distinguish execution + // in service of scrolling between different frames. Prefer the previous + // function if no user-defined metadata is required. Note: values specified + // for a name and key are stored separately from values specified with only a + // name. + void Set(int64_t key, int64_t value); + + // Removes the metadata item with the specified name from the process-global + // stack sampling profiler metadata. + // + // If such an item doesn't exist, this has no effect. + void Remove(); + + // Removes the metadata item with the specified (|name|, |key|) pair from the + // process-global stack sampling profiler metadata. This function does not + // alter values set with the name |name| but no key. + // + // If such an item doesn't exist, this has no effect. + void Remove(int64_t key); + + private: + const uint64_t name_hash_; +}; + class BASE_EXPORT ScopedSampleMetadata { public: // Set the metadata value associated with |name|. @@ -75,36 +120,6 @@ class BASE_EXPORT ScopedSampleMetadata { Optional<int64_t> key_; }; -// Set the metadata value associated with |name| in the process-global stack -// sampling profiler metadata, overwriting any previous value set for that -// |name|. -BASE_EXPORT void SetSampleMetadata(StringPiece name, int64_t value); - -// Set the metadata value associated with the pair (|name|, |key|) in the -// process-global stack sampling profiler metadata, overwriting any previous -// value set for that (|name|, |key|) pair. This constructor allows the metadata -// to be associated with an additional user-defined key. One might supply a key -// based on the frame id, for example, to distinguish execution in service of -// scrolling between different frames. Prefer the previous function if no -// user-defined metadata is required. Note: values specified for a name and key -// are stored separately from values specified with only a name. -BASE_EXPORT void SetSampleMetadata(StringPiece name, - int64_t key, - int64_t value); - -// Removes the metadata item with the specified name from the process-global -// stack sampling profiler metadata. -// -// If such an item doesn't exist, this has no effect. -BASE_EXPORT void RemoveSampleMetadata(StringPiece name); - -// Removes the metadata item with the specified (|name|, |key|) pair from the -// process-global stack sampling profiler metadata. This function does not alter -// values set with the name |name| but no key. -// -// If such an item doesn't exist, this has no effect. -BASE_EXPORT void RemoveSampleMetadata(StringPiece name, int64_t key); - // Applies the specified metadata to samples already recorded between // |period_start| and |period_end| in all thread's active profiles, subject to // the condition that the profile fully encompasses the period and the profile diff --git a/chromium/base/profiler/sample_metadata_unittest.cc b/chromium/base/profiler/sample_metadata_unittest.cc index fbb9ddf2818..7c76ce4e435 100644 --- a/chromium/base/profiler/sample_metadata_unittest.cc +++ b/chromium/base/profiler/sample_metadata_unittest.cc @@ -10,79 +10,81 @@ namespace base { TEST(SampleMetadataTest, ScopedSampleMetadata) { - ProfileBuilder::MetadataItemArray items; - ASSERT_EQ(0u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + MetadataRecorder::ItemArray items; + ASSERT_EQ(0u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); { ScopedSampleMetadata m("myname", 100); ASSERT_EQ(1u, - GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); EXPECT_EQ(HashMetricName("myname"), items[0].name_hash); EXPECT_FALSE(items[0].key.has_value()); EXPECT_EQ(100, items[0].value); } - ASSERT_EQ(0u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + ASSERT_EQ(0u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); } TEST(SampleMetadataTest, ScopedSampleMetadataWithKey) { - ProfileBuilder::MetadataItemArray items; - ASSERT_EQ(0u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + MetadataRecorder::ItemArray items; + ASSERT_EQ(0u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); { ScopedSampleMetadata m("myname", 10, 100); ASSERT_EQ(1u, - GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); EXPECT_EQ(HashMetricName("myname"), items[0].name_hash); ASSERT_TRUE(items[0].key.has_value()); EXPECT_EQ(10, *items[0].key); EXPECT_EQ(100, items[0].value); } - ASSERT_EQ(0u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + ASSERT_EQ(0u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); } TEST(SampleMetadataTest, SampleMetadata) { - ProfileBuilder::MetadataItemArray items; - ASSERT_EQ(0u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); - - SetSampleMetadata("myname", 100); - ASSERT_EQ(1u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + MetadataRecorder::ItemArray items; + ASSERT_EQ(0u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); + + SampleMetadata metadata("myname"); + metadata.Set(100); + ASSERT_EQ(1u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); EXPECT_EQ(HashMetricName("myname"), items[0].name_hash); EXPECT_FALSE(items[0].key.has_value()); EXPECT_EQ(100, items[0].value); - RemoveSampleMetadata("myname"); - ASSERT_EQ(0u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + metadata.Remove(); + ASSERT_EQ(0u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); } TEST(SampleMetadataTest, SampleMetadataWithKey) { - ProfileBuilder::MetadataItemArray items; - ASSERT_EQ(0u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); - - SetSampleMetadata("myname", 10, 100); - ASSERT_EQ(1u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + MetadataRecorder::ItemArray items; + ASSERT_EQ(0u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); + + SampleMetadata metadata("myname"); + metadata.Set(10, 100); + ASSERT_EQ(1u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); EXPECT_EQ(HashMetricName("myname"), items[0].name_hash); ASSERT_TRUE(items[0].key.has_value()); EXPECT_EQ(10, *items[0].key); EXPECT_EQ(100, items[0].value); - RemoveSampleMetadata("myname", 10); - ASSERT_EQ(0u, GetSampleMetadataRecorder()->CreateMetadataProvider()->GetItems( - &items)); + metadata.Remove(10); + ASSERT_EQ(0u, MetadataRecorder::MetadataProvider(GetSampleMetadataRecorder()) + .GetItems(&items)); } } // namespace base diff --git a/chromium/base/profiler/stack_copier.h b/chromium/base/profiler/stack_copier.h index b1eedcb6bbf..245ba276d37 100644 --- a/chromium/base/profiler/stack_copier.h +++ b/chromium/base/profiler/stack_copier.h @@ -35,9 +35,6 @@ class BASE_EXPORT StackCopier { // deallocation, including indirectly via use of DCHECK/CHECK or other // logging statements. virtual void OnStackCopy() = 0; - - // Invoked after the stack has been copied and the target thread resumed. - virtual void OnThreadResume() = 0; }; virtual ~StackCopier(); diff --git a/chromium/base/profiler/stack_copier_signal.cc b/chromium/base/profiler/stack_copier_signal.cc index 1b601703e7c..bbef65bf520 100644 --- a/chromium/base/profiler/stack_copier_signal.cc +++ b/chromium/base/profiler/stack_copier_signal.cc @@ -229,8 +229,6 @@ bool StackCopierSignal::CopyStack(StackBuffer* stack_buffer, } } - delegate->OnThreadResume(); - const uintptr_t bottom = RegisterContextStackPointer(params.context); for (uintptr_t* reg : thread_delegate_->GetRegistersToRewrite(thread_context)) { diff --git a/chromium/base/profiler/stack_copier_signal_unittest.cc b/chromium/base/profiler/stack_copier_signal_unittest.cc index 6e2953c1764..2e321b7e9f4 100644 --- a/chromium/base/profiler/stack_copier_signal_unittest.cc +++ b/chromium/base/profiler/stack_copier_signal_unittest.cc @@ -63,25 +63,13 @@ class TargetThread : public SimpleThread { class TestStackCopierDelegate : public StackCopier::Delegate { public: void OnStackCopy() override { - // We can't EXPECT_FALSE(on_thread_resume_was_invoked_) here because that - // invocation is not reentrant. on_stack_copy_was_invoked_ = true; } - void OnThreadResume() override { - EXPECT_TRUE(on_stack_copy_was_invoked_); - on_thread_resume_was_invoked_ = true; - } - bool on_stack_copy_was_invoked() const { return on_stack_copy_was_invoked_; } - bool on_thread_resume_was_invoked() const { - return on_thread_resume_was_invoked_; - } - private: bool on_stack_copy_was_invoked_ = false; - bool on_thread_resume_was_invoked_ = false; }; } // namespace @@ -179,7 +167,6 @@ TEST(StackCopierSignalTest, MAYBE_CopyStackDelegateInvoked) { ASSERT_TRUE(result); EXPECT_TRUE(stack_copier_delegate.on_stack_copy_was_invoked()); - EXPECT_TRUE(stack_copier_delegate.on_thread_resume_was_invoked()); } // Limit to 32-bit Android, which is the platform we care about for this diff --git a/chromium/base/profiler/stack_copier_suspend.cc b/chromium/base/profiler/stack_copier_suspend.cc index d570c77cb01..7e320f776c9 100644 --- a/chromium/base/profiler/stack_copier_suspend.cc +++ b/chromium/base/profiler/stack_copier_suspend.cc @@ -64,8 +64,6 @@ bool StackCopierSuspend::CopyStack(StackBuffer* stack_buffer, StackBuffer::kPlatformStackAlignment, stack_buffer->buffer()); } - delegate->OnThreadResume(); - *stack_top = reinterpret_cast<uintptr_t>(stack_copy_bottom) + (top - bottom); for (uintptr_t* reg : diff --git a/chromium/base/profiler/stack_copier_suspend_unittest.cc b/chromium/base/profiler/stack_copier_suspend_unittest.cc index a81eca588f3..926afd3746c 100644 --- a/chromium/base/profiler/stack_copier_suspend_unittest.cc +++ b/chromium/base/profiler/stack_copier_suspend_unittest.cc @@ -85,25 +85,13 @@ class TestSuspendableThreadDelegate : public SuspendableThreadDelegate { class TestStackCopierDelegate : public StackCopier::Delegate { public: void OnStackCopy() override { - // We can't EXPECT_FALSE(on_thread_resume_was_invoked_) here because that - // invocation is not reentrant. on_stack_copy_was_invoked_ = true; } - void OnThreadResume() override { - EXPECT_TRUE(on_stack_copy_was_invoked_); - on_thread_resume_was_invoked_ = true; - } - bool on_stack_copy_was_invoked() const { return on_stack_copy_was_invoked_; } - bool on_thread_resume_was_invoked() const { - return on_thread_resume_was_invoked_; - } - private: bool on_stack_copy_was_invoked_ = false; - bool on_thread_resume_was_invoked_ = false; }; } // namespace @@ -218,7 +206,6 @@ TEST(StackCopierSuspendTest, CopyStackDelegateInvoked) { ®ister_context, &stack_copier_delegate); EXPECT_TRUE(stack_copier_delegate.on_stack_copy_was_invoked()); - EXPECT_TRUE(stack_copier_delegate.on_thread_resume_was_invoked()); } TEST(StackCopierSuspendTest, RewriteRegisters) { diff --git a/chromium/base/profiler/stack_sampler.h b/chromium/base/profiler/stack_sampler.h index 517c3a99e24..9fc91051e85 100644 --- a/chromium/base/profiler/stack_sampler.h +++ b/chromium/base/profiler/stack_sampler.h @@ -33,8 +33,8 @@ class BASE_EXPORT StackSampler { static std::unique_ptr<StackSampler> Create( SamplingProfilerThreadToken thread_token, ModuleCache* module_cache, - StackSamplerTestDelegate* test_delegate, - std::unique_ptr<Unwinder> native_unwinder = nullptr); + std::unique_ptr<Unwinder> native_unwinder, + StackSamplerTestDelegate* test_delegate); // Gets the required size of the stack buffer. static size_t GetStackBufferSize(); @@ -47,7 +47,9 @@ class BASE_EXPORT StackSampler { // thread being sampled). // Adds an auxiliary unwinder to handle additional, non-native-code unwind - // scenarios. + // scenarios. When attempting to unwind, the relative priority of auxiliary + // unwinders is the inverse of the order of insertion, and the native + // unwinder is given the lowest priority virtual void AddAuxUnwinder(std::unique_ptr<Unwinder> unwinder) = 0; // Records a set of frames and returns them. diff --git a/chromium/base/profiler/stack_sampler_android.cc b/chromium/base/profiler/stack_sampler_android.cc index 80ec117fc81..8414de9e3f4 100644 --- a/chromium/base/profiler/stack_sampler_android.cc +++ b/chromium/base/profiler/stack_sampler_android.cc @@ -6,6 +6,7 @@ #include <pthread.h> +#include "base/check.h" #include "base/profiler/stack_copier_signal.h" #include "base/profiler/stack_sampler_impl.h" #include "base/profiler/thread_delegate_posix.h" @@ -17,8 +18,9 @@ namespace base { std::unique_ptr<StackSampler> StackSampler::Create( SamplingProfilerThreadToken thread_token, ModuleCache* module_cache, - StackSamplerTestDelegate* test_delegate, - std::unique_ptr<Unwinder> native_unwinder) { + std::unique_ptr<Unwinder> native_unwinder, + StackSamplerTestDelegate* test_delegate) { + DCHECK(native_unwinder); return std::make_unique<StackSamplerImpl>( std::make_unique<StackCopierSignal>( std::make_unique<ThreadDelegatePosix>(thread_token)), diff --git a/chromium/base/profiler/stack_sampler_impl.cc b/chromium/base/profiler/stack_sampler_impl.cc index 336decd9f8b..9903b33feea 100644 --- a/chromium/base/profiler/stack_sampler_impl.cc +++ b/chromium/base/profiler/stack_sampler_impl.cc @@ -6,8 +6,10 @@ #include <utility> +#include "base/check.h" #include "base/compiler_specific.h" #include "base/logging.h" +#include "base/profiler/metadata_recorder.h" #include "base/profiler/profile_builder.h" #include "base/profiler/sample_metadata.h" #include "base/profiler/stack_buffer.h" @@ -31,16 +33,13 @@ namespace { // the thread is suspended. class StackCopierDelegate : public StackCopier::Delegate { public: - StackCopierDelegate(ModuleCache* module_cache, - Unwinder* native_unwinder, - Unwinder* aux_unwinder, - ProfileBuilder* profile_builder) - : module_cache_(module_cache), - native_unwinder_(native_unwinder), - aux_unwinder_(aux_unwinder), + StackCopierDelegate( + const base::circular_deque<std::unique_ptr<Unwinder>>* unwinders, + ProfileBuilder* profile_builder, + MetadataRecorder::MetadataProvider* metadata_provider) + : unwinders_(unwinders), profile_builder_(profile_builder), - metadata_provider_( - GetSampleMetadataRecorder()->CreateMetadataProvider()) {} + metadata_provider_(metadata_provider) {} StackCopierDelegate(const StackCopierDelegate&) = delete; StackCopierDelegate& operator=(const StackCopierDelegate&) = delete; @@ -51,35 +50,16 @@ class StackCopierDelegate : public StackCopier::Delegate { // particular, it may not perform any heap allocation or deallocation, // including indirectly via use of DCHECK/CHECK or other logging statements. void OnStackCopy() override { - native_unwinder_->OnStackCapture(); - if (aux_unwinder_) - aux_unwinder_->OnStackCapture(); - -#if !defined(OS_POSIX) || defined(OS_MACOSX) - profile_builder_->RecordMetadata(metadata_provider_.get()); -#else - // TODO(https://crbug.com/1056283): Support metadata recording on POSIX - // platforms. - ALLOW_UNUSED_LOCAL(profile_builder_); -#endif - } - - void OnThreadResume() override { - // Reset this as soon as possible because it may hold a lock on the - // metadata. - metadata_provider_.reset(); + for (const auto& unwinder : *unwinders_) + unwinder->OnStackCapture(); - native_unwinder_->UpdateModules(module_cache_); - if (aux_unwinder_) - aux_unwinder_->UpdateModules(module_cache_); + profile_builder_->RecordMetadata(*metadata_provider_); } private: - ModuleCache* const module_cache_; - Unwinder* const native_unwinder_; - Unwinder* const aux_unwinder_; + const base::circular_deque<std::unique_ptr<Unwinder>>* unwinders_; ProfileBuilder* const profile_builder_; - std::unique_ptr<ProfileBuilder::MetadataProvider> metadata_provider_; + const MetadataRecorder::MetadataProvider* const metadata_provider_; }; } // namespace @@ -89,15 +69,17 @@ StackSamplerImpl::StackSamplerImpl(std::unique_ptr<StackCopier> stack_copier, ModuleCache* module_cache, StackSamplerTestDelegate* test_delegate) : stack_copier_(std::move(stack_copier)), - native_unwinder_(std::move(native_unwinder)), module_cache_(module_cache), - test_delegate_(test_delegate) {} + test_delegate_(test_delegate) { + DCHECK(native_unwinder); + unwinders_.push_front(std::move(native_unwinder)); +} StackSamplerImpl::~StackSamplerImpl() = default; void StackSamplerImpl::AddAuxUnwinder(std::unique_ptr<Unwinder> unwinder) { - aux_unwinder_ = std::move(unwinder); - aux_unwinder_->AddInitialModules(module_cache_); + unwinder->AddInitialModules(module_cache_); + unwinders_.push_front(std::move(unwinder)); } void StackSamplerImpl::RecordStackFrames(StackBuffer* stack_buffer, @@ -107,19 +89,26 @@ void StackSamplerImpl::RecordStackFrames(StackBuffer* stack_buffer, RegisterContext thread_context; uintptr_t stack_top; TimeTicks timestamp; - StackCopierDelegate delegate(module_cache_, native_unwinder_.get(), - aux_unwinder_.get(), profile_builder); - bool success = stack_copier_->CopyStack(stack_buffer, &stack_top, ×tamp, - &thread_context, &delegate); - if (!success) - return; + { + // Make this scope as small as possible because |metadata_provider| is + // holding a lock. + MetadataRecorder::MetadataProvider metadata_provider( + GetSampleMetadataRecorder()); + StackCopierDelegate delegate(&unwinders_, profile_builder, + &metadata_provider); + bool success = stack_copier_->CopyStack( + stack_buffer, &stack_top, ×tamp, &thread_context, &delegate); + if (!success) + return; + } + for (const auto& unwinder : unwinders_) + unwinder->UpdateModules(module_cache_); if (test_delegate_) test_delegate_->OnPreStackWalk(); profile_builder->OnSampleCompleted( - WalkStack(module_cache_, &thread_context, stack_top, - native_unwinder_.get(), aux_unwinder_.get()), + WalkStack(module_cache_, &thread_context, stack_top, unwinders_), timestamp); } @@ -128,18 +117,16 @@ std::vector<Frame> StackSamplerImpl::WalkStackForTesting( ModuleCache* module_cache, RegisterContext* thread_context, uintptr_t stack_top, - Unwinder* native_unwinder, - Unwinder* aux_unwinder) { - return WalkStack(module_cache, thread_context, stack_top, native_unwinder, - aux_unwinder); + const base::circular_deque<std::unique_ptr<Unwinder>>& unwinders) { + return WalkStack(module_cache, thread_context, stack_top, unwinders); } // static -std::vector<Frame> StackSamplerImpl::WalkStack(ModuleCache* module_cache, - RegisterContext* thread_context, - uintptr_t stack_top, - Unwinder* native_unwinder, - Unwinder* aux_unwinder) { +std::vector<Frame> StackSamplerImpl::WalkStack( + ModuleCache* module_cache, + RegisterContext* thread_context, + uintptr_t stack_top, + const base::circular_deque<std::unique_ptr<Unwinder>>& unwinders) { std::vector<Frame> stack; // Reserve enough memory for most stacks, to avoid repeated // allocations. Approximately 99.9% of recorded stacks are 128 frames or @@ -154,21 +141,24 @@ std::vector<Frame> StackSamplerImpl::WalkStack(ModuleCache* module_cache, size_t prior_stack_size; UnwindResult result; do { - // Choose an authoritative unwinder for the current module. Use the aux - // unwinder if it thinks it can unwind from the current frame, otherwise use - // the native unwinder. - Unwinder* unwinder = - aux_unwinder && aux_unwinder->CanUnwindFrom(stack.back()) - ? aux_unwinder - : native_unwinder; + // Choose an authoritative unwinder for the current module. Use the first + // unwinder that thinks it can unwind from the current frame. + auto unwinder = + std::find_if(unwinders.begin(), unwinders.end(), + [&stack](const std::unique_ptr<Unwinder>& unwinder) { + return unwinder->CanUnwindFrom(stack.back()); + }); + if (unwinder == unwinders.end()) + return stack; prior_stack_size = stack.size(); - result = - unwinder->TryUnwind(thread_context, stack_top, module_cache, &stack); + result = unwinder->get()->TryUnwind(thread_context, stack_top, module_cache, + &stack); // The native unwinder should be the only one that returns COMPLETED // since the stack starts in native code. - DCHECK(result != UnwindResult::COMPLETED || unwinder == native_unwinder); + DCHECK(result != UnwindResult::COMPLETED || + unwinder->get() == unwinders.back().get()); } while (result != UnwindResult::ABORTED && result != UnwindResult::COMPLETED && // Give up if the authoritative unwinder for the module was unable to diff --git a/chromium/base/profiler/stack_sampler_impl.h b/chromium/base/profiler/stack_sampler_impl.h index 725905e0ea9..cceee652f12 100644 --- a/chromium/base/profiler/stack_sampler_impl.h +++ b/chromium/base/profiler/stack_sampler_impl.h @@ -8,6 +8,7 @@ #include <memory> #include "base/base_export.h" +#include "base/containers/circular_deque.h" #include "base/profiler/frame.h" #include "base/profiler/register_context.h" #include "base/profiler/stack_copier.h" @@ -36,22 +37,22 @@ class BASE_EXPORT StackSamplerImpl : public StackSampler { ProfileBuilder* profile_builder) override; // Exposes the internal function for unit testing. - static std::vector<Frame> WalkStackForTesting(ModuleCache* module_cache, - RegisterContext* thread_context, - uintptr_t stack_top, - Unwinder* native_unwinder, - Unwinder* aux_unwinder); + static std::vector<Frame> WalkStackForTesting( + ModuleCache* module_cache, + RegisterContext* thread_context, + uintptr_t stack_top, + const base::circular_deque<std::unique_ptr<Unwinder>>& unwinders); private: - static std::vector<Frame> WalkStack(ModuleCache* module_cache, - RegisterContext* thread_context, - uintptr_t stack_top, - Unwinder* native_unwinder, - Unwinder* aux_unwinder); + static std::vector<Frame> WalkStack( + ModuleCache* module_cache, + RegisterContext* thread_context, + uintptr_t stack_top, + const base::circular_deque<std::unique_ptr<Unwinder>>& unwinders); const std::unique_ptr<StackCopier> stack_copier_; - const std::unique_ptr<Unwinder> native_unwinder_; - std::unique_ptr<Unwinder> aux_unwinder_; + // Store all unwinder in decreasing priority order. + base::circular_deque<std::unique_ptr<Unwinder>> unwinders_; ModuleCache* const module_cache_; StackSamplerTestDelegate* const test_delegate_; }; diff --git a/chromium/base/profiler/stack_sampler_impl_unittest.cc b/chromium/base/profiler/stack_sampler_impl_unittest.cc index 30a40af2ee3..e3cd67ce59b 100644 --- a/chromium/base/profiler/stack_sampler_impl_unittest.cc +++ b/chromium/base/profiler/stack_sampler_impl_unittest.cc @@ -9,6 +9,7 @@ #include <numeric> #include <utility> +#include "base/memory/ptr_util.h" #include "base/profiler/module_cache.h" #include "base/profiler/profile_builder.h" #include "base/profiler/stack_buffer.h" @@ -37,7 +38,7 @@ class TestProfileBuilder : public ProfileBuilder { // ProfileBuilder ModuleCache* GetModuleCache() override { return module_cache_; } void RecordMetadata( - ProfileBuilder::MetadataProvider* metadata_provider) override {} + const MetadataRecorder::MetadataProvider& metadata_provider) override {} void OnSampleCompleted(std::vector<Frame> frames, TimeTicks sample_timestamp) override { @@ -97,7 +98,6 @@ class DelegateInvokingStackCopier : public StackCopier { RegisterContext* thread_context, Delegate* delegate) override { delegate->OnStackCopy(); - delegate->OnThreadResume(); return true; } }; @@ -268,6 +268,17 @@ class FakeTestUnwinder : public Unwinder { std::vector<Result> results_; }; +base::circular_deque<std::unique_ptr<Unwinder>> MakeUnwinderList( + std::unique_ptr<Unwinder> native_unwinder, + std::unique_ptr<Unwinder> aux_unwinder) { + base::circular_deque<std::unique_ptr<Unwinder>> unwinders; + if (aux_unwinder) + unwinders.push_back(std::move(aux_unwinder)); + if (native_unwinder) + unwinders.push_back(std::move(native_unwinder)); + return unwinders; +} + } // namespace // TODO(crbug.com/1001923): Fails on Linux MSan. @@ -351,10 +362,12 @@ TEST(StackSamplerImplTest, WalkStack_Completed) { RegisterContextInstructionPointer(&thread_context) = GetTestInstructionPointer(); module_cache.AddCustomNativeModule(std::make_unique<TestModule>(1u, 1u)); - FakeTestUnwinder native_unwinder({{UnwindResult::COMPLETED, {1u}}}); + auto native_unwinder = + WrapUnique(new FakeTestUnwinder({{UnwindResult::COMPLETED, {1u}}})); std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting( - &module_cache, &thread_context, 0u, &native_unwinder, nullptr); + &module_cache, &thread_context, 0u, + MakeUnwinderList(std::move(native_unwinder), nullptr)); ASSERT_EQ(2u, stack.size()); EXPECT_EQ(1u, stack[1].instruction_pointer); @@ -366,10 +379,12 @@ TEST(StackSamplerImplTest, WalkStack_Aborted) { RegisterContextInstructionPointer(&thread_context) = GetTestInstructionPointer(); module_cache.AddCustomNativeModule(std::make_unique<TestModule>(1u, 1u)); - FakeTestUnwinder native_unwinder({{UnwindResult::ABORTED, {1u}}}); + auto native_unwinder = + WrapUnique(new FakeTestUnwinder({{UnwindResult::ABORTED, {1u}}})); std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting( - &module_cache, &thread_context, 0u, &native_unwinder, nullptr); + &module_cache, &thread_context, 0u, + MakeUnwinderList(std::move(native_unwinder), nullptr)); ASSERT_EQ(2u, stack.size()); EXPECT_EQ(1u, stack[1].instruction_pointer); @@ -380,10 +395,12 @@ TEST(StackSamplerImplTest, WalkStack_NotUnwound) { RegisterContext thread_context; RegisterContextInstructionPointer(&thread_context) = GetTestInstructionPointer(); - FakeTestUnwinder native_unwinder({{UnwindResult::UNRECOGNIZED_FRAME, {}}}); + auto native_unwinder = WrapUnique( + new FakeTestUnwinder({{UnwindResult::UNRECOGNIZED_FRAME, {}}})); std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting( - &module_cache, &thread_context, 0u, &native_unwinder, nullptr); + &module_cache, &thread_context, 0u, + MakeUnwinderList(std::move(native_unwinder), nullptr)); ASSERT_EQ(1u, stack.size()); } @@ -400,10 +417,11 @@ TEST(StackSamplerImplTest, WalkStack_AuxUnwind) { {}, ToModuleVector(std::make_unique<TestModule>( GetTestInstructionPointer(), 1u, false))); - FakeTestUnwinder aux_unwinder({{UnwindResult::ABORTED, {1u}}}); - + auto aux_unwinder = + WrapUnique(new FakeTestUnwinder({{UnwindResult::ABORTED, {1u}}})); std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting( - &module_cache, &thread_context, 0u, nullptr, &aux_unwinder); + &module_cache, &thread_context, 0u, + MakeUnwinderList(nullptr, std::move(aux_unwinder))); ASSERT_EQ(2u, stack.size()); EXPECT_EQ(GetTestInstructionPointer(), stack[0].instruction_pointer); @@ -422,12 +440,14 @@ TEST(StackSamplerImplTest, WalkStack_AuxThenNative) { // Inject a fake native module for the second frame. module_cache.AddCustomNativeModule(std::make_unique<TestModule>(1u, 1u)); - FakeTestUnwinder aux_unwinder( - {{UnwindResult::UNRECOGNIZED_FRAME, {1u}}, false}); - FakeTestUnwinder native_unwinder({{UnwindResult::COMPLETED, {2u}}}); + auto aux_unwinder = WrapUnique( + new FakeTestUnwinder({{UnwindResult::UNRECOGNIZED_FRAME, {1u}}, false})); + auto native_unwinder = + WrapUnique(new FakeTestUnwinder({{UnwindResult::COMPLETED, {2u}}})); std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting( - &module_cache, &thread_context, 0u, &native_unwinder, &aux_unwinder); + &module_cache, &thread_context, 0u, + MakeUnwinderList(std::move(native_unwinder), std::move(aux_unwinder))); ASSERT_EQ(3u, stack.size()); EXPECT_EQ(0u, stack[0].instruction_pointer); @@ -449,13 +469,15 @@ TEST(StackSamplerImplTest, WalkStack_NativeThenAux) { module_cache.UpdateNonNativeModules( {}, ToModuleVector(std::make_unique<TestModule>(1u, 1u, false))); - FakeTestUnwinder aux_unwinder( - {{false}, {UnwindResult::UNRECOGNIZED_FRAME, {2u}}, {false}}); - FakeTestUnwinder native_unwinder({{UnwindResult::UNRECOGNIZED_FRAME, {1u}}, - {UnwindResult::COMPLETED, {3u}}}); + auto aux_unwinder = WrapUnique(new FakeTestUnwinder( + {{false}, {UnwindResult::UNRECOGNIZED_FRAME, {2u}}, {false}})); + auto native_unwinder = + WrapUnique(new FakeTestUnwinder({{UnwindResult::UNRECOGNIZED_FRAME, {1u}}, + {UnwindResult::COMPLETED, {3u}}})); std::vector<Frame> stack = StackSamplerImpl::WalkStackForTesting( - &module_cache, &thread_context, 0u, &native_unwinder, &aux_unwinder); + &module_cache, &thread_context, 0u, + MakeUnwinderList(std::move(native_unwinder), std::move(aux_unwinder))); ASSERT_EQ(4u, stack.size()); EXPECT_EQ(0u, stack[0].instruction_pointer); diff --git a/chromium/base/profiler/stack_sampler_ios.cc b/chromium/base/profiler/stack_sampler_ios.cc index ea2c91ede72..82ad01f3028 100644 --- a/chromium/base/profiler/stack_sampler_ios.cc +++ b/chromium/base/profiler/stack_sampler_ios.cc @@ -13,8 +13,8 @@ namespace base { std::unique_ptr<StackSampler> StackSampler::Create( SamplingProfilerThreadToken thread_token, ModuleCache* module_cache, - StackSamplerTestDelegate* test_delegate, - std::unique_ptr<Unwinder> native_unwinder) { + std::unique_ptr<Unwinder> native_unwinder, + StackSamplerTestDelegate* test_delegate) { return nullptr; } diff --git a/chromium/base/profiler/stack_sampler_mac.cc b/chromium/base/profiler/stack_sampler_mac.cc index ef46c299cae..109f6425835 100644 --- a/chromium/base/profiler/stack_sampler_mac.cc +++ b/chromium/base/profiler/stack_sampler_mac.cc @@ -4,6 +4,7 @@ #include "base/profiler/stack_sampler.h" +#include "base/check.h" #include "base/profiler/native_unwinder_mac.h" #include "base/profiler/stack_copier_suspend.h" #include "base/profiler/stack_sampler_impl.h" @@ -15,8 +16,8 @@ namespace base { std::unique_ptr<StackSampler> StackSampler::Create( SamplingProfilerThreadToken thread_token, ModuleCache* module_cache, - StackSamplerTestDelegate* test_delegate, - std::unique_ptr<Unwinder> native_unwinder) { + std::unique_ptr<Unwinder> native_unwinder, + StackSamplerTestDelegate* test_delegate) { DCHECK(!native_unwinder); return std::make_unique<StackSamplerImpl>( std::make_unique<StackCopierSuspend>( diff --git a/chromium/base/profiler/stack_sampler_posix.cc b/chromium/base/profiler/stack_sampler_posix.cc index a6728419e16..44215298c63 100644 --- a/chromium/base/profiler/stack_sampler_posix.cc +++ b/chromium/base/profiler/stack_sampler_posix.cc @@ -14,8 +14,8 @@ namespace base { std::unique_ptr<StackSampler> StackSampler::Create( SamplingProfilerThreadToken thread_token, ModuleCache* module_cache, - StackSamplerTestDelegate* test_delegate, - std::unique_ptr<Unwinder> native_unwinder) { + std::unique_ptr<Unwinder> native_unwinder, + StackSamplerTestDelegate* test_delegate) { return nullptr; } diff --git a/chromium/base/profiler/stack_sampler_win.cc b/chromium/base/profiler/stack_sampler_win.cc index 6f6ff33900c..c19009b77e4 100644 --- a/chromium/base/profiler/stack_sampler_win.cc +++ b/chromium/base/profiler/stack_sampler_win.cc @@ -4,6 +4,7 @@ #include "base/profiler/stack_sampler.h" +#include "base/check.h" #include "base/profiler/native_unwinder_win.h" #include "base/profiler/stack_copier_suspend.h" #include "base/profiler/stack_sampler_impl.h" @@ -16,8 +17,8 @@ namespace base { std::unique_ptr<StackSampler> StackSampler::Create( SamplingProfilerThreadToken thread_token, ModuleCache* module_cache, - StackSamplerTestDelegate* test_delegate, - std::unique_ptr<Unwinder> native_unwinder) { + std::unique_ptr<Unwinder> native_unwinder, + StackSamplerTestDelegate* test_delegate) { DCHECK(!native_unwinder); #if defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64) return std::make_unique<StackSamplerImpl>( diff --git a/chromium/base/profiler/stack_sampling_profiler.cc b/chromium/base/profiler/stack_sampling_profiler.cc index 62a0ceca452..46634d18aea 100644 --- a/chromium/base/profiler/stack_sampling_profiler.cc +++ b/chromium/base/profiler/stack_sampling_profiler.cc @@ -525,7 +525,7 @@ void StackSamplingProfiler::SamplingThread::ApplyMetadataToPastSamplesTask( Optional<int64_t> key, int64_t value) { DCHECK_EQ(GetThreadId(), PlatformThread::CurrentId()); - ProfileBuilder::MetadataItem item(name_hash, key, value); + MetadataRecorder::Item item(name_hash, key, value); for (auto& id_collection_pair : active_collections_) { id_collection_pair.second->profile_builder->ApplyMetadataRetrospectively( period_start, period_end, item); @@ -693,10 +693,12 @@ StackSamplingProfiler::StackSamplingProfiler( SamplingProfilerThreadToken thread_token, const SamplingParams& params, std::unique_ptr<ProfileBuilder> profile_builder, + std::unique_ptr<Unwinder> native_unwinder, StackSamplerTestDelegate* test_delegate) : StackSamplingProfiler(params, std::move(profile_builder), nullptr) { - sampler_ = StackSampler::Create( - thread_token, profile_builder_->GetModuleCache(), test_delegate); + sampler_ = + StackSampler::Create(thread_token, profile_builder_->GetModuleCache(), + std::move(native_unwinder), test_delegate); } StackSamplingProfiler::StackSamplingProfiler( diff --git a/chromium/base/profiler/stack_sampling_profiler.h b/chromium/base/profiler/stack_sampling_profiler.h index 846d2eaf27c..c6784b8a8f4 100644 --- a/chromium/base/profiler/stack_sampling_profiler.h +++ b/chromium/base/profiler/stack_sampling_profiler.h @@ -13,6 +13,7 @@ #include "base/optional.h" #include "base/profiler/profile_builder.h" #include "base/profiler/sampling_profiler_thread_token.h" +#include "base/profiler/unwinder.h" #include "base/synchronization/waitable_event.h" #include "base/threading/platform_thread.h" #include "base/time/time.h" @@ -83,14 +84,17 @@ class BASE_EXPORT StackSamplingProfiler { bool keep_consistent_sampling_interval = true; }; - // Creates a profiler for the specified thread. An optional |test_delegate| - // can be supplied by tests. + // Creates a profiler for the specified thread. |native_unwinder| is required + // on Android since the unwinder is provided outside StackSamplingProfiler, + // but must be null on other platforms. An optional |test_delegate| can be + // supplied by tests. // // The caller must ensure that this object gets destroyed before the thread // exits. StackSamplingProfiler(SamplingProfilerThreadToken thread_token, const SamplingParams& params, std::unique_ptr<ProfileBuilder> profile_builder, + std::unique_ptr<Unwinder> native_unwinder = nullptr, StackSamplerTestDelegate* test_delegate = nullptr); // Same as above function, with custom |sampler| implementation. The sampler diff --git a/chromium/base/profiler/stack_sampling_profiler_test_util.cc b/chromium/base/profiler/stack_sampling_profiler_test_util.cc index f9ad0921a39..9043d0627be 100644 --- a/chromium/base/profiler/stack_sampling_profiler_test_util.cc +++ b/chromium/base/profiler/stack_sampling_profiler_test_util.cc @@ -9,12 +9,24 @@ #include "base/callback.h" #include "base/compiler_specific.h" #include "base/location.h" +#include "base/path_service.h" +#include "base/profiler/stack_buffer.h" #include "base/profiler/stack_sampling_profiler.h" #include "base/profiler/unwinder.h" #include "base/strings/stringprintf.h" #include "base/test/bind_test_util.h" +#include "build/build_config.h" #include "testing/gtest/include/gtest/gtest.h" +#if defined(OS_WIN) +// Windows doesn't provide an alloca function like Linux does. +// Fortunately, it provides _alloca, which functions identically. +#include <malloc.h> +#define alloca _alloca +#else +#include <alloca.h> +#endif + namespace base { namespace { @@ -35,7 +47,8 @@ class TestProfileBuilder : public ProfileBuilder { // ProfileBuilder: ModuleCache* GetModuleCache() override { return module_cache_; } - void RecordMetadata(MetadataProvider* metadata_provider) override {} + void RecordMetadata( + const MetadataRecorder::MetadataProvider& metadata_provider) override {} void OnSampleCompleted(std::vector<Frame> sample, TimeTicks sample_timestamp) override { @@ -55,6 +68,17 @@ class TestProfileBuilder : public ProfileBuilder { std::vector<Frame> sample_; }; +// The function to be executed by the code in the other library. +void OtherLibraryCallback(void* arg) { + OnceClosure* wait_for_sample = static_cast<OnceClosure*>(arg); + + std::move(*wait_for_sample).Run(); + + // Prevent tail call. + volatile int i = 0; + ALLOW_UNUSED_LOCAL(i); +} + } // namespace TargetThread::TargetThread(OnceClosure to_run) : to_run_(std::move(to_run)) {} @@ -134,6 +158,47 @@ CallWithPlainFunction(OnceClosure wait_for_sample) { return {start_program_counter, end_program_counter}; } +// Disable inlining for this function so that it gets its own stack frame. +NOINLINE FunctionAddressRange CallWithAlloca(OnceClosure wait_for_sample) { + const void* start_program_counter = GetProgramCounter(); + + // Volatile to force a dynamic stack allocation. + const volatile size_t alloca_size = 100; + // Use the memory via volatile writes to prevent the allocation from being + // optimized out. + volatile char* const allocation = + const_cast<volatile char*>(static_cast<char*>(alloca(alloca_size))); + for (volatile char* p = allocation; p < allocation + alloca_size; ++p) + *p = '\0'; + + if (!wait_for_sample.is_null()) + std::move(wait_for_sample).Run(); + + // Volatile to prevent a tail call to GetProgramCounter(). + const void* volatile end_program_counter = GetProgramCounter(); + return {start_program_counter, end_program_counter}; +} + +// Disable inlining for this function so that it gets its own stack frame. +NOINLINE FunctionAddressRange +CallThroughOtherLibrary(NativeLibrary library, OnceClosure wait_for_sample) { + const void* start_program_counter = GetProgramCounter(); + + if (!wait_for_sample.is_null()) { + // A function whose arguments are a function accepting void*, and a void*. + using InvokeCallbackFunction = void (*)(void (*)(void*), void*); + EXPECT_TRUE(library); + InvokeCallbackFunction function = reinterpret_cast<InvokeCallbackFunction>( + GetFunctionPointerFromNativeLibrary(library, "InvokeCallbackFunction")); + EXPECT_TRUE(function); + (*function)(&OtherLibraryCallback, &wait_for_sample); + } + + // Volatile to prevent a tail call to GetProgramCounter(). + const void* volatile end_program_counter = GetProgramCounter(); + return {start_program_counter, end_program_counter}; +} + void WithTargetThread(UnwindScenario* scenario, ProfileCallback profile_callback) { UnwindScenario::SampleEvents events; @@ -246,4 +311,31 @@ void ExpectStackDoesNotContain( } } +NativeLibrary LoadOtherLibrary() { + // The lambda gymnastics works around the fact that we can't use ASSERT_* + // macros in a function returning non-null. + const auto load = [](NativeLibrary* library) { + FilePath other_library_path; + ASSERT_TRUE(PathService::Get(DIR_MODULE, &other_library_path)); + other_library_path = other_library_path.AppendASCII( + GetLoadableModuleName("base_profiler_test_support_library")); + NativeLibraryLoadError load_error; + *library = LoadNativeLibrary(other_library_path, &load_error); + ASSERT_TRUE(*library) << "error loading " << other_library_path.value() + << ": " << load_error.ToString(); + }; + + NativeLibrary library = nullptr; + load(&library); + return library; +} + +uintptr_t GetAddressInOtherLibrary(NativeLibrary library) { + EXPECT_TRUE(library); + uintptr_t address = reinterpret_cast<uintptr_t>( + GetFunctionPointerFromNativeLibrary(library, "InvokeCallbackFunction")); + EXPECT_NE(address, 0u); + return address; +} + } // namespace base diff --git a/chromium/base/profiler/stack_sampling_profiler_test_util.h b/chromium/base/profiler/stack_sampling_profiler_test_util.h index 2e0c2b9c9c1..ff2daebfe14 100644 --- a/chromium/base/profiler/stack_sampling_profiler_test_util.h +++ b/chromium/base/profiler/stack_sampling_profiler_test_util.h @@ -9,8 +9,10 @@ #include <vector> #include "base/callback.h" +#include "base/native_library.h" #include "base/profiler/frame.h" #include "base/profiler/sampling_profiler_thread_token.h" +#include "base/profiler/stack_sampler.h" #include "base/synchronization/waitable_event.h" #include "base/threading/platform_thread.h" @@ -91,6 +93,16 @@ class UnwindScenario { // any special unwinding setup, to exercise the "normal" unwind scenario. FunctionAddressRange CallWithPlainFunction(OnceClosure wait_for_sample); +// Calls into |wait_for_sample| after using alloca(), to test unwinding with a +// frame pointer. +FunctionAddressRange CallWithAlloca(OnceClosure wait_for_sample); + +// Calls into |wait_for_sample| through a function within another library, to +// test unwinding through multiple modules and scenarios involving unloaded +// modules. +FunctionAddressRange CallThroughOtherLibrary(NativeLibrary library, + OnceClosure wait_for_sample); + // The callback to perform profiling on the provided thread. using ProfileCallback = OnceCallback<void(SamplingProfilerThreadToken)>; @@ -122,6 +134,12 @@ void ExpectStackDoesNotContain( const std::vector<Frame>& stack, const std::vector<FunctionAddressRange>& functions); +// Loads the other library, which defines a function to be called in the +// WITH_OTHER_LIBRARY configuration. +NativeLibrary LoadOtherLibrary(); + +uintptr_t GetAddressInOtherLibrary(NativeLibrary library); + } // namespace base #endif // BASE_PROFILER_STACK_SAMPLING_PROFILER_TEST_UTIL_H_ diff --git a/chromium/base/profiler/stack_sampling_profiler_unittest.cc b/chromium/base/profiler/stack_sampling_profiler_unittest.cc index 071874700f5..b9fc305dd91 100644 --- a/chromium/base/profiler/stack_sampling_profiler_unittest.cc +++ b/chromium/base/profiler/stack_sampling_profiler_unittest.cc @@ -20,8 +20,6 @@ #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/metrics/metrics_hashes.h" -#include "base/native_library.h" -#include "base/path_service.h" #include "base/profiler/sample_metadata.h" #include "base/profiler/stack_sampler.h" #include "base/profiler/stack_sampling_profiler.h" @@ -66,69 +64,11 @@ using SamplingParams = StackSamplingProfiler::SamplingParams; namespace { -// Calls into |wait_for_sample| after using alloca(), to test unwinding with a -// frame pointer. -// Disable inlining for this function so that it gets its own stack frame. -NOINLINE FunctionAddressRange CallWithAlloca(OnceClosure wait_for_sample) { - const void* start_program_counter = GetProgramCounter(); - - // Volatile to force a dynamic stack allocation. - const volatile size_t alloca_size = 100; - // Use the memory via volatile writes to prevent the allocation from being - // optimized out. - volatile char* const allocation = - const_cast<volatile char*>(static_cast<char*>(alloca(alloca_size))); - for (volatile char* p = allocation; p < allocation + alloca_size; ++p) - *p = '\0'; - - if (!wait_for_sample.is_null()) - std::move(wait_for_sample).Run(); - - // Volatile to prevent a tail call to GetProgramCounter(). - const void* volatile end_program_counter = GetProgramCounter(); - return {start_program_counter, end_program_counter}; -} - -// The function to be executed by the code in the other library. -void OtherLibraryCallback(void* arg) { - OnceClosure* wait_for_sample = static_cast<OnceClosure*>(arg); - - std::move(*wait_for_sample).Run(); - - // Prevent tail call. - volatile int i = 0; - ALLOW_UNUSED_LOCAL(i); -} - -// Calls into |wait_for_sample| through a function within another library, to -// test unwinding through multiple modules and scenarios involving unloaded -// modules. -// Disable inlining for this function so that it gets its own stack frame. -NOINLINE FunctionAddressRange -CallThroughOtherLibrary(NativeLibrary library, OnceClosure wait_for_sample) { - const void* start_program_counter = GetProgramCounter(); - - if (!wait_for_sample.is_null()) { - // A function whose arguments are a function accepting void*, and a void*. - using InvokeCallbackFunction = void (*)(void (*)(void*), void*); - EXPECT_TRUE(library); - InvokeCallbackFunction function = reinterpret_cast<InvokeCallbackFunction>( - GetFunctionPointerFromNativeLibrary(library, "InvokeCallbackFunction")); - EXPECT_TRUE(function); - - (*function)(&OtherLibraryCallback, &wait_for_sample); - } - - // Volatile to prevent a tail call to GetProgramCounter(). - const void* volatile end_program_counter = GetProgramCounter(); - return {start_program_counter, end_program_counter}; -} - // State provided to the ProfileBuilder's ApplyMetadataRetrospectively function. struct RetrospectiveMetadata { TimeTicks period_start; TimeTicks period_end; - ProfileBuilder::MetadataItem item; + MetadataRecorder::Item item; }; // Profile consists of a set of samples and other sampling information. @@ -165,10 +105,11 @@ class TestProfileBuilder : public ProfileBuilder { // ProfileBuilder: ModuleCache* GetModuleCache() override; void RecordMetadata( - ProfileBuilder::MetadataProvider* metadata_provider) override; - void ApplyMetadataRetrospectively(TimeTicks period_start, - TimeTicks period_end, - const MetadataItem& item) override; + const MetadataRecorder::MetadataProvider& metadata_provider) override; + void ApplyMetadataRetrospectively( + TimeTicks period_start, + TimeTicks period_end, + const MetadataRecorder::Item& item) override; void OnSampleCompleted(std::vector<Frame> sample, TimeTicks sample_timestamp) override; void OnProfileCompleted(TimeDelta profile_duration, @@ -203,14 +144,14 @@ ModuleCache* TestProfileBuilder::GetModuleCache() { } void TestProfileBuilder::RecordMetadata( - ProfileBuilder::MetadataProvider* metadata_provider) { + const MetadataRecorder::MetadataProvider& metadata_provider) { ++record_metadata_count_; } void TestProfileBuilder::ApplyMetadataRetrospectively( TimeTicks period_start, TimeTicks period_end, - const MetadataItem& item) { + const MetadataRecorder::Item& item) { retrospective_metadata_.push_back( RetrospectiveMetadata{period_start, period_end, item}); } @@ -227,27 +168,6 @@ void TestProfileBuilder::OnProfileCompleted(TimeDelta profile_duration, sampling_period}); } -// Loads the other library, which defines a function to be called in the -// WITH_OTHER_LIBRARY configuration. -NativeLibrary LoadOtherLibrary() { - // The lambda gymnastics works around the fact that we can't use ASSERT_* - // macros in a function returning non-null. - const auto load = [](NativeLibrary* library) { - FilePath other_library_path; - ASSERT_TRUE(PathService::Get(DIR_MODULE, &other_library_path)); - other_library_path = other_library_path.AppendASCII( - GetLoadableModuleName("base_profiler_test_support_library")); - NativeLibraryLoadError load_error; - *library = LoadNativeLibrary(other_library_path, &load_error); - ASSERT_TRUE(*library) << "error loading " << other_library_path.value() - << ": " << load_error.ToString(); - }; - - NativeLibrary library = nullptr; - load(&library); - return library; -} - // Unloads |library| and returns when it has completed unloading. Unloading a // library is asynchronous on Windows, so simply calling UnloadNativeLibrary() // is insufficient to ensure it's been unloaded. @@ -293,6 +213,7 @@ struct TestProfilerInfo { profile = std::move(result_profile); completed.Signal(); })), + nullptr, delegate) {} // The order here is important to ensure objects being referenced don't get @@ -426,7 +347,7 @@ void TestLibraryUnload(bool wait_until_unloaded, ModuleCache* module_cache) { profile = std::move(result_profile); sampling_thread_completed.Signal(); })), - &test_delegate); + nullptr, &test_delegate); profiler.Start(); @@ -1439,7 +1360,7 @@ PROFILER_TEST_F(StackSamplingProfilerTest, BindLambdaForTesting([&profile](Profile result_profile) { profile = std::move(result_profile); })), - &post_sample_invoker); + nullptr, &post_sample_invoker); profiler.Start(); // Wait for 5 samples to be collected. for (int i = 0; i < 5; ++i) diff --git a/chromium/base/profiler/suspendable_thread_delegate_mac.cc b/chromium/base/profiler/suspendable_thread_delegate_mac.cc index 32fdd475cb2..a31a0caf2b1 100644 --- a/chromium/base/profiler/suspendable_thread_delegate_mac.cc +++ b/chromium/base/profiler/suspendable_thread_delegate_mac.cc @@ -8,7 +8,7 @@ #include <mach/thread_act.h> #include <pthread.h> -#include "base/logging.h" +#include "base/check.h" #include "base/mac/mach_logging.h" #include "base/profiler/profile_builder.h" diff --git a/chromium/base/profiler/suspendable_thread_delegate_win.cc b/chromium/base/profiler/suspendable_thread_delegate_win.cc index e2a30cf34be..1fbc67eb3f2 100644 --- a/chromium/base/profiler/suspendable_thread_delegate_win.cc +++ b/chromium/base/profiler/suspendable_thread_delegate_win.cc @@ -7,8 +7,8 @@ #include <windows.h> #include <winternl.h> +#include "base/check.h" #include "base/debug/alias.h" -#include "base/logging.h" #include "base/profiler/native_unwinder_win.h" #include "build/build_config.h" diff --git a/chromium/base/profiler/unwindstack_internal_android.cc b/chromium/base/profiler/unwindstack_internal_android.cc new file mode 100644 index 00000000000..92328590aa4 --- /dev/null +++ b/chromium/base/profiler/unwindstack_internal_android.cc @@ -0,0 +1,30 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/profiler/unwindstack_internal_android.h" + +#include <string.h> + +#include "base/logging.h" + +namespace base { + +UnwindStackMemoryAndroid::UnwindStackMemoryAndroid(uintptr_t stack_ptr, + uintptr_t stack_top) + : stack_ptr_(stack_ptr), stack_top_(stack_top) { + DCHECK_LE(stack_ptr_, stack_top_); +} + +UnwindStackMemoryAndroid::~UnwindStackMemoryAndroid() = default; + +size_t UnwindStackMemoryAndroid::Read(uint64_t addr, void* dst, size_t size) { + if (addr < stack_ptr_) + return 0; + if (size >= stack_top_ || addr > stack_top_ - size) + return 0; + memcpy(dst, reinterpret_cast<void*>(addr), size); + return size; +} + +} // namespace base
\ No newline at end of file diff --git a/chromium/base/profiler/unwindstack_internal_android.h b/chromium/base/profiler/unwindstack_internal_android.h new file mode 100644 index 00000000000..75058613fc7 --- /dev/null +++ b/chromium/base/profiler/unwindstack_internal_android.h @@ -0,0 +1,34 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_PROFILER_UNWINDSTACK_INTERNAL_ANDROID_H_ +#define BASE_PROFILER_UNWINDSTACK_INTERNAL_ANDROID_H_ + +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Maps.h" +#include "third_party/libunwindstack/src/libunwindstack/include/unwindstack/Memory.h" + +// Avoid including this file directly in a header as it leaks headers from +// libunwindstack. In particular, it's not to be included directly or +// transitively from native_unwinder_android.h + +namespace base { + +// Implementation of unwindstack::Memory that restricts memory access to a stack +// buffer, used by NativeUnwinderAndroid. While unwinding, only memory accesses +// within the stack should be performed to restore registers. +class UnwindStackMemoryAndroid : public unwindstack::Memory { + public: + UnwindStackMemoryAndroid(uintptr_t stack_ptr, uintptr_t stack_top); + ~UnwindStackMemoryAndroid() override; + + size_t Read(uint64_t addr, void* dst, size_t size) override; + + private: + const uintptr_t stack_ptr_; + const uintptr_t stack_top_; +}; + +} // namespace base + +#endif // BASE_PROFILER_UNWINDSTACK_INTERNAL_ANDROID_H_
\ No newline at end of file |