| // 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 "components/browser_watcher/extended_crash_reporting.h" |
| |
| #include <windows.h> |
| |
| #include <memory> |
| |
| #include "base/debug/activity_tracker.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/persistent_memory_allocator.h" |
| #include "base/win/pe_image.h" |
| #include "build/build_config.h" |
| #include "components/browser_watcher/activity_data_names.h" |
| #include "components/browser_watcher/activity_report.pb.h" |
| #include "components/browser_watcher/activity_tracker_annotation.h" |
| #include "components/browser_watcher/extended_crash_reporting.h" |
| #include "components/browser_watcher/extended_crash_reporting_metrics.h" |
| #include "components/browser_watcher/features.h" |
| |
| #if defined(OS_WIN) |
| // https://devblogs.microsoft.com/oldnewthing/20041025-00/?p=37483. |
| extern "C" IMAGE_DOS_HEADER __ImageBase; |
| #endif |
| |
| namespace browser_watcher { |
| |
| namespace { |
| |
| ExtendedCrashReporting* g_instance = nullptr; |
| |
| uintptr_t GetProgramCounter(const CONTEXT& context) { |
| #if defined(ARCH_CPU_X86) |
| return context.Eip; |
| #elif defined(ARCH_CPU_X86_64) |
| return context.Rip; |
| #elif defined(ARCH_CPU_ARM64) |
| return context.Pc; |
| #endif |
| } |
| |
| LONG CALLBACK VectoredExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { |
| base::debug::GlobalActivityTracker* tracker = |
| base::debug::GlobalActivityTracker::Get(); |
| if (tracker) { |
| EXCEPTION_RECORD* record = exception_pointers->ExceptionRecord; |
| uintptr_t pc = GetProgramCounter(*exception_pointers->ContextRecord); |
| tracker->RecordException(reinterpret_cast<void*>(pc), |
| record->ExceptionAddress, record->ExceptionCode); |
| } |
| |
| return EXCEPTION_CONTINUE_SEARCH; // Continue to the next handler. |
| } |
| |
| // Record information about the chrome module. |
| void RecordChromeModuleInfo( |
| base::debug::GlobalActivityTracker* global_tracker) { |
| DCHECK(global_tracker); |
| |
| base::debug::GlobalActivityTracker::ModuleInfo module; |
| module.is_loaded = true; |
| module.address = reinterpret_cast<uintptr_t>(&__ImageBase); |
| |
| base::win::PEImage pe(&__ImageBase); |
| PIMAGE_NT_HEADERS headers = pe.GetNTHeaders(); |
| CHECK(headers); |
| module.size = headers->OptionalHeader.SizeOfImage; |
| module.timestamp = headers->FileHeader.TimeDateStamp; |
| |
| GUID guid; |
| DWORD age; |
| LPCSTR pdb_filename = nullptr; |
| size_t pdb_filename_length = 0; |
| if (pe.GetDebugId(&guid, &age, &pdb_filename, &pdb_filename_length)) { |
| module.age = age; |
| static_assert(sizeof(module.identifier) >= sizeof(guid), |
| "Identifier field must be able to contain a GUID."); |
| memcpy(module.identifier, &guid, sizeof(guid)); |
| } else { |
| memset(module.identifier, 0, sizeof(module.identifier)); |
| } |
| |
| module.file = "chrome.dll"; |
| module.debug_file = |
| base::StringPiece(pdb_filename, pdb_filename_length).as_string(); |
| |
| global_tracker->RecordModuleInfo(module); |
| } |
| |
| } // namespace |
| |
| ExtendedCrashReporting::ExtendedCrashReporting( |
| base::debug::GlobalActivityTracker* tracker) |
| : tracker_(tracker) {} |
| |
| ExtendedCrashReporting::~ExtendedCrashReporting() { |
| if (veh_handle_) |
| ::RemoveVectoredExceptionHandler(veh_handle_); |
| } |
| |
| ExtendedCrashReporting* ExtendedCrashReporting::SetUpIfEnabled( |
| ProcessType process_type) { |
| DCHECK_EQ(nullptr, g_instance); |
| if (!base::FeatureList::IsEnabled(kExtendedCrashReportingFeature)) { |
| return nullptr; |
| } |
| |
| return SetUpImpl(process_type); |
| } |
| |
| ExtendedCrashReporting* ExtendedCrashReporting::GetInstance() { |
| return g_instance; |
| } |
| |
| void ExtendedCrashReporting::SetProductStrings( |
| const base::string16& product_name, |
| const base::string16& product_version, |
| const base::string16& channel_name, |
| const base::string16& special_build) { |
| base::debug::ActivityUserData& proc_data = tracker_->process_data(); |
| proc_data.SetString(kActivityProduct, product_name); |
| proc_data.SetString(kActivityVersion, product_version); |
| proc_data.SetString(kActivityChannel, channel_name); |
| proc_data.SetString(kActivitySpecialBuild, special_build); |
| } |
| |
| void ExtendedCrashReporting::SetBool(base::StringPiece name, bool value) { |
| tracker_->process_data().SetBool(name, value); |
| } |
| |
| void ExtendedCrashReporting::SetInt(base::StringPiece name, int64_t value) { |
| tracker_->process_data().SetInt(name, value); |
| } |
| |
| void ExtendedCrashReporting::SetDataBool(base::StringPiece name, bool value) { |
| if (g_instance) |
| g_instance->SetBool(name, value); |
| } |
| |
| void ExtendedCrashReporting::SetDataInt(base::StringPiece name, int64_t value) { |
| if (g_instance) |
| g_instance->SetInt(name, value); |
| } |
| |
| void ExtendedCrashReporting::RegisterVEH() { |
| #if defined(ADDRESS_SANITIZER) |
| // ASAN on windows x64 is dynamically allocating the shadow memory on a |
| // memory access violation by setting up an vector exception handler. |
| // When instrumented with ASAN, this code may trigger an exception by |
| // accessing unallocated shadow memory, which is causing an infinite |
| // recursion (i.e. infinite memory access violation). |
| (void)&VectoredExceptionHandler; |
| #else |
| DCHECK_EQ(nullptr, veh_handle_); |
| // Register a vectored exception handler and request it be first. Note that |
| // subsequent registrations may also request to be first, in which case this |
| // one will be bumped. |
| // TODO(manzagop): Depending on observations, it may be necessary to |
| // consider refreshing the registration, either periodically or at opportune |
| // (e.g. risky) times. |
| veh_handle_ = ::AddVectoredExceptionHandler(1, &VectoredExceptionHandler); |
| DCHECK(veh_handle_); |
| #endif // ADDRESS_SANITIZER |
| } |
| |
| void ExtendedCrashReporting::SetUpForTesting() { |
| ExtendedCrashReporting::SetUpImpl(kBrowserProcess); |
| } |
| |
| void ExtendedCrashReporting::TearDownForTesting() { |
| if (g_instance) { |
| ExtendedCrashReporting* instance_to_delete = g_instance; |
| g_instance = nullptr; |
| delete instance_to_delete; |
| } |
| |
| // Clear the crash annotation. |
| ActivityTrackerAnnotation::GetInstance()->Clear(); |
| } |
| |
| ExtendedCrashReporting* ExtendedCrashReporting::SetUpImpl( |
| ProcessType process_type) { |
| DCHECK_EQ(nullptr, g_instance); |
| |
| // TODO(https://crbug.com/1044707): Adjust these numbers once there is real |
| // data to show just how much of an arena is necessary. |
| const size_t kMemorySize = 1 << 20; // 1 MiB |
| const int kStackDepth = 4; |
| const uint64_t kAllocatorId = 0; |
| |
| base::debug::GlobalActivityTracker::CreateWithAllocator( |
| std::make_unique<base::LocalPersistentMemoryAllocator>( |
| kMemorySize, kAllocatorId, kExtendedCrashReportingFeature.name), |
| kStackDepth, 0); |
| |
| // Track code activities (such as posting task, blocking on locks, and |
| // joining threads) that can cause hanging threads and general instability |
| base::debug::GlobalActivityTracker* global_tracker = |
| base::debug::GlobalActivityTracker::Get(); |
| DCHECK(global_tracker); |
| |
| // Construct the instance with the new global tracker, this object is |
| // intentionally leaked. |
| std::unique_ptr<ExtendedCrashReporting> new_instance = |
| base::WrapUnique(new ExtendedCrashReporting(global_tracker)); |
| new_instance->Initialize(process_type); |
| g_instance = new_instance.release(); |
| return g_instance; |
| } |
| |
| void ExtendedCrashReporting::Initialize(ProcessType process_type) { |
| // Record the location and size of the tracker memory range in a Crashpad |
| // annotation to allow the handler to retrieve it on crash. |
| // Record the buffer size and location for the annotation beacon. |
| auto* allocator = tracker_->allocator(); |
| ActivityTrackerAnnotation::GetInstance()->SetValue(allocator->data(), |
| allocator->size()); |
| |
| // Record the main DLL module info for easier symbolization. |
| RecordChromeModuleInfo(tracker_); |
| |
| LogActivityRecordEvent(ActivityRecordEvent::kGotTracker); |
| |
| base::debug::ActivityUserData& proc_data = tracker_->process_data(); |
| #if defined(ARCH_CPU_X86) |
| proc_data.SetString(kActivityPlatform, "Win32"); |
| #elif defined(ARCH_CPU_X86_64) |
| proc_data.SetString(kActivityPlatform, "Win64"); |
| #endif |
| proc_data.SetInt( |
| kActivityStartTimestamp, |
| base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds()); |
| |
| if (process_type == kBrowserProcess) |
| proc_data.SetInt(kActivityProcessType, ProcessState::BROWSER_PROCESS); |
| |
| RegisterVEH(); |
| } |
| |
| } // namespace browser_watcher |