blob: fb99c076e42cbe961c378d20d21c82fa9b4689a8 [file] [log] [blame]
// Copyright 2017 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/activity_report_user_stream_data_source.h"
#include <string>
#include <utility>
#include <windows.h>
// Must be included after windows.h.
#include <psapi.h>
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/memory/free_deleter.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/persistent_memory_allocator.h"
#include "base/process/memory.h"
#include "base/process/process.h"
#include "base/strings/string16.h"
#include "base/time/time.h"
#include "components/browser_watcher/activity_report_extractor.h"
#include "components/browser_watcher/activity_tracker_annotation.h"
#include "components/browser_watcher/extended_crash_reporting_metrics.h"
#include "components/browser_watcher/minidump_user_streams.h"
#include "third_party/crashpad/crashpad/minidump/minidump_user_extension_stream_data_source.h"
#include "third_party/crashpad/crashpad/snapshot/annotation_snapshot.h"
#include "third_party/crashpad/crashpad/snapshot/exception_snapshot.h"
#include "third_party/crashpad/crashpad/snapshot/module_snapshot.h"
#include "third_party/crashpad/crashpad/snapshot/process_snapshot.h"
#include "third_party/crashpad/crashpad/util/process/process_memory.h"
namespace browser_watcher {
namespace {
// TODO(siggi): Refactor this to harmonize with the activity tracker setup.
const size_t kMaxActivityAnnotationSize = 2 << 20;
using UniqueMallocPtr = std::unique_ptr<void, base::FreeDeleter>;
UniqueMallocPtr UncheckedAllocate(size_t size) {
void* raw_ptr = nullptr;
if (!base::UncheckedMalloc(size, &raw_ptr))
return UniqueMallocPtr();
return UniqueMallocPtr(raw_ptr);
}
// A PersistentMemoryAllocator subclass that can take ownership of a buffer
// that's allocated with a malloc-compatible allocation function.
class MallocMemoryAllocator : public base::PersistentMemoryAllocator {
public:
MallocMemoryAllocator(UniqueMallocPtr buffer, size_t size);
~MallocMemoryAllocator() override;
};
MallocMemoryAllocator::MallocMemoryAllocator(UniqueMallocPtr buffer,
size_t size)
: base::PersistentMemoryAllocator(buffer.release(), size, 0, 0, "", true) {}
MallocMemoryAllocator::~MallocMemoryAllocator() {
free(const_cast<char*>(mem_base_));
}
class BufferExtensionStreamDataSource final
: public crashpad::MinidumpUserExtensionStreamDataSource {
public:
explicit BufferExtensionStreamDataSource(uint32_t stream_type);
bool Init(const StabilityReport& report);
size_t StreamDataSize() override;
bool ReadStreamData(Delegate* delegate) override;
private:
std::string data_;
DISALLOW_COPY_AND_ASSIGN(BufferExtensionStreamDataSource);
};
BufferExtensionStreamDataSource::BufferExtensionStreamDataSource(
uint32_t stream_type)
: crashpad::MinidumpUserExtensionStreamDataSource(stream_type) {}
bool BufferExtensionStreamDataSource::Init(const StabilityReport& report) {
if (report.SerializeToString(&data_))
return true;
data_.clear();
return false;
}
size_t BufferExtensionStreamDataSource::StreamDataSize() {
DCHECK(!data_.empty());
return data_.size();
}
bool BufferExtensionStreamDataSource::ReadStreamData(Delegate* delegate) {
DCHECK(!data_.empty());
return delegate->ExtensionStreamDataSourceRead(
data_.size() ? data_.data() : nullptr, data_.size());
}
// TODO(manzagop): Collection should factor in whether this is a true crash or
// dump without crashing.
bool CollectStabilityReport(
std::unique_ptr<base::debug::GlobalActivityAnalyzer> global_analyzer,
StabilityReport* report) {
CollectionStatus status = ANALYZER_CREATION_FAILED;
if (global_analyzer)
status = Extract(std::move(global_analyzer), report);
base::UmaHistogramEnumeration("ActivityTracker.CollectCrash.Status", status,
COLLECTION_STATUS_MAX);
if (status != SUCCESS)
return false;
LogCollectOnCrashEvent(CollectOnCrashEvent::kReportExtractionSuccess);
return true;
}
void CollectSystemPerformanceMetrics(StabilityReport* report) {
// Grab system commit memory. Also best effort.
PERFORMANCE_INFORMATION perf_info = {sizeof(perf_info)};
if (GetPerformanceInfo(&perf_info, sizeof(perf_info))) {
auto* memory_state =
report->mutable_system_memory_state()->mutable_windows_memory();
memory_state->set_system_commit_limit(perf_info.CommitLimit);
memory_state->set_system_commit_remaining(perf_info.CommitLimit -
perf_info.CommitTotal);
memory_state->set_system_handle_count(perf_info.HandleCount);
}
}
void CollectProcessPerformanceMetrics(
crashpad::ProcessSnapshot* process_snapshot,
StabilityReport* report) {
const crashpad::ExceptionSnapshot* exception = process_snapshot->Exception();
if (!exception)
return;
// Find or create the ProcessState for the process in question.
base::ProcessId pid = process_snapshot->ProcessID();
ProcessState* process_state = nullptr;
for (int i = 0; i < report->process_states_size(); ++i) {
ProcessState* temp = report->mutable_process_states(i);
if (temp->has_process_id() && temp->process_id() == pid) {
process_state = temp;
break;
}
}
if (!process_state) {
process_state = report->add_process_states();
process_state->set_process_id(pid);
}
auto* memory_state =
process_state->mutable_memory_state()->mutable_windows_memory();
// Grab the requested allocation size in case of OOM exception.
if (exception->Exception() == base::win::kOomExceptionCode) {
const auto& codes = exception->Codes();
if (codes.size()) {
// The first parameter, if present, is the size of the allocation attempt.
memory_state->set_process_allocation_attempt(codes[0]);
}
}
base::Process process(base::Process::OpenWithAccess(
pid, PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ));
if (process.IsValid()) {
PROCESS_MEMORY_COUNTERS_EX process_memory = {sizeof(process_memory)};
if (::GetProcessMemoryInfo(
process.Handle(),
reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&process_memory),
sizeof(process_memory))) {
// This is in units of bytes, re-scale to pages for consistency with
// system metrics.
const uint64_t kPageSize = 4096;
memory_state->set_process_private_usage(process_memory.PrivateUsage /
kPageSize);
memory_state->set_process_peak_workingset_size(
process_memory.PeakWorkingSetSize / kPageSize);
memory_state->set_process_peak_pagefile_usage(
process_memory.PeakPagefileUsage / kPageSize);
}
DWORD process_handle_count = 0;
if (::GetProcessHandleCount(process.Handle(), &process_handle_count)) {
memory_state->set_process_handle_count(process_handle_count);
}
}
}
// If the process has a beacon for in-memory activities, returns an analyzer
// for it.
std::unique_ptr<base::debug::GlobalActivityAnalyzer>
MaybeGetInMemoryActivityAnalyzer(crashpad::ProcessSnapshot* process_snapshot) {
if (!process_snapshot->Memory())
return nullptr;
auto modules = process_snapshot->Modules();
for (auto* module : modules) {
auto annotations = module->AnnotationObjects();
for (const auto& annotation : annotations) {
if (annotation.name == ActivityTrackerAnnotation::kAnnotationName &&
annotation.type == static_cast<uint16_t>(
ActivityTrackerAnnotation::kAnnotationType) &&
annotation.value.size() ==
sizeof(ActivityTrackerAnnotation::ValueType)) {
// Re-cast the annotation to its value type.
ActivityTrackerAnnotation::ValueType value;
memcpy(&value, annotation.value.data(), sizeof(value));
// Check the size field for sanity.
if (value.size > kMaxActivityAnnotationSize)
continue;
// Allocate the buffer with no terminate-on-exhaustion to make sure
// this can't be used to bring down the handler and thus elide
// crash reporting.
UniqueMallocPtr buffer = UncheckedAllocate(value.size);
if (!buffer || !base::PersistentMemoryAllocator::IsMemoryAcceptable(
buffer.get(), value.size, 0, true)) {
continue;
}
// Read the activity tracker data from the crashed process.
if (process_snapshot->Memory()->Read(value.address, value.size,
buffer.get())) {
// Success - wrap an allocator on the buffer, and an analyzer on that.
std::unique_ptr<MallocMemoryAllocator> allocator =
std::make_unique<MallocMemoryAllocator>(std::move(buffer),
value.size);
return base::debug::GlobalActivityAnalyzer::CreateWithAllocator(
std::move(allocator));
} else {
return nullptr;
}
}
}
}
return nullptr;
}
} // namespace
ActivityReportUserStreamDataSource::ActivityReportUserStreamDataSource(
const base::FilePath& user_data_dir)
: user_data_dir_(user_data_dir) {}
std::unique_ptr<crashpad::MinidumpUserExtensionStreamDataSource>
ActivityReportUserStreamDataSource::ProduceStreamData(
crashpad::ProcessSnapshot* process_snapshot) {
DCHECK(process_snapshot);
LogCollectOnCrashEvent(CollectOnCrashEvent::kCollectAttempt);
StabilityReport report;
// See whether there's an activity tracking report beacon in the process'
// annotations.
std::unique_ptr<base::debug::GlobalActivityAnalyzer> global_analyzer =
MaybeGetInMemoryActivityAnalyzer(process_snapshot);
bool collected_report = false;
if (global_analyzer) {
LogCollectOnCrashEvent(CollectOnCrashEvent::kInMemoryAnnotationExists);
collected_report =
CollectStabilityReport(std::move(global_analyzer), &report);
}
CollectSystemPerformanceMetrics(&report);
CollectProcessPerformanceMetrics(process_snapshot, &report);
std::unique_ptr<BufferExtensionStreamDataSource> source(
new BufferExtensionStreamDataSource(kActivityReportStreamType));
if (!source->Init(report))
return nullptr;
if (collected_report)
LogCollectOnCrashEvent(CollectOnCrashEvent::kSuccess);
return source;
}
} // namespace browser_watcher