// Copyright 2016 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/debug/activity_tracker.h" #include #include #include #include "base/atomic_sequence_num.h" #include "base/bits.h" #include "base/containers/contains.h" #include "base/debug/stack_trace.h" #include "base/files/file.h" #include "base/files/file_path.h" #include "base/files/memory_mapped_file.h" #include "base/logging.h" #include "base/memory/ptr_util.h" #include "base/metrics/field_trial.h" #include "base/metrics/histogram_macros.h" #include "base/notreached.h" #include "base/pending_task.h" #include "base/pickle.h" #include "base/process/process.h" #include "base/process/process_handle.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/threading/platform_thread.h" #include "build/build_config.h" #if BUILDFLAG(IS_WIN) #include #endif namespace base { namespace debug { namespace { // The minimum depth a stack should support. const int kMinStackDepth = 2; // The amount of memory set aside for holding arbitrary user data (key/value // pairs) globally or associated with ActivityData entries. const size_t kUserDataSize = 1 << 10; // 1 KiB const size_t kProcessDataSize = 4 << 10; // 4 KiB const size_t kMaxUserDataNameLength = static_cast(std::numeric_limits::max()); // A constant used to indicate that module information is changing. const uint32_t kModuleInformationChanging = 0x80000000; // The key used to record process information. const char kProcessPhaseDataKey[] = "process-phase"; // An atomically incrementing number, used to check for recreations of objects // in the same memory space. AtomicSequenceNumber g_next_id; // Gets the next non-zero identifier. It is only unique within a process. uint32_t GetNextDataId() { uint32_t id; while ((id = static_cast(g_next_id.GetNext())) == 0) { } return id; } // Gets the current process-id, either from the GlobalActivityTracker if it // exists (where the PID can be defined for testing) or from the system if // there isn't such. ProcessId GetProcessId() { GlobalActivityTracker* global = GlobalActivityTracker::Get(); if (global) return global->process_id(); return GetCurrentProcId(); } // Finds and reuses a specific allocation or creates a new one. PersistentMemoryAllocator::Reference AllocateFrom( PersistentMemoryAllocator* allocator, uint32_t from_type, size_t size, uint32_t to_type) { PersistentMemoryAllocator::Iterator iter(allocator); PersistentMemoryAllocator::Reference ref; while ((ref = iter.GetNextOfType(from_type)) != 0) { DCHECK_LE(size, allocator->GetAllocSize(ref)); // This can fail if a another thread has just taken it. It is assumed that // the memory is cleared during the "free" operation. if (allocator->ChangeType(ref, to_type, from_type, /*clear=*/false)) return ref; } return allocator->Allocate(size, to_type); } // Converts "tick" timing into wall time. Time WallTimeFromTickTime(int64_t ticks_start, int64_t ticks, Time time_start) { return time_start + TimeDelta::FromInternalValue(ticks - ticks_start); } } // namespace union ThreadRef { int64_t as_id; #if BUILDFLAG(IS_WIN) // On Windows, the handle itself is often a pseudo-handle with a common // value meaning "this thread" and so the thread-id is used. The former // can be converted to a thread-id with a system call. PlatformThreadId as_tid; #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) // On Posix and Fuchsia, the handle is always a unique identifier so no // conversion needs to be done. However, its value is officially opaque so // there is no one correct way to convert it to a numerical identifier. PlatformThreadHandle::Handle as_handle; #endif }; OwningProcess::OwningProcess() = default; OwningProcess::~OwningProcess() = default; void OwningProcess::Release_Initialize(ProcessId pid) { uint32_t old_id = data_id.load(std::memory_order_acquire); DCHECK_EQ(0U, old_id); process_id = static_cast(pid != 0 ? pid : GetProcessId()); create_stamp = Time::Now().ToInternalValue(); data_id.store(GetNextDataId(), std::memory_order_release); } void OwningProcess::SetOwningProcessIdForTesting(ProcessId pid, int64_t stamp) { DCHECK_NE(0U, data_id); process_id = static_cast(pid); create_stamp = stamp; } // static bool OwningProcess::GetOwningProcessId(const void* memory, ProcessId* out_id, int64_t* out_stamp) { const OwningProcess* info = reinterpret_cast(memory); uint32_t id = info->data_id.load(std::memory_order_acquire); if (id == 0) return false; *out_id = static_cast(info->process_id); *out_stamp = info->create_stamp; return id == info->data_id.load(std::memory_order_seq_cst); } // It doesn't matter what is contained in this (though it will be all zeros) // as only the address of it is important. const ActivityData kNullActivityData = {}; ActivityData ActivityData::ForThread(const PlatformThreadHandle& handle) { ThreadRef thread_ref; thread_ref.as_id = 0; // Zero the union in case other is smaller. #if BUILDFLAG(IS_WIN) thread_ref.as_tid = ::GetThreadId(handle.platform_handle()); #elif BUILDFLAG(IS_POSIX) thread_ref.as_handle = handle.platform_handle(); #endif return ForThread(thread_ref.as_id); } ActivityTrackerMemoryAllocator::ActivityTrackerMemoryAllocator( PersistentMemoryAllocator* allocator, uint32_t object_type, uint32_t object_free_type, size_t object_size, size_t cache_size, bool make_iterable) : allocator_(allocator), object_type_(object_type), object_free_type_(object_free_type), object_size_(object_size), cache_size_(cache_size), make_iterable_(make_iterable), iterator_(allocator), cache_values_(new Reference[cache_size]), cache_used_(0) { DCHECK(allocator); } ActivityTrackerMemoryAllocator::~ActivityTrackerMemoryAllocator() = default; ActivityTrackerMemoryAllocator::Reference ActivityTrackerMemoryAllocator::GetObjectReference() { // First see if there is a cached value that can be returned. This is much // faster than searching the memory system for free blocks. while (cache_used_ > 0) { Reference cached = cache_values_[--cache_used_]; // Change the type of the cached object to the proper type and return it. // If the type-change fails that means another thread has taken this from // under us (via the search below) so ignore it and keep trying. Don't // clear the memory because that was done when the type was made "free". if (allocator_->ChangeType(cached, object_type_, object_free_type_, false)) return cached; } // Fetch the next "free" object from persistent memory. Rather than restart // the iterator at the head each time and likely waste time going again // through objects that aren't relevant, the iterator continues from where // it last left off and is only reset when the end is reached. If the // returned reference matches |last|, then it has wrapped without finding // anything. const Reference last = iterator_.GetLast(); while (true) { uint32_t type; Reference found = iterator_.GetNext(&type); if (found && type == object_free_type_) { // Found a free object. Change it to the proper type and return it. If // the type-change fails that means another thread has taken this from // under us so ignore it and keep trying. if (allocator_->ChangeType(found, object_type_, object_free_type_, false)) return found; } if (found == last) { // Wrapped. No desired object was found. break; } if (!found) { // Reached end; start over at the beginning. iterator_.Reset(); } } // No free block was found so instead allocate a new one. Reference allocated = allocator_->Allocate(object_size_, object_type_); if (allocated && make_iterable_) allocator_->MakeIterable(allocated); return allocated; } void ActivityTrackerMemoryAllocator::ReleaseObjectReference(Reference ref) { // Mark object as free. bool success = allocator_->ChangeType(ref, object_free_type_, object_type_, /*clear=*/true); DCHECK(success); // Add this reference to our "free" cache if there is space. If not, the type // has still been changed to indicate that it is free so this (or another) // thread can find it, albeit more slowly, using the iteration method above. if (cache_used_ < cache_size_) cache_values_[cache_used_++] = ref; } // static void Activity::FillFrom(Activity* activity, const void* program_counter, const void* origin, Type type, const ActivityData& data) { activity->time_internal = base::TimeTicks::Now().ToInternalValue(); activity->calling_address = reinterpret_cast(program_counter); activity->origin_address = reinterpret_cast(origin); activity->activity_type = type; activity->data = data; #if (!BUILDFLAG(IS_NACL) && DCHECK_IS_ON()) || defined(ADDRESS_SANITIZER) // Create a stacktrace from the current location and get the addresses for // improved debuggability. StackTrace stack_trace; size_t stack_depth; const void* const* stack_addrs = stack_trace.Addresses(&stack_depth); // Copy the stack addresses, ignoring the first one (here). size_t i; for (i = 1; i < stack_depth && i < kActivityCallStackSize; ++i) { activity->call_stack[i - 1] = reinterpret_cast(stack_addrs[i]); } activity->call_stack[i - 1] = 0; #else activity->call_stack[0] = 0; #endif } ActivityUserData::TypedValue::TypedValue() = default; ActivityUserData::TypedValue::TypedValue(const TypedValue& other) = default; ActivityUserData::TypedValue::~TypedValue() = default; StringPiece ActivityUserData::TypedValue::Get() const { DCHECK_EQ(RAW_VALUE, type_); return long_value_; } StringPiece ActivityUserData::TypedValue::GetString() const { DCHECK_EQ(STRING_VALUE, type_); return long_value_; } bool ActivityUserData::TypedValue::GetBool() const { DCHECK_EQ(BOOL_VALUE, type_); return short_value_ != 0; } char ActivityUserData::TypedValue::GetChar() const { DCHECK_EQ(CHAR_VALUE, type_); return static_cast(short_value_); } int64_t ActivityUserData::TypedValue::GetInt() const { DCHECK_EQ(SIGNED_VALUE, type_); return static_cast(short_value_); } uint64_t ActivityUserData::TypedValue::GetUint() const { DCHECK_EQ(UNSIGNED_VALUE, type_); return static_cast(short_value_); } StringPiece ActivityUserData::TypedValue::GetReference() const { DCHECK_EQ(RAW_VALUE_REFERENCE, type_); return ref_value_; } StringPiece ActivityUserData::TypedValue::GetStringReference() const { DCHECK_EQ(STRING_VALUE_REFERENCE, type_); return ref_value_; } // These are required because std::atomic is (currently) not a POD type and // thus clang requires explicit out-of-line constructors and destructors even // when they do nothing. ActivityUserData::ValueInfo::ValueInfo() = default; ActivityUserData::ValueInfo::ValueInfo(ValueInfo&&) = default; ActivityUserData::ValueInfo::~ValueInfo() = default; ActivityUserData::MemoryHeader::MemoryHeader() = default; ActivityUserData::MemoryHeader::~MemoryHeader() = default; ActivityUserData::FieldHeader::FieldHeader() = default; ActivityUserData::FieldHeader::~FieldHeader() = default; ActivityUserData::ActivityUserData() : ActivityUserData(nullptr, 0, static_cast(-1)) {} ActivityUserData::ActivityUserData(void* memory, size_t size, ProcessId pid) : memory_(reinterpret_cast(memory)), available_(bits::AlignDown(size, kMemoryAlignment)), header_(reinterpret_cast(memory)), orig_data_id(0), orig_process_id(0), orig_create_stamp(0) { // It's possible that no user data is being stored. if (!memory_) return; static_assert(0 == sizeof(MemoryHeader) % kMemoryAlignment, "invalid header"); DCHECK_LT(sizeof(MemoryHeader), available_); if (header_->owner.data_id.load(std::memory_order_acquire) == 0) header_->owner.Release_Initialize(pid); memory_ += sizeof(MemoryHeader); available_ -= sizeof(MemoryHeader); // Make a copy of identifying information for later comparison. *const_cast(&orig_data_id) = header_->owner.data_id.load(std::memory_order_acquire); *const_cast(&orig_process_id) = static_cast(header_->owner.process_id); *const_cast(&orig_create_stamp) = header_->owner.create_stamp; // If there is already data present, load that. This allows the same class // to be used for analysis through snapshots. ImportExistingData(); } ActivityUserData::~ActivityUserData() = default; bool ActivityUserData::CreateSnapshot(Snapshot* output_snapshot) const { DCHECK(output_snapshot); DCHECK(output_snapshot->empty()); // Find any new data that may have been added by an active instance of this // class that is adding records. ImportExistingData(); // Add all the values to the snapshot. for (const auto& entry : values_) { TypedValue value; const size_t size = entry.second.size_ptr->load(std::memory_order_acquire); value.type_ = entry.second.type; DCHECK_GE(entry.second.extent, size); switch (entry.second.type) { case RAW_VALUE: case STRING_VALUE: value.long_value_ = std::string( reinterpret_cast(entry.second.memory.get()), size); break; case RAW_VALUE_REFERENCE: case STRING_VALUE_REFERENCE: { ReferenceRecord* ref = reinterpret_cast(entry.second.memory.get()); value.ref_value_ = StringPiece( reinterpret_cast(static_cast(ref->address)), static_cast(ref->size)); } break; case BOOL_VALUE: case CHAR_VALUE: value.short_value_ = static_cast( reinterpret_cast*>(entry.second.memory.get()) ->load(std::memory_order_relaxed)); break; case SIGNED_VALUE: case UNSIGNED_VALUE: value.short_value_ = reinterpret_cast*>(entry.second.memory.get()) ->load(std::memory_order_relaxed); break; case END_OF_VALUES: // Included for completeness purposes. NOTREACHED(); } auto inserted = output_snapshot->emplace(std::string(entry.second.name), std::move(value)); DCHECK(inserted.second); // True if inserted, false if existed. } // Another import attempt will validate that the underlying memory has not // been reused for another purpose. Entries added since the first import // will be ignored here but will be returned if another snapshot is created. ImportExistingData(); if (!memory_) { output_snapshot->clear(); return false; } // Successful snapshot. return true; } const void* ActivityUserData::GetBaseAddress() const { // The |memory_| pointer advances as elements are written but the |header_| // value is always at the start of the block so just return that. return header_; } void ActivityUserData::SetOwningProcessIdForTesting(ProcessId pid, int64_t stamp) { if (!header_) return; header_->owner.SetOwningProcessIdForTesting(pid, stamp); } // static bool ActivityUserData::GetOwningProcessId(const void* memory, ProcessId* out_id, int64_t* out_stamp) { const MemoryHeader* header = reinterpret_cast(memory); return OwningProcess::GetOwningProcessId(&header->owner, out_id, out_stamp); } void* ActivityUserData::Set(StringPiece name, ValueType type, const void* memory, size_t size) { DCHECK_LT(name.length(), kMaxUserDataNameLength); // It's possible that no user data is being stored. if (!memory_) return nullptr; ValueInfo* info; auto existing = values_.find(name); if (existing != values_.end()) { info = &existing->second; } else { // The name size is limited to what can be held in a single byte but // because there are not alignment constraints on strings, it's set tight // against the header. Its extent (the reserved space, even if it's not // all used) is calculated so that, when pressed against the header, the // following field will be aligned properly. size_t name_size = name.length(); size_t name_extent = bits::AlignUp(sizeof(FieldHeader) + name_size, kMemoryAlignment) - sizeof(FieldHeader); size_t value_extent = bits::AlignUp(size, kMemoryAlignment); // The "base size" is the size of the header and (padded) string key. Stop // now if there's not room enough for even this. size_t base_size = sizeof(FieldHeader) + name_extent; if (base_size > available_) return nullptr; // The "full size" is the size for storing the entire value. This must fit // into a uint16_t. size_t full_size = std::min({base_size + value_extent, available_, bits::AlignDown(size_t{std::numeric_limits::max()}, kMemoryAlignment)}); // If the value is actually a single byte, see if it can be stuffed at the // end of the name extent rather than wasting kMemoryAlignment bytes. if (size == 1 && name_extent > name_size) { // This assignment is safe because `base_size` cannot be much larger than // UINT8_MAX. full_size = base_size; --name_extent; --base_size; } // Truncate the stored size to the amount of available memory. Stop now if // there's not any room for even part of the value. if (size != 0) { size = std::min(full_size - base_size, size); if (size == 0) return nullptr; } // Allocate a chunk of memory. FieldHeader* header = reinterpret_cast(memory_.get()); memory_ += full_size; available_ -= full_size; // Datafill the header and name records. Memory must be zeroed. The |type| // is written last, atomically, to release all the other values. DCHECK_EQ(END_OF_VALUES, header->type.load(std::memory_order_relaxed)); DCHECK_EQ(0, header->value_size.load(std::memory_order_relaxed)); header->name_size = static_cast(name_size); header->record_size = static_cast(full_size); char* name_memory = reinterpret_cast(header) + sizeof(FieldHeader); void* value_memory = reinterpret_cast(header) + sizeof(FieldHeader) + name_extent; memcpy(name_memory, name.data(), name_size); header->type.store(type, std::memory_order_release); // Create an entry in |values_| so that this field can be found and changed // later on without having to allocate new entries. StringPiece persistent_name(name_memory, name_size); auto inserted = values_.insert(std::make_pair(persistent_name, ValueInfo())); DCHECK(inserted.second); // True if inserted, false if existed. info = &inserted.first->second; info->name = persistent_name; info->memory = value_memory; info->size_ptr = &header->value_size; info->extent = full_size - sizeof(FieldHeader) - name_extent; info->type = type; } // Copy the value data to storage. The |size| is written last, atomically, to // release the copied data. Until then, a parallel reader will just ignore // records with a zero size. DCHECK_EQ(type, info->type); size = std::min(size, info->extent); info->size_ptr->store(0, std::memory_order_seq_cst); memcpy(info->memory, memory, size); // This cast is safe because `size` <= info->extent < `full_size`, and // `full_size` fits in a uint16_t. info->size_ptr->store(static_cast(size), std::memory_order_release); // The address of the stored value is returned so it can be re-used by the // caller, so long as it's done in an atomic way. return info->memory; } void ActivityUserData::SetReference(StringPiece name, ValueType type, const void* memory, size_t size) { ReferenceRecord rec; rec.address = reinterpret_cast(memory); rec.size = size; Set(name, type, &rec, sizeof(rec)); } void ActivityUserData::ImportExistingData() const { // It's possible that no user data is being stored. if (!memory_) return; while (available_ > sizeof(FieldHeader)) { FieldHeader* header = reinterpret_cast(memory_.get()); ValueType type = static_cast(header->type.load(std::memory_order_acquire)); if (type == END_OF_VALUES) return; if (header->record_size > available_) return; size_t value_offset = bits::AlignUp(sizeof(FieldHeader) + header->name_size, kMemoryAlignment); if (header->record_size == value_offset && header->value_size.load(std::memory_order_relaxed) == 1) { value_offset -= 1; } if (value_offset + header->value_size > header->record_size) return; ValueInfo info; info.name = StringPiece(memory_ + sizeof(FieldHeader), header->name_size); info.type = type; info.memory = memory_ + value_offset; info.size_ptr = &header->value_size; info.extent = header->record_size - value_offset; StringPiece key(info.name); values_.insert(std::make_pair(key, std::move(info))); memory_ += header->record_size; available_ -= header->record_size; } // Check if memory has been completely reused. if (header_->owner.data_id.load(std::memory_order_acquire) != orig_data_id || static_cast(header_->owner.process_id) != orig_process_id || header_->owner.create_stamp != orig_create_stamp) { memory_ = nullptr; values_.clear(); } } // This information is kept for every thread that is tracked. It is filled // the very first time the thread is seen. All fields must be of exact sizes // so there is no issue moving between 32 and 64-bit builds. struct ThreadActivityTracker::Header { // Defined in .h for analyzer access. Increment this if structure changes! static constexpr uint32_t kPersistentTypeId = GlobalActivityTracker::kTypeIdActivityTracker; // Expected size for 32/64-bit check. static constexpr size_t kExpectedInstanceSize = OwningProcess::kExpectedInstanceSize + Activity::kExpectedInstanceSize + 72; // This information uniquely identifies a process. OwningProcess owner; // The thread-id (thread_ref.as_id) to which this data belongs. This number // is not guaranteed to mean anything but combined with the process-id from // OwningProcess is unique among all active trackers. ThreadRef thread_ref; // The start-time and start-ticks when the data was created. Each activity // record has a |time_internal| value that can be converted to a "wall time" // with these two values. int64_t start_time; int64_t start_ticks; // The number of Activity slots (spaces that can hold an Activity) that // immediately follow this structure in memory. uint32_t stack_slots; // Some padding to keep everything 64-bit aligned. uint32_t padding; // The current depth of the stack. This may be greater than the number of // slots. If the depth exceeds the number of slots, the newest entries // won't be recorded. std::atomic current_depth; // A memory location used to indicate if changes have been made to the data // that would invalidate an in-progress read of its contents. The active // tracker will increment the value whenever something gets popped from the // stack. A monitoring tracker can check the value before and after access // to know, if it's still the same, that the contents didn't change while // being copied. std::atomic data_version; // The last "exception" activity. This can't be stored on the stack because // that could get popped as things unwind. Activity last_exception; // The name of the thread (up to a maximum length). Dynamic-length names // are not practical since the memory has to come from the same persistent // allocator that holds this structure and to which this object has no // reference. char thread_name[32]; }; ThreadActivityTracker::Snapshot::Snapshot() = default; ThreadActivityTracker::Snapshot::~Snapshot() = default; ThreadActivityTracker::ScopedActivity::ScopedActivity( ThreadActivityTracker* tracker, const void* program_counter, const void* origin, Activity::Type type, const ActivityData& data) : tracker_(tracker) { if (tracker_) activity_id_ = tracker_->PushActivity(program_counter, origin, type, data); } ThreadActivityTracker::ScopedActivity::~ScopedActivity() { if (tracker_) tracker_->PopActivity(activity_id_); } bool ThreadActivityTracker::ScopedActivity::IsRecorded() { return tracker_ && tracker_->IsRecorded(activity_id_); } void ThreadActivityTracker::ScopedActivity::ChangeTypeAndData( Activity::Type type, const ActivityData& data) { if (tracker_) tracker_->ChangeActivity(activity_id_, type, data); } ThreadActivityTracker::ThreadActivityTracker(void* base, size_t size) : header_(static_cast(base)), stack_(reinterpret_cast(reinterpret_cast(base) + sizeof(Header))), #if DCHECK_IS_ON() thread_id_(PlatformThreadRef()), #endif stack_slots_( static_cast((size - sizeof(Header)) / sizeof(Activity))) { // Verify the parameters but fail gracefully if they're not valid so that // production code based on external inputs will not crash. IsValid() will // return false in this case. if (!base || // Ensure there is enough space for the header and at least a few records. size < sizeof(Header) + kMinStackDepth * sizeof(Activity) || // Ensure that the |stack_slots_| calculation didn't overflow. (size - sizeof(Header)) / sizeof(Activity) > std::numeric_limits::max()) { NOTREACHED(); return; } // Ensure that the thread reference doesn't exceed the size of the ID number. // This won't compile at the global scope because Header is a private struct. static_assert( sizeof(header_->thread_ref) == sizeof(header_->thread_ref.as_id), "PlatformThreadHandle::Handle is too big to hold in 64-bit ID"); // Ensure that the alignment of Activity.data is properly aligned to a // 64-bit boundary so there are no interoperability-issues across cpu // architectures. static_assert(offsetof(Activity, data) % sizeof(uint64_t) == 0, "ActivityData.data is not 64-bit aligned"); // Provided memory should either be completely initialized or all zeros. if (header_->owner.data_id.load(std::memory_order_relaxed) == 0) { // This is a new file. Double-check other fields and then initialize. DCHECK_EQ(0, header_->owner.process_id); DCHECK_EQ(0, header_->owner.create_stamp); DCHECK_EQ(0, header_->thread_ref.as_id); DCHECK_EQ(0, header_->start_time); DCHECK_EQ(0, header_->start_ticks); DCHECK_EQ(0U, header_->stack_slots); DCHECK_EQ(0U, header_->current_depth.load(std::memory_order_relaxed)); DCHECK_EQ(0U, header_->data_version.load(std::memory_order_relaxed)); DCHECK_EQ(0, stack_[0].time_internal); DCHECK_EQ(0U, stack_[0].origin_address); DCHECK_EQ(0U, stack_[0].call_stack[0]); DCHECK_EQ(0U, stack_[0].data.task.sequence_id); #if BUILDFLAG(IS_WIN) header_->thread_ref.as_tid = PlatformThread::CurrentId(); #elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA) header_->thread_ref.as_handle = PlatformThread::CurrentHandle().platform_handle(); #endif header_->start_time = base::Time::Now().ToInternalValue(); header_->start_ticks = base::TimeTicks::Now().ToInternalValue(); header_->stack_slots = stack_slots_; strlcpy(header_->thread_name, PlatformThread::GetName(), sizeof(header_->thread_name)); // This is done last so as to guarantee that everything above is "released" // by the time this value gets written. header_->owner.Release_Initialize(); valid_ = true; DCHECK(IsValid()); } else { // This is a file with existing data. Perform basic consistency checks. valid_ = true; valid_ = IsValid(); } } ThreadActivityTracker::~ThreadActivityTracker() = default; ThreadActivityTracker::ActivityId ThreadActivityTracker::PushActivity( const void* program_counter, const void* origin, Activity::Type type, const ActivityData& data) { // A thread-checker creates a lock to check the thread-id which means // re-entry into this code if lock acquisitions are being tracked. DCHECK(type == Activity::ACT_LOCK_ACQUIRE || CalledOnValidThread()); // Get the current depth of the stack. No access to other memory guarded // by this variable is done here so a "relaxed" load is acceptable. uint32_t depth = header_->current_depth.load(std::memory_order_relaxed); // Handle the case where the stack depth has exceeded the storage capacity. // Extra entries will be lost leaving only the base of the stack. if (depth >= stack_slots_) { // Since no other threads modify the data, no compare/exchange is needed. // Since no other memory is being modified, a "relaxed" store is acceptable. header_->current_depth.store(depth + 1, std::memory_order_relaxed); return depth; } // Get a pointer to the next activity and load it. No atomicity is required // here because the memory is known only to this thread. It will be made // known to other threads once the depth is incremented. Activity::FillFrom(&stack_[depth], program_counter, origin, type, data); // Save the incremented depth. Because this guards |activity| memory filled // above that may be read by another thread once the recorded depth changes, // a "release" store is required. header_->current_depth.store(depth + 1, std::memory_order_release); // The current depth is used as the activity ID because it simply identifies // an entry. Once an entry is pop'd, it's okay to reuse the ID. return depth; } void ThreadActivityTracker::ChangeActivity(ActivityId id, Activity::Type type, const ActivityData& data) { DCHECK(CalledOnValidThread()); DCHECK(type != Activity::ACT_NULL || &data != &kNullActivityData); DCHECK_LT(id, header_->current_depth.load(std::memory_order_acquire)); // Update the information if it is being recorded (i.e. within slot limit). if (id < stack_slots_) { Activity* activity = &stack_[id]; if (type != Activity::ACT_NULL) { DCHECK_EQ(activity->activity_type & Activity::ACT_CATEGORY_MASK, type & Activity::ACT_CATEGORY_MASK); activity->activity_type = type; } if (&data != &kNullActivityData) activity->data = data; } } void ThreadActivityTracker::PopActivity(ActivityId id) { // Do an atomic decrement of the depth. No changes to stack entries guarded // by this variable are done here so a "relaxed" operation is acceptable. // |depth| will receive the value BEFORE it was modified which means the // return value must also be decremented. The slot will be "free" after // this call but since only a single thread can access this object, the // data will remain valid until this method returns or calls outside. uint32_t depth = header_->current_depth.fetch_sub(1, std::memory_order_relaxed) - 1; // Validate that everything is running correctly. DCHECK_EQ(id, depth); // A thread-checker creates a lock to check the thread-id which means // re-entry into this code if lock acquisitions are being tracked. DCHECK(stack_[depth].activity_type == Activity::ACT_LOCK_ACQUIRE || CalledOnValidThread()); // The stack has shrunk meaning that some other thread trying to copy the // contents for reporting purposes could get bad data. Increment the data // version so that it con tell that things have changed. This needs to // happen after the atomic |depth| operation above so a "release" store // is required. header_->data_version.fetch_add(1, std::memory_order_release); } bool ThreadActivityTracker::IsRecorded(ActivityId id) { return id < stack_slots_; } std::unique_ptr ThreadActivityTracker::GetUserData( ActivityId id, ActivityTrackerMemoryAllocator* allocator) { // Don't allow user data for lock acquisition as recursion may occur. if (stack_[id].activity_type == Activity::ACT_LOCK_ACQUIRE) { NOTREACHED(); return std::make_unique(); } // User-data is only stored for activities actually held in the stack. if (id >= stack_slots_) return std::make_unique(); // Create and return a real UserData object. return CreateUserDataForActivity(&stack_[id], allocator); } bool ThreadActivityTracker::HasUserData(ActivityId id) { // User-data is only stored for activities actually held in the stack. return (id < stack_slots_ && stack_[id].user_data_ref); } void ThreadActivityTracker::ReleaseUserData( ActivityId id, ActivityTrackerMemoryAllocator* allocator) { // User-data is only stored for activities actually held in the stack. if (id < stack_slots_ && stack_[id].user_data_ref) { allocator->ReleaseObjectReference(stack_[id].user_data_ref); stack_[id].user_data_ref = 0; } } void ThreadActivityTracker::RecordExceptionActivity(const void* program_counter, const void* origin, Activity::Type type, const ActivityData& data) { // A thread-checker creates a lock to check the thread-id which means // re-entry into this code if lock acquisitions are being tracked. DCHECK(CalledOnValidThread()); // Fill the reusable exception activity. Activity::FillFrom(&header_->last_exception, program_counter, origin, type, data); // The data has changed meaning that some other thread trying to copy the // contents for reporting purposes could get bad data. header_->data_version.fetch_add(1, std::memory_order_relaxed); } bool ThreadActivityTracker::IsValid() const { if (header_->owner.data_id.load(std::memory_order_acquire) == 0 || header_->owner.process_id == 0 || header_->thread_ref.as_id == 0 || header_->start_time == 0 || header_->start_ticks == 0 || header_->stack_slots != stack_slots_ || header_->thread_name[sizeof(header_->thread_name) - 1] != '\0') { return false; } return valid_; } bool ThreadActivityTracker::CreateSnapshot(Snapshot* output_snapshot) const { DCHECK(output_snapshot); // There is no "called on valid thread" check for this method as it can be // called from other threads or even other processes. It is also the reason // why atomic operations must be used in certain places above. // It's possible for the data to change while reading it in such a way that it // invalidates the read. Make several attempts but don't try forever. const int kMaxAttempts = 10; uint32_t depth; // Stop here if the data isn't valid. if (!IsValid()) return false; // Allocate the maximum size for the stack so it doesn't have to be done // during the time-sensitive snapshot operation. It is shrunk once the // actual size is known. output_snapshot->activity_stack.reserve(stack_slots_); for (int attempt = 0; attempt < kMaxAttempts; ++attempt) { // Remember the data IDs to ensure nothing is replaced during the snapshot // operation. Use "acquire" so that all the non-atomic fields of the // structure are valid (at least at the current moment in time). const uint32_t starting_id = header_->owner.data_id.load(std::memory_order_acquire); const int64_t starting_create_stamp = header_->owner.create_stamp; const auto starting_process_id = static_cast(header_->owner.process_id); const int64_t starting_thread_id = header_->thread_ref.as_id; // Note the current |data_version| so it's possible to detect at the end // that nothing has changed since copying the data began. A "cst" operation // is required to ensure it occurs before everything else. Using "cst" // memory ordering is relatively expensive but this is only done during // analysis so doesn't directly affect the worker threads. const uint32_t pre_version = header_->data_version.load(std::memory_order_seq_cst); // Fetching the current depth also "acquires" the contents of the stack. depth = header_->current_depth.load(std::memory_order_acquire); uint32_t count = std::min(depth, stack_slots_); output_snapshot->activity_stack.resize(count); if (count > 0) { // Copy the existing contents. Memcpy is used for speed. memcpy(&output_snapshot->activity_stack[0], stack_, count * sizeof(Activity)); } // Capture the last exception. memcpy(&output_snapshot->last_exception, &header_->last_exception, sizeof(Activity)); // Snapshot other things here. // Retry if something changed during the copy. A "cst" operation ensures // it must happen after all the above operations. if (header_->data_version.load(std::memory_order_seq_cst) != pre_version) continue; // Stack copied. Record it's full depth. output_snapshot->activity_stack_depth = depth; // Get the general thread information. output_snapshot->thread_name = std::string(header_->thread_name, sizeof(header_->thread_name) - 1); output_snapshot->create_stamp = header_->owner.create_stamp; output_snapshot->thread_id = header_->thread_ref.as_id; output_snapshot->process_id = static_cast(header_->owner.process_id); // All characters of the thread-name buffer were copied so as to not break // if the trailing NUL were missing. Now limit the length if the actual // name is shorter. output_snapshot->thread_name.resize( strlen(output_snapshot->thread_name.c_str())); // If the data ID has changed then the tracker has exited and the memory // reused by a new one. Try again. if (header_->owner.data_id.load(std::memory_order_seq_cst) != starting_id || output_snapshot->create_stamp != starting_create_stamp || output_snapshot->process_id != starting_process_id || output_snapshot->thread_id != starting_thread_id) { continue; } // Only successful if the data is still valid once everything is done since // it's possible for the thread to end somewhere in the middle and all its // values become garbage. if (!IsValid()) return false; // Change all the timestamps in the activities from "ticks" to "wall" time. const Time start_time = Time::FromInternalValue(header_->start_time); const int64_t start_ticks = header_->start_ticks; for (Activity& activity : output_snapshot->activity_stack) { activity.time_internal = WallTimeFromTickTime(start_ticks, activity.time_internal, start_time) .ToInternalValue(); } output_snapshot->last_exception.time_internal = WallTimeFromTickTime(start_ticks, output_snapshot->last_exception.time_internal, start_time) .ToInternalValue(); // Success! return true; } // Too many attempts. return false; } const void* ThreadActivityTracker::GetBaseAddress() { return header_; } uint32_t ThreadActivityTracker::GetDataVersionForTesting() { return header_->data_version.load(std::memory_order_relaxed); } void ThreadActivityTracker::SetOwningProcessIdForTesting(ProcessId pid, int64_t stamp) { header_->owner.SetOwningProcessIdForTesting(pid, stamp); } // static bool ThreadActivityTracker::GetOwningProcessId(const void* memory, ProcessId* out_id, int64_t* out_stamp) { const Header* header = reinterpret_cast(memory); return OwningProcess::GetOwningProcessId(&header->owner, out_id, out_stamp); } // static size_t ThreadActivityTracker::SizeForStackDepth(int stack_depth) { return static_cast(stack_depth) * sizeof(Activity) + sizeof(Header); } bool ThreadActivityTracker::CalledOnValidThread() { #if DCHECK_IS_ON() return thread_id_ == PlatformThreadRef(); #else return true; #endif } std::unique_ptr ThreadActivityTracker::CreateUserDataForActivity( Activity* activity, ActivityTrackerMemoryAllocator* allocator) { DCHECK_EQ(0U, activity->user_data_ref); PersistentMemoryAllocator::Reference ref = allocator->GetObjectReference(); void* memory = allocator->GetAsArray(ref, kUserDataSize); if (memory) { std::unique_ptr user_data = std::make_unique(memory, kUserDataSize); activity->user_data_ref = ref; activity->user_data_id = user_data->id(); return user_data; } // Return a dummy object that will still accept (but ignore) Set() calls. return std::make_unique(); } // The instantiation of the GlobalActivityTracker object. // The object held here will obviously not be destructed at process exit // but that's best since PersistentMemoryAllocator objects (that underlie // GlobalActivityTracker objects) are explicitly forbidden from doing anything // essential at exit anyway due to the fact that they depend on data managed // elsewhere and which could be destructed first. std::atomic GlobalActivityTracker::g_tracker_{nullptr}; GlobalActivityTracker::ModuleInfo::ModuleInfo() = default; GlobalActivityTracker::ModuleInfo::ModuleInfo(ModuleInfo&& rhs) = default; GlobalActivityTracker::ModuleInfo::ModuleInfo(const ModuleInfo& rhs) = default; GlobalActivityTracker::ModuleInfo::~ModuleInfo() = default; GlobalActivityTracker::ModuleInfo& GlobalActivityTracker::ModuleInfo::operator=( ModuleInfo&& rhs) = default; GlobalActivityTracker::ModuleInfo& GlobalActivityTracker::ModuleInfo::operator=( const ModuleInfo& rhs) = default; GlobalActivityTracker::ModuleInfoRecord::ModuleInfoRecord() = default; GlobalActivityTracker::ModuleInfoRecord::~ModuleInfoRecord() = default; bool GlobalActivityTracker::ModuleInfoRecord::DecodeTo( GlobalActivityTracker::ModuleInfo* info, size_t record_size) const { // Get the current "changes" indicator, acquiring all the other values. uint32_t current_changes = changes.load(std::memory_order_acquire); // Copy out the dynamic information. info->is_loaded = loaded != 0; info->address = static_cast(address); info->load_time = load_time; // Check to make sure no information changed while being read. A "seq-cst" // operation is expensive but is only done during analysis and it's the only // way to ensure this occurs after all the accesses above. If changes did // occur then return a "not loaded" result so that |size| and |address| // aren't expected to be accurate. if ((current_changes & kModuleInformationChanging) != 0 || changes.load(std::memory_order_seq_cst) != current_changes) { info->is_loaded = false; } // Copy out the static information. These never change so don't have to be // protected by the atomic |current_changes| operations. info->size = static_cast(size); info->timestamp = timestamp; info->age = age; memcpy(info->identifier, identifier, sizeof(info->identifier)); if (offsetof(ModuleInfoRecord, pickle) + pickle_size > record_size) return false; Pickle pickler(pickle, pickle_size); PickleIterator iter(pickler); return iter.ReadString(&info->file) && iter.ReadString(&info->debug_file); } GlobalActivityTracker::ModuleInfoRecord* GlobalActivityTracker::ModuleInfoRecord::CreateFrom( const GlobalActivityTracker::ModuleInfo& info, PersistentMemoryAllocator* allocator) { Pickle pickler; pickler.WriteString(info.file); pickler.WriteString(info.debug_file); size_t required_size = offsetof(ModuleInfoRecord, pickle) + pickler.size(); ModuleInfoRecord* record = allocator->New(required_size); if (!record) return nullptr; // These fields never changes and are done before the record is made // iterable so no thread protection is necessary. record->size = info.size; record->timestamp = info.timestamp; record->age = info.age; memcpy(record->identifier, info.identifier, sizeof(identifier)); memcpy(record->pickle, pickler.data(), pickler.size()); record->pickle_size = checked_cast(pickler.size()); record->changes.store(0, std::memory_order_relaxed); // Initialize the owner info. record->owner.Release_Initialize(); // Now set those fields that can change. bool success = record->UpdateFrom(info); DCHECK(success); return record; } bool GlobalActivityTracker::ModuleInfoRecord::UpdateFrom( const GlobalActivityTracker::ModuleInfo& info) { // Updates can occur after the record is made visible so make changes atomic. // A "strong" exchange ensures no false failures. uint32_t old_changes = changes.load(std::memory_order_relaxed); uint32_t new_changes = old_changes | kModuleInformationChanging; if ((old_changes & kModuleInformationChanging) != 0 || !changes.compare_exchange_strong(old_changes, new_changes, std::memory_order_acquire, std::memory_order_acquire)) { NOTREACHED() << "Multiple sources are updating module information."; return false; } loaded = info.is_loaded ? 1 : 0; address = info.address; load_time = Time::Now().ToInternalValue(); bool success = changes.compare_exchange_strong(new_changes, old_changes + 1, std::memory_order_release, std::memory_order_relaxed); DCHECK(success); return true; } GlobalActivityTracker::ScopedThreadActivity::ScopedThreadActivity( const void* program_counter, const void* origin, Activity::Type type, const ActivityData& data, bool lock_allowed) : ThreadActivityTracker::ScopedActivity(GetOrCreateTracker(lock_allowed), program_counter, origin, type, data) {} GlobalActivityTracker::ScopedThreadActivity::~ScopedThreadActivity() { if (tracker_ && tracker_->HasUserData(activity_id_)) { GlobalActivityTracker* global = GlobalActivityTracker::Get(); AutoLock lock(global->user_data_allocator_lock_); tracker_->ReleaseUserData(activity_id_, &global->user_data_allocator_); } } ActivityUserData& GlobalActivityTracker::ScopedThreadActivity::user_data() { if (!user_data_) { if (tracker_) { GlobalActivityTracker* global = GlobalActivityTracker::Get(); AutoLock lock(global->user_data_allocator_lock_); user_data_ = tracker_->GetUserData(activity_id_, &global->user_data_allocator_); } else { user_data_ = std::make_unique(); } } return *user_data_; } GlobalActivityTracker::ThreadSafeUserData::ThreadSafeUserData(void* memory, size_t size, ProcessId pid) : ActivityUserData(memory, size, pid) {} GlobalActivityTracker::ThreadSafeUserData::~ThreadSafeUserData() = default; void* GlobalActivityTracker::ThreadSafeUserData::Set(StringPiece name, ValueType type, const void* memory, size_t size) { AutoLock lock(data_lock_); return ActivityUserData::Set(name, type, memory, size); } GlobalActivityTracker::ManagedActivityTracker::ManagedActivityTracker( PersistentMemoryAllocator::Reference mem_reference, void* base, size_t size) : ThreadActivityTracker(base, size), mem_reference_(mem_reference), mem_base_(base) {} GlobalActivityTracker::ManagedActivityTracker::~ManagedActivityTracker() { // The global |g_tracker_| must point to the owner of this class since all // objects of this type must be destructed before |g_tracker_| can be changed // (something that only occurs in tests). DCHECK(g_tracker_.load(std::memory_order_relaxed)); GlobalActivityTracker::Get()->ReturnTrackerMemory(this); } void GlobalActivityTracker::CreateWithAllocator( std::unique_ptr allocator, int stack_depth, ProcessId process_id) { // There's no need to do anything with the result. It is self-managing. GlobalActivityTracker* global_tracker = new GlobalActivityTracker(std::move(allocator), stack_depth, process_id); // Create a tracker for this thread since it is known. global_tracker->CreateTrackerForCurrentThread(); } #if !BUILDFLAG(IS_NACL) // static bool GlobalActivityTracker::CreateWithFile(const FilePath& file_path, size_t size, uint64_t id, StringPiece name, int stack_depth) { DCHECK(!file_path.empty()); DCHECK_GE(static_cast(std::numeric_limits::max()), size); // Create and map the file into memory and make it globally available. std::unique_ptr mapped_file(new MemoryMappedFile()); bool success = mapped_file->Initialize( File(file_path, File::FLAG_CREATE_ALWAYS | File::FLAG_READ | File::FLAG_WRITE | File::FLAG_WIN_SHARE_DELETE), {0, size}, MemoryMappedFile::READ_WRITE_EXTEND); if (!success) return false; if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mapped_file, false)) return false; CreateWithAllocator(std::make_unique( std::move(mapped_file), size, id, name, false), stack_depth, 0); return true; } #endif // !BUILDFLAG(IS_NACL) // static bool GlobalActivityTracker::CreateWithLocalMemory(size_t size, uint64_t id, StringPiece name, int stack_depth, ProcessId process_id) { CreateWithAllocator( std::make_unique(size, id, name), stack_depth, process_id); return true; } // static bool GlobalActivityTracker::CreateWithSharedMemory( base::WritableSharedMemoryMapping mapping, uint64_t id, StringPiece name, int stack_depth) { if (!mapping.IsValid() || !WritableSharedPersistentMemoryAllocator::IsSharedMemoryAcceptable( mapping)) { return false; } CreateWithAllocator(std::make_unique( std::move(mapping), id, name), stack_depth, 0); return true; } // static void GlobalActivityTracker::SetForTesting( std::unique_ptr tracker) { CHECK(!g_tracker_.load(std::memory_order_relaxed)); g_tracker_.store(tracker.release(), std::memory_order_release); } // static std::unique_ptr GlobalActivityTracker::ReleaseForTesting() { GlobalActivityTracker* tracker = Get(); if (!tracker) return nullptr; // Thread trackers assume that the global tracker is present for some // operations so ensure that there aren't any. tracker->ReleaseTrackerForCurrentThreadForTesting(); DCHECK_EQ(0, tracker->thread_tracker_count_.load(std::memory_order_relaxed)); g_tracker_.store(nullptr, std::memory_order_release); return WrapUnique(tracker); } ThreadActivityTracker* GlobalActivityTracker::CreateTrackerForCurrentThread() { // It is not safe to use TLS once TLS has been destroyed. if (base::ThreadLocalStorage::HasBeenDestroyed()) return nullptr; DCHECK(!this_thread_tracker_.Get()); PersistentMemoryAllocator::Reference mem_reference; { base::AutoLock autolock(thread_tracker_allocator_lock_); mem_reference = thread_tracker_allocator_.GetObjectReference(); } if (!mem_reference) { // Failure. This shouldn't happen. But be graceful if it does, probably // because the underlying allocator wasn't given enough memory to satisfy // to all possible requests. NOTREACHED(); // Return null, just as if tracking wasn't enabled. return nullptr; } // Convert the memory block found above into an actual memory address. // Doing the conversion as a Header object enacts the 32/64-bit size // consistency checks which would not otherwise be done. DCHECK(mem_reference); void* mem_base; mem_base = allocator_->GetAsObject(mem_reference); DCHECK(mem_base); DCHECK_LE(stack_memory_size_, allocator_->GetAllocSize(mem_reference)); // Create a tracker with the acquired memory and set it as the tracker // for this particular thread in thread-local-storage. auto tracker = std::make_unique( mem_reference, mem_base, stack_memory_size_); DCHECK(tracker->IsValid()); auto* tracker_raw = tracker.get(); this_thread_tracker_.Set(std::move(tracker)); thread_tracker_count_.fetch_add(1, std::memory_order_relaxed); return tracker_raw; } void GlobalActivityTracker::ReleaseTrackerForCurrentThreadForTesting() { if (this_thread_tracker_.Get()) this_thread_tracker_.Set(nullptr); } void GlobalActivityTracker::SetBackgroundTaskRunner( const scoped_refptr& runner) { AutoLock lock(global_tracker_lock_); background_task_runner_ = std::move(runner); } void GlobalActivityTracker::SetProcessExitCallback( ProcessExitCallback callback) { AutoLock lock(global_tracker_lock_); process_exit_callback_ = callback; } void GlobalActivityTracker::RecordProcessLaunch( ProcessId process_id, const FilePath::StringType& cmd) { DCHECK_NE(GetProcessId(), process_id); DCHECK_NE(ProcessId{0}, process_id); base::AutoLock lock(global_tracker_lock_); if (base::Contains(known_processes_, process_id)) { NOTREACHED() << "Process #" << process_id << " was previously recorded as \"launched\"" << " with no corresponding exit.\n" << known_processes_[process_id]; known_processes_.erase(process_id); } #if BUILDFLAG(IS_WIN) known_processes_.insert(std::make_pair(process_id, WideToUTF8(cmd))); #else known_processes_.insert(std::make_pair(process_id, cmd)); #endif } void GlobalActivityTracker::RecordProcessLaunch( ProcessId process_id, const FilePath::StringType& exe, const FilePath::StringType& args) { if (exe.find(FILE_PATH_LITERAL(" "))) { RecordProcessLaunch(process_id, FilePath::StringType(FILE_PATH_LITERAL("\"")) + exe + FILE_PATH_LITERAL("\" ") + args); } else { RecordProcessLaunch(process_id, exe + FILE_PATH_LITERAL(' ') + args); } } void GlobalActivityTracker::RecordProcessExit(ProcessId process_id, int exit_code) { DCHECK_NE(GetProcessId(), process_id); DCHECK_NE(ProcessId{0}, process_id); scoped_refptr task_runner; std::string command_line; { base::AutoLock lock(global_tracker_lock_); task_runner = background_task_runner_; auto found = known_processes_.find(process_id); if (found != known_processes_.end()) { command_line = std::move(found->second); known_processes_.erase(found); } else { DLOG(ERROR) << "Recording exit of unknown process #" << process_id; } } // Use the current time to differentiate the process that just exited // from any that might be created in the future with the same ID. int64_t now_stamp = Time::Now().ToInternalValue(); // The persistent allocator is thread-safe so run the iteration and // adjustments on a worker thread if one was provided. if (task_runner && !task_runner->RunsTasksInCurrentSequence()) { task_runner->PostTask( FROM_HERE, BindOnce(&GlobalActivityTracker::CleanupAfterProcess, Unretained(this), process_id, now_stamp, exit_code, std::move(command_line))); return; } CleanupAfterProcess(process_id, now_stamp, exit_code, std::move(command_line)); } void GlobalActivityTracker::SetProcessPhase(ProcessPhase phase) { process_data().SetInt(kProcessPhaseDataKey, phase); } void GlobalActivityTracker::CleanupAfterProcess(ProcessId process_id, int64_t exit_stamp, int exit_code, std::string&& command_line) { // The process may not have exited cleanly so its necessary to go through // all the data structures it may have allocated in the persistent memory // segment and mark them as "released". This will allow them to be reused // later on. PersistentMemoryAllocator::Iterator iter(allocator_.get()); PersistentMemoryAllocator::Reference ref; ProcessExitCallback process_exit_callback; { AutoLock lock(global_tracker_lock_); process_exit_callback = process_exit_callback_; } if (process_exit_callback) { // Find the processes user-data record so the process phase can be passed // to the callback. ActivityUserData::Snapshot process_data_snapshot; while ((ref = iter.GetNextOfType(kTypeIdProcessDataRecord)) != 0) { const void* memory = allocator_->GetAsArray( ref, kTypeIdProcessDataRecord, PersistentMemoryAllocator::kSizeAny); if (!memory) continue; ProcessId found_id; int64_t create_stamp; if (ActivityUserData::GetOwningProcessId(memory, &found_id, &create_stamp)) { if (found_id == process_id && create_stamp < exit_stamp) { const ActivityUserData process_data(const_cast(memory), allocator_->GetAllocSize(ref)); process_data.CreateSnapshot(&process_data_snapshot); break; // No need to look for any others. } } } iter.Reset(); // So it starts anew when used below. // Record the process's phase at exit so callback doesn't need to go // searching based on a private key value. ProcessPhase exit_phase = PROCESS_PHASE_UNKNOWN; auto phase = process_data_snapshot.find(kProcessPhaseDataKey); if (phase != process_data_snapshot.end()) exit_phase = static_cast(phase->second.GetInt()); // Perform the callback. process_exit_callback.Run(process_id, exit_stamp, exit_code, exit_phase, std::move(command_line), std::move(process_data_snapshot)); } // Find all allocations associated with the exited process and free them. uint32_t type; while ((ref = iter.GetNext(&type)) != 0) { switch (type) { case kTypeIdActivityTracker: case kTypeIdUserDataRecord: case kTypeIdProcessDataRecord: case ModuleInfoRecord::kPersistentTypeId: { const void* memory = allocator_->GetAsArray( ref, type, PersistentMemoryAllocator::kSizeAny); if (!memory) continue; ProcessId found_id; int64_t create_stamp; // By convention, the OwningProcess structure is always the first // field of the structure so there's no need to handle all the // cases separately. if (OwningProcess::GetOwningProcessId(memory, &found_id, &create_stamp)) { // Only change the type to be "free" if the process ID matches and // the creation time is before the exit time (so PID re-use doesn't // cause the erasure of something that is in-use). Memory is cleared // here, rather than when it's needed, so as to limit the impact at // that critical time. if (found_id == process_id && create_stamp < exit_stamp) allocator_->ChangeType(ref, ~type, type, /*clear=*/true); } } break; } } } void GlobalActivityTracker::RecordLogMessage(StringPiece message) { // Allocate at least one extra byte so the string is NUL terminated. All // memory returned by the allocator is guaranteed to be zeroed. PersistentMemoryAllocator::Reference ref = allocator_->Allocate(message.size() + 1, kTypeIdGlobalLogMessage); char* memory = allocator_->GetAsArray(ref, kTypeIdGlobalLogMessage, message.size() + 1); if (memory) { memcpy(memory, message.data(), message.size()); allocator_->MakeIterable(ref); } } void GlobalActivityTracker::RecordModuleInfo(const ModuleInfo& info) { AutoLock lock(modules_lock_); auto found = modules_.find(info.file); if (found != modules_.end()) { ModuleInfoRecord* record = found->second; DCHECK(record); // Update the basic state of module information that has been already // recorded. It is assumed that the string information (identifier, // version, etc.) remain unchanged which means that there's no need // to create a new record to accommodate a possibly longer length. record->UpdateFrom(info); return; } ModuleInfoRecord* record = ModuleInfoRecord::CreateFrom(info, allocator_.get()); if (!record) return; allocator_->MakeIterable(record); modules_.emplace(info.file, record); } void GlobalActivityTracker::RecordException(const void* pc, const void* origin, uint32_t code) { RecordExceptionImpl(pc, origin, code); } void GlobalActivityTracker::MarkDeleted() { allocator_->SetMemoryState(PersistentMemoryAllocator::MEMORY_DELETED); } GlobalActivityTracker::GlobalActivityTracker( std::unique_ptr allocator, int stack_depth, ProcessId process_id) : allocator_(std::move(allocator)), stack_memory_size_(ThreadActivityTracker::SizeForStackDepth(stack_depth)), process_id_(process_id == 0 ? GetCurrentProcId() : process_id), thread_tracker_count_(0), thread_tracker_allocator_(allocator_.get(), kTypeIdActivityTracker, kTypeIdActivityTrackerFree, stack_memory_size_, kCachedThreadMemories, /*make_iterable=*/true), user_data_allocator_(allocator_.get(), kTypeIdUserDataRecord, kTypeIdUserDataRecordFree, kUserDataSize, kCachedUserDataMemories, /*make_iterable=*/true), process_data_(allocator_->GetAsArray( AllocateFrom(allocator_.get(), kTypeIdProcessDataRecordFree, kProcessDataSize, kTypeIdProcessDataRecord), kTypeIdProcessDataRecord, kProcessDataSize), kProcessDataSize, process_id_) { DCHECK_NE(ProcessId{0}, process_id_); // Ensure that there is no other global object and then make this one such. DCHECK(!g_tracker_.load(std::memory_order_relaxed)); g_tracker_.store(this, std::memory_order_release); // The data records must be iterable in order to be found by an analyzer. allocator_->MakeIterable(allocator_->GetAsReference( process_data_.GetBaseAddress(), kTypeIdProcessDataRecord)); // Note that this process has launched. SetProcessPhase(PROCESS_LAUNCHED); } GlobalActivityTracker::~GlobalActivityTracker() { DCHECK(Get() == nullptr || Get() == this); DCHECK_EQ(0, thread_tracker_count_.load(std::memory_order_relaxed)); g_tracker_.store(nullptr, std::memory_order_release); } void GlobalActivityTracker::ReturnTrackerMemory( ManagedActivityTracker* tracker) { PersistentMemoryAllocator::Reference mem_reference = tracker->mem_reference_; void* mem_base = tracker->mem_base_; DCHECK(mem_reference); DCHECK(mem_base); // Remove the destructed tracker from the set of known ones. DCHECK_LE(1, thread_tracker_count_.load(std::memory_order_relaxed)); thread_tracker_count_.fetch_sub(1, std::memory_order_relaxed); // Release this memory for re-use at a later time. base::AutoLock autolock(thread_tracker_allocator_lock_); thread_tracker_allocator_.ReleaseObjectReference(mem_reference); } void GlobalActivityTracker::RecordExceptionImpl(const void* pc, const void* origin, uint32_t code) { // Get an existing tracker for this thread. It's not possible to create // one at this point because such would involve memory allocations and // other potentially complex operations that can cause failures if done // within an exception handler. In most cases various operations will // have already created the tracker so this shouldn't generally be a // problem. ThreadActivityTracker* tracker = GetTrackerForCurrentThread(); if (!tracker) return; tracker->RecordExceptionActivity(pc, origin, Activity::ACT_EXCEPTION, ActivityData::ForException(code)); } ScopedActivity::ScopedActivity(const void* program_counter, uint8_t action, uint32_t id, int32_t info) : GlobalActivityTracker::ScopedThreadActivity( program_counter, nullptr, static_cast(Activity::ACT_GENERIC | action), ActivityData::ForGeneric(id, info), /*lock_allowed=*/true), id_(id) { // The action must not affect the category bits of the activity type. DCHECK_EQ(0, action & Activity::ACT_CATEGORY_MASK); } void ScopedActivity::ChangeAction(uint8_t action) { DCHECK_EQ(0, action & Activity::ACT_CATEGORY_MASK); ChangeTypeAndData(static_cast(Activity::ACT_GENERIC | action), kNullActivityData); } void ScopedActivity::ChangeInfo(int32_t info) { ChangeTypeAndData(Activity::ACT_NULL, ActivityData::ForGeneric(id_, info)); } void ScopedActivity::ChangeActionAndInfo(uint8_t action, int32_t info) { DCHECK_EQ(0, action & Activity::ACT_CATEGORY_MASK); ChangeTypeAndData(static_cast(Activity::ACT_GENERIC | action), ActivityData::ForGeneric(id_, info)); } ScopedTaskRunActivity::ScopedTaskRunActivity(const void* program_counter, const base::PendingTask& task) : GlobalActivityTracker::ScopedThreadActivity( program_counter, task.posted_from.program_counter(), Activity::ACT_TASK_RUN, ActivityData::ForTask(static_cast(task.sequence_num)), /*lock_allowed=*/true) {} ScopedLockAcquireActivity::ScopedLockAcquireActivity( const void* program_counter, const base::internal::LockImpl* lock) : GlobalActivityTracker::ScopedThreadActivity( program_counter, nullptr, Activity::ACT_LOCK_ACQUIRE, ActivityData::ForLock(lock), /*lock_allowed=*/false) {} ScopedEventWaitActivity::ScopedEventWaitActivity( const void* program_counter, const base::WaitableEvent* event) : GlobalActivityTracker::ScopedThreadActivity( program_counter, nullptr, Activity::ACT_EVENT_WAIT, ActivityData::ForEvent(event), /*lock_allowed=*/true) {} ScopedThreadJoinActivity::ScopedThreadJoinActivity( const void* program_counter, const base::PlatformThreadHandle* thread) : GlobalActivityTracker::ScopedThreadActivity( program_counter, nullptr, Activity::ACT_THREAD_JOIN, ActivityData::ForThread(*thread), /*lock_allowed=*/true) {} #if !BUILDFLAG(IS_NACL) && !BUILDFLAG(IS_IOS) ScopedProcessWaitActivity::ScopedProcessWaitActivity( const void* program_counter, const base::Process* process) : GlobalActivityTracker::ScopedThreadActivity( program_counter, nullptr, Activity::ACT_PROCESS_WAIT, ActivityData::ForProcess(process->Pid()), /*lock_allowed=*/true) {} #endif } // namespace debug } // namespace base