mirror of
https://github.com/klzgrad/naiveproxy.git
synced 2026-08-18 06:55:34 +03:00
Import chromium-64.0.3282.140
This commit is contained in:
2
base/debug/OWNERS
Normal file
2
base/debug/OWNERS
Normal file
@@ -0,0 +1,2 @@
|
||||
# For activity tracking:
|
||||
per-file activity_*=bcwhite@chromium.org
|
||||
412
base/debug/activity_analyzer.cc
Normal file
412
base/debug/activity_analyzer.cc
Normal file
@@ -0,0 +1,412 @@
|
||||
// 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_analyzer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/memory_mapped_file.h"
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/metrics/histogram_macros.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "base/strings/string_util.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
// An empty snapshot that can be returned when there otherwise is none.
|
||||
LazyInstance<ActivityUserData::Snapshot>::Leaky g_empty_user_data_snapshot;
|
||||
|
||||
// DO NOT CHANGE VALUES. This is logged persistently in a histogram.
|
||||
enum AnalyzerCreationError {
|
||||
kInvalidMemoryMappedFile,
|
||||
kPmaBadFile,
|
||||
kPmaUninitialized,
|
||||
kPmaDeleted,
|
||||
kPmaCorrupt,
|
||||
kAnalyzerCreationErrorMax // Keep this last.
|
||||
};
|
||||
|
||||
void LogAnalyzerCreationError(AnalyzerCreationError error) {
|
||||
UMA_HISTOGRAM_ENUMERATION("ActivityTracker.Collect.AnalyzerCreationError",
|
||||
error, kAnalyzerCreationErrorMax);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ThreadActivityAnalyzer::Snapshot::Snapshot() = default;
|
||||
ThreadActivityAnalyzer::Snapshot::~Snapshot() = default;
|
||||
|
||||
ThreadActivityAnalyzer::ThreadActivityAnalyzer(
|
||||
const ThreadActivityTracker& tracker)
|
||||
: activity_snapshot_valid_(tracker.CreateSnapshot(&activity_snapshot_)) {}
|
||||
|
||||
ThreadActivityAnalyzer::ThreadActivityAnalyzer(void* base, size_t size)
|
||||
: ThreadActivityAnalyzer(ThreadActivityTracker(base, size)) {}
|
||||
|
||||
ThreadActivityAnalyzer::ThreadActivityAnalyzer(
|
||||
PersistentMemoryAllocator* allocator,
|
||||
PersistentMemoryAllocator::Reference reference)
|
||||
: ThreadActivityAnalyzer(allocator->GetAsArray<char>(
|
||||
reference,
|
||||
GlobalActivityTracker::kTypeIdActivityTracker,
|
||||
PersistentMemoryAllocator::kSizeAny),
|
||||
allocator->GetAllocSize(reference)) {}
|
||||
|
||||
ThreadActivityAnalyzer::~ThreadActivityAnalyzer() = default;
|
||||
|
||||
void ThreadActivityAnalyzer::AddGlobalInformation(
|
||||
GlobalActivityAnalyzer* global) {
|
||||
if (!IsValid())
|
||||
return;
|
||||
|
||||
// User-data is held at the global scope even though it's referenced at the
|
||||
// thread scope.
|
||||
activity_snapshot_.user_data_stack.clear();
|
||||
for (auto& activity : activity_snapshot_.activity_stack) {
|
||||
// The global GetUserDataSnapshot will return an empty snapshot if the ref
|
||||
// or id is not valid.
|
||||
activity_snapshot_.user_data_stack.push_back(global->GetUserDataSnapshot(
|
||||
activity_snapshot_.process_id, activity.user_data_ref,
|
||||
activity.user_data_id));
|
||||
}
|
||||
}
|
||||
|
||||
GlobalActivityAnalyzer::GlobalActivityAnalyzer(
|
||||
std::unique_ptr<PersistentMemoryAllocator> allocator)
|
||||
: allocator_(std::move(allocator)),
|
||||
analysis_stamp_(0LL),
|
||||
allocator_iterator_(allocator_.get()) {
|
||||
DCHECK(allocator_);
|
||||
}
|
||||
|
||||
GlobalActivityAnalyzer::~GlobalActivityAnalyzer() = default;
|
||||
|
||||
// static
|
||||
std::unique_ptr<GlobalActivityAnalyzer>
|
||||
GlobalActivityAnalyzer::CreateWithAllocator(
|
||||
std::unique_ptr<PersistentMemoryAllocator> allocator) {
|
||||
if (allocator->GetMemoryState() ==
|
||||
PersistentMemoryAllocator::MEMORY_UNINITIALIZED) {
|
||||
LogAnalyzerCreationError(kPmaUninitialized);
|
||||
return nullptr;
|
||||
}
|
||||
if (allocator->GetMemoryState() ==
|
||||
PersistentMemoryAllocator::MEMORY_DELETED) {
|
||||
LogAnalyzerCreationError(kPmaDeleted);
|
||||
return nullptr;
|
||||
}
|
||||
if (allocator->IsCorrupt()) {
|
||||
LogAnalyzerCreationError(kPmaCorrupt);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return WrapUnique(new GlobalActivityAnalyzer(std::move(allocator)));
|
||||
}
|
||||
|
||||
#if !defined(OS_NACL)
|
||||
// static
|
||||
std::unique_ptr<GlobalActivityAnalyzer> GlobalActivityAnalyzer::CreateWithFile(
|
||||
const FilePath& file_path) {
|
||||
// Map the file read-write so it can guarantee consistency between
|
||||
// the analyzer and any trackers that my still be active.
|
||||
std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile());
|
||||
mmfile->Initialize(file_path, MemoryMappedFile::READ_WRITE);
|
||||
if (!mmfile->IsValid()) {
|
||||
LogAnalyzerCreationError(kInvalidMemoryMappedFile);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true)) {
|
||||
LogAnalyzerCreationError(kPmaBadFile);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return CreateWithAllocator(std::make_unique<FilePersistentMemoryAllocator>(
|
||||
std::move(mmfile), 0, 0, StringPiece(), /*readonly=*/true));
|
||||
}
|
||||
#endif // !defined(OS_NACL)
|
||||
|
||||
// static
|
||||
std::unique_ptr<GlobalActivityAnalyzer>
|
||||
GlobalActivityAnalyzer::CreateWithSharedMemory(
|
||||
std::unique_ptr<SharedMemory> shm) {
|
||||
if (shm->mapped_size() == 0 ||
|
||||
!SharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(*shm)) {
|
||||
return nullptr;
|
||||
}
|
||||
return CreateWithAllocator(std::make_unique<SharedPersistentMemoryAllocator>(
|
||||
std::move(shm), 0, StringPiece(), /*readonly=*/true));
|
||||
}
|
||||
|
||||
// static
|
||||
std::unique_ptr<GlobalActivityAnalyzer>
|
||||
GlobalActivityAnalyzer::CreateWithSharedMemoryHandle(
|
||||
const SharedMemoryHandle& handle,
|
||||
size_t size) {
|
||||
std::unique_ptr<SharedMemory> shm(
|
||||
new SharedMemory(handle, /*readonly=*/true));
|
||||
if (!shm->Map(size))
|
||||
return nullptr;
|
||||
return CreateWithSharedMemory(std::move(shm));
|
||||
}
|
||||
|
||||
int64_t GlobalActivityAnalyzer::GetFirstProcess() {
|
||||
PrepareAllAnalyzers();
|
||||
return GetNextProcess();
|
||||
}
|
||||
|
||||
int64_t GlobalActivityAnalyzer::GetNextProcess() {
|
||||
if (process_ids_.empty())
|
||||
return 0;
|
||||
int64_t pid = process_ids_.back();
|
||||
process_ids_.pop_back();
|
||||
return pid;
|
||||
}
|
||||
|
||||
ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetFirstAnalyzer(int64_t pid) {
|
||||
analyzers_iterator_ = analyzers_.begin();
|
||||
analyzers_iterator_pid_ = pid;
|
||||
if (analyzers_iterator_ == analyzers_.end())
|
||||
return nullptr;
|
||||
int64_t create_stamp;
|
||||
if (analyzers_iterator_->second->GetProcessId(&create_stamp) == pid &&
|
||||
create_stamp <= analysis_stamp_) {
|
||||
return analyzers_iterator_->second.get();
|
||||
}
|
||||
return GetNextAnalyzer();
|
||||
}
|
||||
|
||||
ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetNextAnalyzer() {
|
||||
DCHECK(analyzers_iterator_ != analyzers_.end());
|
||||
int64_t create_stamp;
|
||||
do {
|
||||
++analyzers_iterator_;
|
||||
if (analyzers_iterator_ == analyzers_.end())
|
||||
return nullptr;
|
||||
} while (analyzers_iterator_->second->GetProcessId(&create_stamp) !=
|
||||
analyzers_iterator_pid_ ||
|
||||
create_stamp > analysis_stamp_);
|
||||
return analyzers_iterator_->second.get();
|
||||
}
|
||||
|
||||
ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetAnalyzerForThread(
|
||||
const ThreadKey& key) {
|
||||
auto found = analyzers_.find(key);
|
||||
if (found == analyzers_.end())
|
||||
return nullptr;
|
||||
return found->second.get();
|
||||
}
|
||||
|
||||
ActivityUserData::Snapshot GlobalActivityAnalyzer::GetUserDataSnapshot(
|
||||
int64_t pid,
|
||||
uint32_t ref,
|
||||
uint32_t id) {
|
||||
ActivityUserData::Snapshot snapshot;
|
||||
|
||||
void* memory = allocator_->GetAsArray<char>(
|
||||
ref, GlobalActivityTracker::kTypeIdUserDataRecord,
|
||||
PersistentMemoryAllocator::kSizeAny);
|
||||
if (memory) {
|
||||
size_t size = allocator_->GetAllocSize(ref);
|
||||
const ActivityUserData user_data(memory, size);
|
||||
user_data.CreateSnapshot(&snapshot);
|
||||
int64_t process_id;
|
||||
int64_t create_stamp;
|
||||
if (!ActivityUserData::GetOwningProcessId(memory, &process_id,
|
||||
&create_stamp) ||
|
||||
process_id != pid || user_data.id() != id) {
|
||||
// This allocation has been overwritten since it was created. Return an
|
||||
// empty snapshot because whatever was captured is incorrect.
|
||||
snapshot.clear();
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
const ActivityUserData::Snapshot&
|
||||
GlobalActivityAnalyzer::GetProcessDataSnapshot(int64_t pid) {
|
||||
auto iter = process_data_.find(pid);
|
||||
if (iter == process_data_.end())
|
||||
return g_empty_user_data_snapshot.Get();
|
||||
if (iter->second.create_stamp > analysis_stamp_)
|
||||
return g_empty_user_data_snapshot.Get();
|
||||
DCHECK_EQ(pid, iter->second.process_id);
|
||||
return iter->second.data;
|
||||
}
|
||||
|
||||
std::vector<std::string> GlobalActivityAnalyzer::GetLogMessages() {
|
||||
std::vector<std::string> messages;
|
||||
PersistentMemoryAllocator::Reference ref;
|
||||
|
||||
PersistentMemoryAllocator::Iterator iter(allocator_.get());
|
||||
while ((ref = iter.GetNextOfType(
|
||||
GlobalActivityTracker::kTypeIdGlobalLogMessage)) != 0) {
|
||||
const char* message = allocator_->GetAsArray<char>(
|
||||
ref, GlobalActivityTracker::kTypeIdGlobalLogMessage,
|
||||
PersistentMemoryAllocator::kSizeAny);
|
||||
if (message)
|
||||
messages.push_back(message);
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
std::vector<GlobalActivityTracker::ModuleInfo>
|
||||
GlobalActivityAnalyzer::GetModules(int64_t pid) {
|
||||
std::vector<GlobalActivityTracker::ModuleInfo> modules;
|
||||
|
||||
PersistentMemoryAllocator::Iterator iter(allocator_.get());
|
||||
const GlobalActivityTracker::ModuleInfoRecord* record;
|
||||
while (
|
||||
(record =
|
||||
iter.GetNextOfObject<GlobalActivityTracker::ModuleInfoRecord>()) !=
|
||||
nullptr) {
|
||||
int64_t process_id;
|
||||
int64_t create_stamp;
|
||||
if (!OwningProcess::GetOwningProcessId(&record->owner, &process_id,
|
||||
&create_stamp) ||
|
||||
pid != process_id || create_stamp > analysis_stamp_) {
|
||||
continue;
|
||||
}
|
||||
GlobalActivityTracker::ModuleInfo info;
|
||||
if (record->DecodeTo(&info, allocator_->GetAllocSize(
|
||||
allocator_->GetAsReference(record)))) {
|
||||
modules.push_back(std::move(info));
|
||||
}
|
||||
}
|
||||
|
||||
return modules;
|
||||
}
|
||||
|
||||
GlobalActivityAnalyzer::ProgramLocation
|
||||
GlobalActivityAnalyzer::GetProgramLocationFromAddress(uint64_t address) {
|
||||
// TODO(bcwhite): Implement this.
|
||||
return { 0, 0 };
|
||||
}
|
||||
|
||||
bool GlobalActivityAnalyzer::IsDataComplete() const {
|
||||
DCHECK(allocator_);
|
||||
return !allocator_->IsFull();
|
||||
}
|
||||
|
||||
GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot() = default;
|
||||
GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot(
|
||||
const UserDataSnapshot& rhs) = default;
|
||||
GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot(
|
||||
UserDataSnapshot&& rhs) = default;
|
||||
GlobalActivityAnalyzer::UserDataSnapshot::~UserDataSnapshot() = default;
|
||||
|
||||
void GlobalActivityAnalyzer::PrepareAllAnalyzers() {
|
||||
// Record the time when analysis started.
|
||||
analysis_stamp_ = base::Time::Now().ToInternalValue();
|
||||
|
||||
// Fetch all the records. This will retrieve only ones created since the
|
||||
// last run since the PMA iterator will continue from where it left off.
|
||||
uint32_t type;
|
||||
PersistentMemoryAllocator::Reference ref;
|
||||
while ((ref = allocator_iterator_.GetNext(&type)) != 0) {
|
||||
switch (type) {
|
||||
case GlobalActivityTracker::kTypeIdActivityTracker:
|
||||
case GlobalActivityTracker::kTypeIdActivityTrackerFree:
|
||||
case GlobalActivityTracker::kTypeIdProcessDataRecord:
|
||||
case GlobalActivityTracker::kTypeIdProcessDataRecordFree:
|
||||
case PersistentMemoryAllocator::kTypeIdTransitioning:
|
||||
// Active, free, or transitioning: add it to the list of references
|
||||
// for later analysis.
|
||||
memory_references_.insert(ref);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear out any old information.
|
||||
analyzers_.clear();
|
||||
process_data_.clear();
|
||||
process_ids_.clear();
|
||||
std::set<int64_t> seen_pids;
|
||||
|
||||
// Go through all the known references and create objects for them with
|
||||
// snapshots of the current state.
|
||||
for (PersistentMemoryAllocator::Reference memory_ref : memory_references_) {
|
||||
// Get the actual data segment for the tracker. Any type will do since it
|
||||
// is checked below.
|
||||
void* const base = allocator_->GetAsArray<char>(
|
||||
memory_ref, PersistentMemoryAllocator::kTypeIdAny,
|
||||
PersistentMemoryAllocator::kSizeAny);
|
||||
const size_t size = allocator_->GetAllocSize(memory_ref);
|
||||
if (!base)
|
||||
continue;
|
||||
|
||||
switch (allocator_->GetType(memory_ref)) {
|
||||
case GlobalActivityTracker::kTypeIdActivityTracker: {
|
||||
// Create the analyzer on the data. This will capture a snapshot of the
|
||||
// tracker state. This can fail if the tracker is somehow corrupted or
|
||||
// is in the process of shutting down.
|
||||
std::unique_ptr<ThreadActivityAnalyzer> analyzer(
|
||||
new ThreadActivityAnalyzer(base, size));
|
||||
if (!analyzer->IsValid())
|
||||
continue;
|
||||
analyzer->AddGlobalInformation(this);
|
||||
|
||||
// Track PIDs.
|
||||
int64_t pid = analyzer->GetProcessId();
|
||||
if (seen_pids.find(pid) == seen_pids.end()) {
|
||||
process_ids_.push_back(pid);
|
||||
seen_pids.insert(pid);
|
||||
}
|
||||
|
||||
// Add this analyzer to the map of known ones, indexed by a unique
|
||||
// thread
|
||||
// identifier.
|
||||
DCHECK(!base::ContainsKey(analyzers_, analyzer->GetThreadKey()));
|
||||
analyzer->allocator_reference_ = ref;
|
||||
analyzers_[analyzer->GetThreadKey()] = std::move(analyzer);
|
||||
} break;
|
||||
|
||||
case GlobalActivityTracker::kTypeIdProcessDataRecord: {
|
||||
// Get the PID associated with this data record.
|
||||
int64_t process_id;
|
||||
int64_t create_stamp;
|
||||
ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp);
|
||||
DCHECK(!base::ContainsKey(process_data_, process_id));
|
||||
|
||||
// Create a snapshot of the data. This can fail if the data is somehow
|
||||
// corrupted or the process shutdown and the memory being released.
|
||||
UserDataSnapshot& snapshot = process_data_[process_id];
|
||||
snapshot.process_id = process_id;
|
||||
snapshot.create_stamp = create_stamp;
|
||||
const ActivityUserData process_data(base, size);
|
||||
if (!process_data.CreateSnapshot(&snapshot.data))
|
||||
break;
|
||||
|
||||
// Check that nothing changed. If it did, forget what was recorded.
|
||||
ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp);
|
||||
if (process_id != snapshot.process_id ||
|
||||
create_stamp != snapshot.create_stamp) {
|
||||
process_data_.erase(process_id);
|
||||
break;
|
||||
}
|
||||
|
||||
// Track PIDs.
|
||||
if (seen_pids.find(process_id) == seen_pids.end()) {
|
||||
process_ids_.push_back(process_id);
|
||||
seen_pids.insert(process_id);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse the list of PIDs so that they get popped in the order found.
|
||||
std::reverse(process_ids_.begin(), process_ids_.end());
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
262
base/debug/activity_analyzer.h
Normal file
262
base/debug/activity_analyzer.h
Normal file
@@ -0,0 +1,262 @@
|
||||
// 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.
|
||||
|
||||
#ifndef BASE_DEBUG_ACTIVITY_ANALYZER_H_
|
||||
#define BASE_DEBUG_ACTIVITY_ANALYZER_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/base_export.h"
|
||||
#include "base/debug/activity_tracker.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
class GlobalActivityAnalyzer;
|
||||
|
||||
// This class provides analysis of data captured from a ThreadActivityTracker.
|
||||
// When created, it takes a snapshot of the data held by the tracker and
|
||||
// makes that information available to other code.
|
||||
class BASE_EXPORT ThreadActivityAnalyzer {
|
||||
public:
|
||||
struct BASE_EXPORT Snapshot : ThreadActivityTracker::Snapshot {
|
||||
Snapshot();
|
||||
~Snapshot();
|
||||
|
||||
// The user-data snapshot for an activity, matching the |activity_stack|
|
||||
// of ThreadActivityTracker::Snapshot, if any.
|
||||
std::vector<ActivityUserData::Snapshot> user_data_stack;
|
||||
};
|
||||
|
||||
// This class provides keys that uniquely identify a thread, even across
|
||||
// multiple processes.
|
||||
class ThreadKey {
|
||||
public:
|
||||
ThreadKey(int64_t pid, int64_t tid) : pid_(pid), tid_(tid) {}
|
||||
|
||||
bool operator<(const ThreadKey& rhs) const {
|
||||
if (pid_ != rhs.pid_)
|
||||
return pid_ < rhs.pid_;
|
||||
return tid_ < rhs.tid_;
|
||||
}
|
||||
|
||||
bool operator==(const ThreadKey& rhs) const {
|
||||
return (pid_ == rhs.pid_ && tid_ == rhs.tid_);
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t pid_;
|
||||
int64_t tid_;
|
||||
};
|
||||
|
||||
// Creates an analyzer for an existing activity |tracker|. A snapshot is taken
|
||||
// immediately and the tracker is not referenced again.
|
||||
explicit ThreadActivityAnalyzer(const ThreadActivityTracker& tracker);
|
||||
|
||||
// Creates an analyzer for a block of memory currently or previously in-use
|
||||
// by an activity-tracker. A snapshot is taken immediately and the memory
|
||||
// is not referenced again.
|
||||
ThreadActivityAnalyzer(void* base, size_t size);
|
||||
|
||||
// Creates an analyzer for a block of memory held within a persistent-memory
|
||||
// |allocator| at the given |reference|. A snapshot is taken immediately and
|
||||
// the memory is not referenced again.
|
||||
ThreadActivityAnalyzer(PersistentMemoryAllocator* allocator,
|
||||
PersistentMemoryAllocator::Reference reference);
|
||||
|
||||
~ThreadActivityAnalyzer();
|
||||
|
||||
// Adds information from the global analyzer.
|
||||
void AddGlobalInformation(GlobalActivityAnalyzer* global);
|
||||
|
||||
// Returns true iff the contained data is valid. Results from all other
|
||||
// methods are undefined if this returns false.
|
||||
bool IsValid() { return activity_snapshot_valid_; }
|
||||
|
||||
// Gets the process id and its creation stamp.
|
||||
int64_t GetProcessId(int64_t* out_stamp = nullptr) {
|
||||
if (out_stamp)
|
||||
*out_stamp = activity_snapshot_.create_stamp;
|
||||
return activity_snapshot_.process_id;
|
||||
}
|
||||
|
||||
// Gets the name of the thread.
|
||||
const std::string& GetThreadName() {
|
||||
return activity_snapshot_.thread_name;
|
||||
}
|
||||
|
||||
// Gets the TheadKey for this thread.
|
||||
ThreadKey GetThreadKey() {
|
||||
return ThreadKey(activity_snapshot_.process_id,
|
||||
activity_snapshot_.thread_id);
|
||||
}
|
||||
|
||||
const Snapshot& activity_snapshot() { return activity_snapshot_; }
|
||||
|
||||
private:
|
||||
friend class GlobalActivityAnalyzer;
|
||||
|
||||
// The snapshot of the activity tracker taken at the moment of construction.
|
||||
Snapshot activity_snapshot_;
|
||||
|
||||
// Flag indicating if the snapshot data is valid.
|
||||
bool activity_snapshot_valid_;
|
||||
|
||||
// A reference into a persistent memory allocator, used by the global
|
||||
// analyzer to know where this tracker came from.
|
||||
PersistentMemoryAllocator::Reference allocator_reference_ = 0;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ThreadActivityAnalyzer);
|
||||
};
|
||||
|
||||
|
||||
// This class manages analyzers for all known processes and threads as stored
|
||||
// in a persistent memory allocator. It supports retrieval of them through
|
||||
// iteration and directly using a ThreadKey, which allows for cross-references
|
||||
// to be resolved.
|
||||
// Note that though atomic snapshots are used and everything has its snapshot
|
||||
// taken at the same time, the multi-snapshot itself is not atomic and thus may
|
||||
// show small inconsistencies between threads if attempted on a live system.
|
||||
class BASE_EXPORT GlobalActivityAnalyzer {
|
||||
public:
|
||||
struct ProgramLocation {
|
||||
int module;
|
||||
uintptr_t offset;
|
||||
};
|
||||
|
||||
using ThreadKey = ThreadActivityAnalyzer::ThreadKey;
|
||||
|
||||
// Creates a global analyzer from a persistent memory allocator.
|
||||
explicit GlobalActivityAnalyzer(
|
||||
std::unique_ptr<PersistentMemoryAllocator> allocator);
|
||||
|
||||
~GlobalActivityAnalyzer();
|
||||
|
||||
// Creates a global analyzer using a given persistent-memory |allocator|.
|
||||
static std::unique_ptr<GlobalActivityAnalyzer> CreateWithAllocator(
|
||||
std::unique_ptr<PersistentMemoryAllocator> allocator);
|
||||
|
||||
#if !defined(OS_NACL)
|
||||
// Creates a global analyzer using the contents of a file given in
|
||||
// |file_path|.
|
||||
static std::unique_ptr<GlobalActivityAnalyzer> CreateWithFile(
|
||||
const FilePath& file_path);
|
||||
#endif // !defined(OS_NACL)
|
||||
|
||||
// Like above but accesses an allocator in a mapped shared-memory segment.
|
||||
static std::unique_ptr<GlobalActivityAnalyzer> CreateWithSharedMemory(
|
||||
std::unique_ptr<SharedMemory> shm);
|
||||
|
||||
// Like above but takes a handle to an existing shared memory segment and
|
||||
// maps it before creating the tracker.
|
||||
static std::unique_ptr<GlobalActivityAnalyzer> CreateWithSharedMemoryHandle(
|
||||
const SharedMemoryHandle& handle,
|
||||
size_t size);
|
||||
|
||||
// Iterates over all known valid processes and returns their PIDs or zero
|
||||
// if there are no more. Calls to GetFirstProcess() will perform a global
|
||||
// snapshot in order to provide a relatively consistent state across the
|
||||
// future calls to GetNextProcess() and GetFirst/NextAnalyzer(). PIDs are
|
||||
// returned in the order they're found meaning that a first-launched
|
||||
// controlling process will be found first. Note, however, that space
|
||||
// freed by an exiting process may be re-used by a later process.
|
||||
int64_t GetFirstProcess();
|
||||
int64_t GetNextProcess();
|
||||
|
||||
// Iterates over all known valid analyzers for the a given process or returns
|
||||
// null if there are no more.
|
||||
//
|
||||
// GetFirstProcess() must be called first in order to capture a global
|
||||
// snapshot! Ownership stays with the global analyzer object and all existing
|
||||
// analyzer pointers are invalidated when GetFirstProcess() is called.
|
||||
ThreadActivityAnalyzer* GetFirstAnalyzer(int64_t pid);
|
||||
ThreadActivityAnalyzer* GetNextAnalyzer();
|
||||
|
||||
// Gets the analyzer for a specific thread or null if there is none.
|
||||
// Ownership stays with the global analyzer object.
|
||||
ThreadActivityAnalyzer* GetAnalyzerForThread(const ThreadKey& key);
|
||||
|
||||
// Extract user data based on a reference and its identifier.
|
||||
ActivityUserData::Snapshot GetUserDataSnapshot(int64_t pid,
|
||||
uint32_t ref,
|
||||
uint32_t id);
|
||||
|
||||
// Extract the data for a specific process. An empty snapshot will be
|
||||
// returned if the process is not known.
|
||||
const ActivityUserData::Snapshot& GetProcessDataSnapshot(int64_t pid);
|
||||
|
||||
// Gets all log messages stored within.
|
||||
std::vector<std::string> GetLogMessages();
|
||||
|
||||
// Gets modules corresponding to a pid. This pid must come from a call to
|
||||
// GetFirst/NextProcess. Only modules that were first registered prior to
|
||||
// GetFirstProcess's snapshot are returned.
|
||||
std::vector<GlobalActivityTracker::ModuleInfo> GetModules(int64_t pid);
|
||||
|
||||
// Gets the corresponding "program location" for a given "program counter".
|
||||
// This will return {0,0} if no mapping could be found.
|
||||
ProgramLocation GetProgramLocationFromAddress(uint64_t address);
|
||||
|
||||
// Returns whether the data is complete. Data can be incomplete if the
|
||||
// recording size quota is hit.
|
||||
bool IsDataComplete() const;
|
||||
|
||||
private:
|
||||
using AnalyzerMap =
|
||||
std::map<ThreadKey, std::unique_ptr<ThreadActivityAnalyzer>>;
|
||||
|
||||
struct UserDataSnapshot {
|
||||
// Complex class needs out-of-line ctor/dtor.
|
||||
UserDataSnapshot();
|
||||
UserDataSnapshot(const UserDataSnapshot& rhs);
|
||||
UserDataSnapshot(UserDataSnapshot&& rhs);
|
||||
~UserDataSnapshot();
|
||||
|
||||
int64_t process_id;
|
||||
int64_t create_stamp;
|
||||
ActivityUserData::Snapshot data;
|
||||
};
|
||||
|
||||
// Finds, creates, and indexes analyzers for all known processes and threads.
|
||||
void PrepareAllAnalyzers();
|
||||
|
||||
// The persistent memory allocator holding all tracking data.
|
||||
std::unique_ptr<PersistentMemoryAllocator> allocator_;
|
||||
|
||||
// The time stamp when analysis began. This is used to prevent looking into
|
||||
// process IDs that get reused when analyzing a live system.
|
||||
int64_t analysis_stamp_;
|
||||
|
||||
// The iterator for finding tracking information in the allocator.
|
||||
PersistentMemoryAllocator::Iterator allocator_iterator_;
|
||||
|
||||
// A set of all interesting memory references found within the allocator.
|
||||
std::set<PersistentMemoryAllocator::Reference> memory_references_;
|
||||
|
||||
// A set of all process-data memory references found within the allocator.
|
||||
std::map<int64_t, UserDataSnapshot> process_data_;
|
||||
|
||||
// A set of all process IDs collected during PrepareAllAnalyzers. These are
|
||||
// popped and returned one-by-one with calls to GetFirst/NextProcess().
|
||||
std::vector<int64_t> process_ids_;
|
||||
|
||||
// A map, keyed by ThreadKey, of all valid activity analyzers.
|
||||
AnalyzerMap analyzers_;
|
||||
|
||||
// The iterator within the analyzers_ map for returning analyzers through
|
||||
// first/next iteration.
|
||||
AnalyzerMap::iterator analyzers_iterator_;
|
||||
int64_t analyzers_iterator_pid_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(GlobalActivityAnalyzer);
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_ACTIVITY_ANALYZER_H_
|
||||
1826
base/debug/activity_tracker.cc
Normal file
1826
base/debug/activity_tracker.cc
Normal file
File diff suppressed because it is too large
Load Diff
1360
base/debug/activity_tracker.h
Normal file
1360
base/debug/activity_tracker.h
Normal file
File diff suppressed because it is too large
Load Diff
23
base/debug/alias.cc
Normal file
23
base/debug/alias.cc
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2011 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/alias.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
#if defined(COMPILER_MSVC)
|
||||
#pragma optimize("", off)
|
||||
#endif
|
||||
|
||||
void Alias(const void* var) {
|
||||
}
|
||||
|
||||
#if defined(COMPILER_MSVC)
|
||||
#pragma optimize("", on)
|
||||
#endif
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
37
base/debug/alias.h
Normal file
37
base/debug/alias.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2011 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_DEBUG_ALIAS_H_
|
||||
#define BASE_DEBUG_ALIAS_H_
|
||||
|
||||
#include "base/base_export.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// Make the optimizer think that var is aliased. This is to prevent it from
|
||||
// optimizing out local variables that would not otherwise be live at the point
|
||||
// of a potential crash.
|
||||
// base::debug::Alias should only be used for local variables, not globals,
|
||||
// object members, or function return values - these must be copied to locals if
|
||||
// you want to ensure they are recorded in crash dumps.
|
||||
// Note that if the local variable is a pointer then its value will be retained
|
||||
// but the memory that it points to will probably not be saved in the crash
|
||||
// dump - by default only stack memory is saved. Therefore the aliasing
|
||||
// technique is usually only worthwhile with non-pointer variables. If you have
|
||||
// a pointer to an object and you want to retain the object's state you need to
|
||||
// copy the object or its fields to local variables. Example usage:
|
||||
// int last_error = err_;
|
||||
// base::debug::Alias(&last_error);
|
||||
// char name_copy[16];
|
||||
// strncpy(name_copy, p->name, sizeof(name_copy) - 1);
|
||||
// name_copy[sizeof(name_copy) - 1] = '\0';
|
||||
// base::debug::Alias(name_copy);
|
||||
// CHECK(false);
|
||||
void BASE_EXPORT Alias(const void* var);
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_ALIAS_H_
|
||||
107
base/debug/asan_invalid_access.cc
Normal file
107
base/debug/asan_invalid_access.cc
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2014 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/asan_invalid_access.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/debug/alias.h"
|
||||
#include "base/logging.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(SYZYASAN) && defined(COMPILER_MSVC)
|
||||
// Disable warning C4530: "C++ exception handler used, but unwind semantics are
|
||||
// not enabled". We don't want to change the compilation flags just for this
|
||||
// test, and no exception should be triggered here, so this warning has no value
|
||||
// here.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4530)
|
||||
// Corrupt a memory block and make sure that the corruption gets detected either
|
||||
// when we free it or when another crash happens (if |induce_crash| is set to
|
||||
// true).
|
||||
NOINLINE void CorruptMemoryBlock(bool induce_crash) {
|
||||
// NOTE(sebmarchand): We intentionally corrupt a memory block here in order to
|
||||
// trigger an Address Sanitizer (ASAN) error report.
|
||||
static const int kArraySize = 5;
|
||||
int* array = new int[kArraySize];
|
||||
// Encapsulate the invalid memory access into a try-catch statement to prevent
|
||||
// this function from being instrumented. This way the underflow won't be
|
||||
// detected but the corruption will (as the allocator will still be hooked).
|
||||
try {
|
||||
// Declares the dummy value as volatile to make sure it doesn't get
|
||||
// optimized away.
|
||||
int volatile dummy = array[-1]--;
|
||||
base::debug::Alias(const_cast<int*>(&dummy));
|
||||
} catch (...) {
|
||||
}
|
||||
if (induce_crash)
|
||||
CHECK(false);
|
||||
delete[] array;
|
||||
}
|
||||
#pragma warning(pop)
|
||||
#endif // SYZYASAN && COMPILER_MSVC
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined(ADDRESS_SANITIZER) || defined(SYZYASAN)
|
||||
// NOTE(sebmarchand): We intentionally perform some invalid heap access here in
|
||||
// order to trigger an AddressSanitizer (ASan) error report.
|
||||
|
||||
static const size_t kArraySize = 5;
|
||||
|
||||
void AsanHeapOverflow() {
|
||||
// Declares the array as volatile to make sure it doesn't get optimized away.
|
||||
std::unique_ptr<volatile int[]> array(
|
||||
const_cast<volatile int*>(new int[kArraySize]));
|
||||
int dummy = array[kArraySize];
|
||||
base::debug::Alias(&dummy);
|
||||
}
|
||||
|
||||
void AsanHeapUnderflow() {
|
||||
// Declares the array as volatile to make sure it doesn't get optimized away.
|
||||
std::unique_ptr<volatile int[]> array(
|
||||
const_cast<volatile int*>(new int[kArraySize]));
|
||||
// We need to store the underflow address in a temporary variable as trying to
|
||||
// access array[-1] will trigger a warning C4245: "conversion from 'int' to
|
||||
// 'size_t', signed/unsigned mismatch".
|
||||
volatile int* underflow_address = &array[0] - 1;
|
||||
int dummy = *underflow_address;
|
||||
base::debug::Alias(&dummy);
|
||||
}
|
||||
|
||||
void AsanHeapUseAfterFree() {
|
||||
// Declares the array as volatile to make sure it doesn't get optimized away.
|
||||
std::unique_ptr<volatile int[]> array(
|
||||
const_cast<volatile int*>(new int[kArraySize]));
|
||||
volatile int* dangling = array.get();
|
||||
array.reset();
|
||||
int dummy = dangling[kArraySize / 2];
|
||||
base::debug::Alias(&dummy);
|
||||
}
|
||||
|
||||
#endif // ADDRESS_SANITIZER || SYZYASAN
|
||||
|
||||
#if defined(SYZYASAN) && defined(COMPILER_MSVC)
|
||||
void AsanCorruptHeapBlock() {
|
||||
CorruptMemoryBlock(false);
|
||||
}
|
||||
|
||||
void AsanCorruptHeap() {
|
||||
CorruptMemoryBlock(true);
|
||||
}
|
||||
#endif // SYZYASAN && COMPILER_MSVC
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
47
base/debug/asan_invalid_access.h
Normal file
47
base/debug/asan_invalid_access.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2014 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.
|
||||
//
|
||||
// Defines some functions that intentionally do an invalid memory access in
|
||||
// order to trigger an AddressSanitizer (ASan) error report.
|
||||
|
||||
#ifndef BASE_DEBUG_ASAN_INVALID_ACCESS_H_
|
||||
#define BASE_DEBUG_ASAN_INVALID_ACCESS_H_
|
||||
|
||||
#include "base/base_export.h"
|
||||
#include "base/compiler_specific.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
#if defined(ADDRESS_SANITIZER) || defined(SYZYASAN)
|
||||
|
||||
// Generates an heap buffer overflow.
|
||||
BASE_EXPORT NOINLINE void AsanHeapOverflow();
|
||||
|
||||
// Generates an heap buffer underflow.
|
||||
BASE_EXPORT NOINLINE void AsanHeapUnderflow();
|
||||
|
||||
// Generates an use after free.
|
||||
BASE_EXPORT NOINLINE void AsanHeapUseAfterFree();
|
||||
|
||||
#endif // ADDRESS_SANITIZER || SYZYASAN
|
||||
|
||||
// The "corrupt-block" and "corrupt-heap" classes of bugs is specific to
|
||||
// SyzyASan.
|
||||
#if defined(SYZYASAN) && defined(COMPILER_MSVC)
|
||||
|
||||
// Corrupts a memory block and makes sure that the corruption gets detected when
|
||||
// we try to free this block.
|
||||
BASE_EXPORT NOINLINE void AsanCorruptHeapBlock();
|
||||
|
||||
// Corrupts the heap and makes sure that the corruption gets detected when a
|
||||
// crash occur.
|
||||
BASE_EXPORT NOINLINE void AsanCorruptHeap();
|
||||
|
||||
#endif // SYZYASAN && COMPILER_MSVC
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_ASAN_INVALID_ACCESS_H_
|
||||
260
base/debug/close_handle_hook_win.cc
Normal file
260
base/debug/close_handle_hook_win.cc
Normal file
@@ -0,0 +1,260 @@
|
||||
// Copyright 2015 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/close_handle_hook_win.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <psapi.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/win/iat_patch_function.h"
|
||||
#include "base/win/pe_image.h"
|
||||
#include "base/win/scoped_handle.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace {
|
||||
|
||||
typedef BOOL (WINAPI* CloseHandleType) (HANDLE handle);
|
||||
|
||||
typedef BOOL (WINAPI* DuplicateHandleType)(HANDLE source_process,
|
||||
HANDLE source_handle,
|
||||
HANDLE target_process,
|
||||
HANDLE* target_handle,
|
||||
DWORD desired_access,
|
||||
BOOL inherit_handle,
|
||||
DWORD options);
|
||||
|
||||
CloseHandleType g_close_function = NULL;
|
||||
DuplicateHandleType g_duplicate_function = NULL;
|
||||
|
||||
// The entry point for CloseHandle interception. This function notifies the
|
||||
// verifier about the handle that is being closed, and calls the original
|
||||
// function.
|
||||
BOOL WINAPI CloseHandleHook(HANDLE handle) {
|
||||
base::win::OnHandleBeingClosed(handle);
|
||||
return g_close_function(handle);
|
||||
}
|
||||
|
||||
BOOL WINAPI DuplicateHandleHook(HANDLE source_process,
|
||||
HANDLE source_handle,
|
||||
HANDLE target_process,
|
||||
HANDLE* target_handle,
|
||||
DWORD desired_access,
|
||||
BOOL inherit_handle,
|
||||
DWORD options) {
|
||||
if ((options & DUPLICATE_CLOSE_SOURCE) &&
|
||||
(GetProcessId(source_process) == ::GetCurrentProcessId())) {
|
||||
base::win::OnHandleBeingClosed(source_handle);
|
||||
}
|
||||
|
||||
return g_duplicate_function(source_process, source_handle, target_process,
|
||||
target_handle, desired_access, inherit_handle,
|
||||
options);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
// Provides a simple way to temporarily change the protection of a memory page.
|
||||
class AutoProtectMemory {
|
||||
public:
|
||||
AutoProtectMemory()
|
||||
: changed_(false), address_(NULL), bytes_(0), old_protect_(0) {}
|
||||
|
||||
~AutoProtectMemory() {
|
||||
RevertProtection();
|
||||
}
|
||||
|
||||
// Grants write access to a given memory range.
|
||||
bool ChangeProtection(void* address, size_t bytes);
|
||||
|
||||
// Restores the original page protection.
|
||||
void RevertProtection();
|
||||
|
||||
private:
|
||||
bool changed_;
|
||||
void* address_;
|
||||
size_t bytes_;
|
||||
DWORD old_protect_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AutoProtectMemory);
|
||||
};
|
||||
|
||||
bool AutoProtectMemory::ChangeProtection(void* address, size_t bytes) {
|
||||
DCHECK(!changed_);
|
||||
DCHECK(address);
|
||||
|
||||
// Change the page protection so that we can write.
|
||||
MEMORY_BASIC_INFORMATION memory_info;
|
||||
if (!VirtualQuery(address, &memory_info, sizeof(memory_info)))
|
||||
return false;
|
||||
|
||||
DWORD is_executable = (PAGE_EXECUTE | PAGE_EXECUTE_READ |
|
||||
PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY) &
|
||||
memory_info.Protect;
|
||||
|
||||
DWORD protect = is_executable ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
|
||||
if (!VirtualProtect(address, bytes, protect, &old_protect_))
|
||||
return false;
|
||||
|
||||
changed_ = true;
|
||||
address_ = address;
|
||||
bytes_ = bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
void AutoProtectMemory::RevertProtection() {
|
||||
if (!changed_)
|
||||
return;
|
||||
|
||||
DCHECK(address_);
|
||||
DCHECK(bytes_);
|
||||
|
||||
VirtualProtect(address_, bytes_, old_protect_, &old_protect_);
|
||||
changed_ = false;
|
||||
address_ = NULL;
|
||||
bytes_ = 0;
|
||||
old_protect_ = 0;
|
||||
}
|
||||
|
||||
// Performs an EAT interception.
|
||||
void EATPatch(HMODULE module, const char* function_name,
|
||||
void* new_function, void** old_function) {
|
||||
if (!module)
|
||||
return;
|
||||
|
||||
base::win::PEImage pe(module);
|
||||
if (!pe.VerifyMagic())
|
||||
return;
|
||||
|
||||
DWORD* eat_entry = pe.GetExportEntry(function_name);
|
||||
if (!eat_entry)
|
||||
return;
|
||||
|
||||
if (!(*old_function))
|
||||
*old_function = pe.RVAToAddr(*eat_entry);
|
||||
|
||||
AutoProtectMemory memory;
|
||||
if (!memory.ChangeProtection(eat_entry, sizeof(DWORD)))
|
||||
return;
|
||||
|
||||
// Perform the patch.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4311 4302)
|
||||
// These casts generate truncation warnings because they are 32 bit specific.
|
||||
*eat_entry = reinterpret_cast<DWORD>(new_function) -
|
||||
reinterpret_cast<DWORD>(module);
|
||||
#pragma warning(pop)
|
||||
}
|
||||
|
||||
// Performs an IAT interception.
|
||||
base::win::IATPatchFunction* IATPatch(HMODULE module, const char* function_name,
|
||||
void* new_function, void** old_function) {
|
||||
if (!module)
|
||||
return NULL;
|
||||
|
||||
base::win::IATPatchFunction* patch = new base::win::IATPatchFunction;
|
||||
__try {
|
||||
// There is no guarantee that |module| is still loaded at this point.
|
||||
if (patch->PatchFromModule(module, "kernel32.dll", function_name,
|
||||
new_function)) {
|
||||
delete patch;
|
||||
return NULL;
|
||||
}
|
||||
} __except((GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ||
|
||||
GetExceptionCode() == EXCEPTION_GUARD_PAGE ||
|
||||
GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR) ?
|
||||
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
|
||||
// Leak the patch.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!(*old_function)) {
|
||||
// Things are probably messed up if each intercepted function points to
|
||||
// a different place, but we need only one function to call.
|
||||
*old_function = patch->original_function();
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
// Keeps track of all the hooks needed to intercept functions which could
|
||||
// possibly close handles.
|
||||
class HandleHooks {
|
||||
public:
|
||||
HandleHooks() {}
|
||||
~HandleHooks() {}
|
||||
|
||||
void AddIATPatch(HMODULE module);
|
||||
void AddEATPatch();
|
||||
|
||||
private:
|
||||
std::vector<base::win::IATPatchFunction*> hooks_;
|
||||
DISALLOW_COPY_AND_ASSIGN(HandleHooks);
|
||||
};
|
||||
|
||||
void HandleHooks::AddIATPatch(HMODULE module) {
|
||||
if (!module)
|
||||
return;
|
||||
|
||||
base::win::IATPatchFunction* patch = NULL;
|
||||
patch = IATPatch(module, "CloseHandle", &CloseHandleHook,
|
||||
reinterpret_cast<void**>(&g_close_function));
|
||||
if (!patch)
|
||||
return;
|
||||
hooks_.push_back(patch);
|
||||
|
||||
patch = IATPatch(module, "DuplicateHandle", &DuplicateHandleHook,
|
||||
reinterpret_cast<void**>(&g_duplicate_function));
|
||||
if (!patch)
|
||||
return;
|
||||
hooks_.push_back(patch);
|
||||
}
|
||||
|
||||
void HandleHooks::AddEATPatch() {
|
||||
// An attempt to restore the entry on the table at destruction is not safe.
|
||||
EATPatch(GetModuleHandleA("kernel32.dll"), "CloseHandle",
|
||||
&CloseHandleHook, reinterpret_cast<void**>(&g_close_function));
|
||||
EATPatch(GetModuleHandleA("kernel32.dll"), "DuplicateHandle",
|
||||
&DuplicateHandleHook,
|
||||
reinterpret_cast<void**>(&g_duplicate_function));
|
||||
}
|
||||
|
||||
void PatchLoadedModules(HandleHooks* hooks) {
|
||||
const DWORD kSize = 256;
|
||||
DWORD returned;
|
||||
std::unique_ptr<HMODULE[]> modules(new HMODULE[kSize]);
|
||||
if (!EnumProcessModules(GetCurrentProcess(), modules.get(),
|
||||
kSize * sizeof(HMODULE), &returned)) {
|
||||
return;
|
||||
}
|
||||
returned /= sizeof(HMODULE);
|
||||
returned = std::min(kSize, returned);
|
||||
|
||||
for (DWORD current = 0; current < returned; current++) {
|
||||
hooks->AddIATPatch(modules[current]);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void InstallHandleHooks() {
|
||||
static HandleHooks* hooks = new HandleHooks();
|
||||
|
||||
// Performing EAT interception first is safer in the presence of other
|
||||
// threads attempting to call CloseHandle.
|
||||
hooks->AddEATPatch();
|
||||
PatchLoadedModules(hooks);
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
19
base/debug/close_handle_hook_win.h
Normal file
19
base/debug/close_handle_hook_win.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2015 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_DEBUG_CLOSE_HANDLE_HOOK_WIN_H_
|
||||
#define BASE_DEBUG_CLOSE_HANDLE_HOOK_WIN_H_
|
||||
|
||||
#include "base/base_export.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// Installs the hooks required to debug use of improper handles.
|
||||
BASE_EXPORT void InstallHandleHooks();
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_CLOSE_HANDLE_HOOK_WIN_H_
|
||||
236
base/debug/crash_logging.cc
Normal file
236
base/debug/crash_logging.cc
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright (c) 2012 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/crash_logging.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "base/debug/stack_trace.h"
|
||||
#include "base/format_macros.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
|
||||
// Undef the macro so the preprocessor doesn't garble the constructor.
|
||||
#undef ScopedCrashKey
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
CrashKeyImplementation* g_crash_key_impl = nullptr;
|
||||
|
||||
// Global map of crash key names to registration entries.
|
||||
typedef std::unordered_map<base::StringPiece, CrashKey, base::StringPieceHash>
|
||||
CrashKeyMap;
|
||||
CrashKeyMap* g_crash_keys_ = nullptr;
|
||||
|
||||
// The maximum length of a single chunk.
|
||||
size_t g_chunk_max_length_ = 0;
|
||||
|
||||
// String used to format chunked key names.
|
||||
const char kChunkFormatString[] = "%s-%" PRIuS;
|
||||
|
||||
// The functions that are called to actually set the key-value pairs in the
|
||||
// crash reportng system.
|
||||
SetCrashKeyValueFuncT g_set_key_func_ = nullptr;
|
||||
ClearCrashKeyValueFuncT g_clear_key_func_ = nullptr;
|
||||
|
||||
// For a given |length|, computes the number of chunks a value of that size
|
||||
// will occupy.
|
||||
size_t NumChunksForLength(size_t length) {
|
||||
// Compute (length / g_chunk_max_length_), rounded up.
|
||||
return (length + g_chunk_max_length_ - 1) / g_chunk_max_length_;
|
||||
}
|
||||
|
||||
// The longest max_length allowed by the system.
|
||||
const size_t kLargestValueAllowed = 2048;
|
||||
|
||||
} // namespace
|
||||
|
||||
CrashKeyString* AllocateCrashKeyString(const char name[],
|
||||
CrashKeySize value_length) {
|
||||
if (!g_crash_key_impl)
|
||||
return nullptr;
|
||||
|
||||
return g_crash_key_impl->Allocate(name, value_length);
|
||||
}
|
||||
|
||||
void SetCrashKeyString(CrashKeyString* crash_key, base::StringPiece value) {
|
||||
if (!g_crash_key_impl || !crash_key)
|
||||
return;
|
||||
|
||||
g_crash_key_impl->Set(crash_key, value);
|
||||
}
|
||||
|
||||
void ClearCrashKeyString(CrashKeyString* crash_key) {
|
||||
if (!g_crash_key_impl || !crash_key)
|
||||
return;
|
||||
|
||||
g_crash_key_impl->Clear(crash_key);
|
||||
}
|
||||
|
||||
void SetCrashKeyImplementation(std::unique_ptr<CrashKeyImplementation> impl) {
|
||||
delete g_crash_key_impl;
|
||||
g_crash_key_impl = impl.release();
|
||||
}
|
||||
|
||||
void SetCrashKeyValue(const base::StringPiece& key,
|
||||
const base::StringPiece& value) {
|
||||
if (!g_set_key_func_ || !g_crash_keys_)
|
||||
return;
|
||||
|
||||
const CrashKey* crash_key = LookupCrashKey(key);
|
||||
|
||||
DCHECK(crash_key) << "All crash keys must be registered before use "
|
||||
<< "(key = " << key << ")";
|
||||
|
||||
// Handle the un-chunked case.
|
||||
if (!crash_key || crash_key->max_length <= g_chunk_max_length_) {
|
||||
g_set_key_func_(key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unset the unused chunks.
|
||||
std::vector<std::string> chunks =
|
||||
ChunkCrashKeyValue(*crash_key, value, g_chunk_max_length_);
|
||||
for (size_t i = chunks.size();
|
||||
i < NumChunksForLength(crash_key->max_length);
|
||||
++i) {
|
||||
g_clear_key_func_(base::StringPrintf(kChunkFormatString, key.data(), i+1));
|
||||
}
|
||||
|
||||
// Set the chunked keys.
|
||||
for (size_t i = 0; i < chunks.size(); ++i) {
|
||||
g_set_key_func_(base::StringPrintf(kChunkFormatString, key.data(), i+1),
|
||||
chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearCrashKey(const base::StringPiece& key) {
|
||||
if (!g_clear_key_func_ || !g_crash_keys_)
|
||||
return;
|
||||
|
||||
const CrashKey* crash_key = LookupCrashKey(key);
|
||||
|
||||
// Handle the un-chunked case.
|
||||
if (!crash_key || crash_key->max_length <= g_chunk_max_length_) {
|
||||
g_clear_key_func_(key);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < NumChunksForLength(crash_key->max_length); ++i) {
|
||||
g_clear_key_func_(base::StringPrintf(kChunkFormatString, key.data(), i+1));
|
||||
}
|
||||
}
|
||||
|
||||
void SetCrashKeyToStackTrace(const base::StringPiece& key,
|
||||
const StackTrace& trace) {
|
||||
size_t count = 0;
|
||||
const void* const* addresses = trace.Addresses(&count);
|
||||
SetCrashKeyFromAddresses(key, addresses, count);
|
||||
}
|
||||
|
||||
void SetCrashKeyFromAddresses(const base::StringPiece& key,
|
||||
const void* const* addresses,
|
||||
size_t count) {
|
||||
std::string value = "<null>";
|
||||
if (addresses && count) {
|
||||
const size_t kBreakpadValueMax = 255;
|
||||
|
||||
std::vector<std::string> hex_backtrace;
|
||||
size_t length = 0;
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
std::string s = base::StringPrintf("%p", addresses[i]);
|
||||
length += s.length() + 1;
|
||||
if (length > kBreakpadValueMax)
|
||||
break;
|
||||
hex_backtrace.push_back(s);
|
||||
}
|
||||
|
||||
value = base::JoinString(hex_backtrace, " ");
|
||||
|
||||
// Warn if this exceeds the breakpad limits.
|
||||
DCHECK_LE(value.length(), kBreakpadValueMax);
|
||||
}
|
||||
|
||||
SetCrashKeyValue(key, value);
|
||||
}
|
||||
|
||||
ScopedCrashKey::ScopedCrashKey(const base::StringPiece& key,
|
||||
const base::StringPiece& value)
|
||||
: key_(key.as_string()) {
|
||||
SetCrashKeyValue(key, value);
|
||||
}
|
||||
|
||||
ScopedCrashKey::~ScopedCrashKey() {
|
||||
ClearCrashKey(key_);
|
||||
}
|
||||
|
||||
size_t InitCrashKeys(const CrashKey* const keys, size_t count,
|
||||
size_t chunk_max_length) {
|
||||
DCHECK(!g_crash_keys_) << "Crash logging may only be initialized once";
|
||||
if (!keys) {
|
||||
delete g_crash_keys_;
|
||||
g_crash_keys_ = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_crash_keys_ = new CrashKeyMap;
|
||||
g_chunk_max_length_ = chunk_max_length;
|
||||
|
||||
size_t total_keys = 0;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
g_crash_keys_->insert(std::make_pair(keys[i].key_name, keys[i]));
|
||||
total_keys += NumChunksForLength(keys[i].max_length);
|
||||
DCHECK_LT(keys[i].max_length, kLargestValueAllowed);
|
||||
}
|
||||
DCHECK_EQ(count, g_crash_keys_->size())
|
||||
<< "Duplicate crash keys were registered";
|
||||
|
||||
return total_keys;
|
||||
}
|
||||
|
||||
const CrashKey* LookupCrashKey(const base::StringPiece& key) {
|
||||
if (!g_crash_keys_)
|
||||
return nullptr;
|
||||
CrashKeyMap::const_iterator it = g_crash_keys_->find(key.as_string());
|
||||
if (it == g_crash_keys_->end())
|
||||
return nullptr;
|
||||
return &(it->second);
|
||||
}
|
||||
|
||||
void SetCrashKeyReportingFunctions(
|
||||
SetCrashKeyValueFuncT set_key_func,
|
||||
ClearCrashKeyValueFuncT clear_key_func) {
|
||||
g_set_key_func_ = set_key_func;
|
||||
g_clear_key_func_ = clear_key_func;
|
||||
}
|
||||
|
||||
std::vector<std::string> ChunkCrashKeyValue(const CrashKey& crash_key,
|
||||
const base::StringPiece& value,
|
||||
size_t chunk_max_length) {
|
||||
std::string value_string = value.substr(0, crash_key.max_length).as_string();
|
||||
std::vector<std::string> chunks;
|
||||
for (size_t offset = 0; offset < value_string.length(); ) {
|
||||
std::string chunk = value_string.substr(offset, chunk_max_length);
|
||||
chunks.push_back(chunk);
|
||||
offset += chunk.length();
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
void ResetCrashLoggingForTesting() {
|
||||
delete g_crash_keys_;
|
||||
g_crash_keys_ = nullptr;
|
||||
g_chunk_max_length_ = 0;
|
||||
g_set_key_func_ = nullptr;
|
||||
g_clear_key_func_ = nullptr;
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
209
base/debug/crash_logging.h
Normal file
209
base/debug/crash_logging.h
Normal file
@@ -0,0 +1,209 @@
|
||||
// Copyright (c) 2012 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_DEBUG_CRASH_LOGGING_H_
|
||||
#define BASE_DEBUG_CRASH_LOGGING_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "base/base_export.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// A crash key is an annotation that is carried along with a crash report, to
|
||||
// provide additional debugging information beyond a stack trace. Crash keys
|
||||
// have a name and a string value.
|
||||
//
|
||||
// The preferred API is //components/crash/core/common:crash_key, however not
|
||||
// all clients can hold a direct dependency on that target. The API provided
|
||||
// in this file indirects the dependency.
|
||||
//
|
||||
// Example usage:
|
||||
// static CrashKeyString* crash_key =
|
||||
// AllocateCrashKeyString("name", CrashKeySize::Size32);
|
||||
// SetCrashKeyString(crash_key, "value");
|
||||
// ClearCrashKeyString(crash_key);
|
||||
|
||||
// The maximum length for a crash key's value must be one of the following
|
||||
// pre-determined values.
|
||||
enum class CrashKeySize {
|
||||
Size32 = 32,
|
||||
Size64 = 64,
|
||||
Size256 = 256,
|
||||
};
|
||||
|
||||
struct CrashKeyString;
|
||||
|
||||
// Allocates a new crash key with the specified |name| with storage for a
|
||||
// value up to length |size|. This will return null if the crash key system is
|
||||
// not initialized.
|
||||
BASE_EXPORT CrashKeyString* AllocateCrashKeyString(const char name[],
|
||||
CrashKeySize size);
|
||||
|
||||
// Stores |value| into the specified |crash_key|. The |crash_key| may be null
|
||||
// if AllocateCrashKeyString() returned null. If |value| is longer than the
|
||||
// size with which the key was allocated, it will be truncated.
|
||||
BASE_EXPORT void SetCrashKeyString(CrashKeyString* crash_key,
|
||||
base::StringPiece value);
|
||||
|
||||
// Clears any value that was stored in |crash_key|. The |crash_key| may be
|
||||
// null.
|
||||
BASE_EXPORT void ClearCrashKeyString(CrashKeyString* crash_key);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// The following declarations are used to initialize the crash key system
|
||||
// in //base by providing implementations for the above functions.
|
||||
|
||||
// The virtual interface that provides the implementation for the crash key
|
||||
// API. This is implemented by a higher-layer component, and the instance is
|
||||
// set using the function below.
|
||||
class CrashKeyImplementation {
|
||||
public:
|
||||
virtual ~CrashKeyImplementation() {}
|
||||
|
||||
virtual CrashKeyString* Allocate(const char name[], CrashKeySize size) = 0;
|
||||
virtual void Set(CrashKeyString* crash_key, base::StringPiece value) = 0;
|
||||
virtual void Clear(CrashKeyString* crash_key) = 0;
|
||||
};
|
||||
|
||||
// Initializes the crash key system in base by replacing the existing
|
||||
// implementation, if it exists, with |impl|. The |impl| is copied into base.
|
||||
BASE_EXPORT void SetCrashKeyImplementation(
|
||||
std::unique_ptr<CrashKeyImplementation> impl);
|
||||
|
||||
// The base structure for a crash key, storing the allocation metadata.
|
||||
struct CrashKeyString {
|
||||
constexpr CrashKeyString(const char name[], CrashKeySize size)
|
||||
: name(name), size(size) {}
|
||||
const char* const name;
|
||||
const CrashKeySize size;
|
||||
};
|
||||
|
||||
// The API below is deprecated.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class StackTrace;
|
||||
|
||||
// Sets or clears a specific key-value pair from the crash metadata. Keys and
|
||||
// values are terminated at the null byte.
|
||||
BASE_EXPORT void SetCrashKeyValue(const base::StringPiece& key,
|
||||
const base::StringPiece& value);
|
||||
BASE_EXPORT void ClearCrashKey(const base::StringPiece& key);
|
||||
|
||||
// Records the given StackTrace into a crash key.
|
||||
BASE_EXPORT void SetCrashKeyToStackTrace(const base::StringPiece& key,
|
||||
const StackTrace& trace);
|
||||
|
||||
// Formats |count| instruction pointers from |addresses| using %p and
|
||||
// sets the resulting string as a value for crash key |key|. A maximum of 23
|
||||
// items will be encoded, since breakpad limits values to 255 bytes.
|
||||
BASE_EXPORT void SetCrashKeyFromAddresses(const base::StringPiece& key,
|
||||
const void* const* addresses,
|
||||
size_t count);
|
||||
|
||||
// A scoper that sets the specified key to value for the lifetime of the
|
||||
// object, and clears it on destruction.
|
||||
class BASE_EXPORT ScopedCrashKey {
|
||||
public:
|
||||
ScopedCrashKey(const base::StringPiece& key, const base::StringPiece& value);
|
||||
~ScopedCrashKey();
|
||||
|
||||
// Helper to force a static_assert when instantiating a ScopedCrashKey
|
||||
// temporary without a name. The usual idiom is to just #define a macro that
|
||||
// static_asserts with the message; however, that doesn't work well when the
|
||||
// type is in a namespace.
|
||||
//
|
||||
// Instead, we use a templated helper to trigger the static_assert, observing
|
||||
// two rules:
|
||||
// - The static_assert needs to be in a normally uninstantiated template;
|
||||
// otherwise, it will fail to compile =)
|
||||
// - Similarly, the static_assert must be dependent on the template argument,
|
||||
// to prevent it from being evaluated until the template is instantiated.
|
||||
//
|
||||
// To prevent this constructor from being accidentally invoked, it takes a
|
||||
// special enum as an argument.
|
||||
|
||||
// Finally, note that this can't just be a template function that takes only
|
||||
// one parameter, because this ends up triggering the vexing parse issue.
|
||||
enum ScopedCrashKeyNeedsNameTag {
|
||||
KEY_NEEDS_NAME,
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
explicit ScopedCrashKey(ScopedCrashKeyNeedsNameTag, const Args&...) {
|
||||
constexpr bool always_false = sizeof...(Args) == 0 && sizeof...(Args) != 0;
|
||||
static_assert(
|
||||
always_false,
|
||||
"scoped crash key objects should not be unnamed temporaries.");
|
||||
}
|
||||
|
||||
private:
|
||||
std::string key_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedCrashKey);
|
||||
};
|
||||
|
||||
// Disallow an instantation of ScopedCrashKey without a name, since this results
|
||||
// in a temporary that is immediately destroyed. Doing so will trigger the
|
||||
// static_assert in the templated constructor helper in ScopedCrashKey.
|
||||
#define ScopedCrashKey(...) \
|
||||
ScopedCrashKey(base::debug::ScopedCrashKey::KEY_NEEDS_NAME, __VA_ARGS__)
|
||||
|
||||
// Before setting values for a key, all the keys must be registered.
|
||||
struct BASE_EXPORT CrashKey {
|
||||
// The name of the crash key, used in the above functions.
|
||||
const char* key_name;
|
||||
|
||||
// The maximum length for a value. If the value is longer than this, it will
|
||||
// be truncated. If the value is larger than the |chunk_max_length| passed to
|
||||
// InitCrashKeys() but less than this value, it will be split into multiple
|
||||
// numbered chunks.
|
||||
size_t max_length;
|
||||
};
|
||||
|
||||
// Before the crash key logging mechanism can be used, all crash keys must be
|
||||
// registered with this function. The function returns the amount of space
|
||||
// the crash reporting implementation should allocate space for the registered
|
||||
// crash keys. |chunk_max_length| is the maximum size that a value in a single
|
||||
// chunk can be.
|
||||
BASE_EXPORT size_t InitCrashKeys(const CrashKey* const keys, size_t count,
|
||||
size_t chunk_max_length);
|
||||
|
||||
// Returns the corresponding crash key object or NULL for a given key.
|
||||
BASE_EXPORT const CrashKey* LookupCrashKey(const base::StringPiece& key);
|
||||
|
||||
// In the platform crash reporting implementation, these functions set and
|
||||
// clear the NUL-terminated key-value pairs.
|
||||
typedef void (*SetCrashKeyValueFuncT)(const base::StringPiece&,
|
||||
const base::StringPiece&);
|
||||
typedef void (*ClearCrashKeyValueFuncT)(const base::StringPiece&);
|
||||
|
||||
// Sets the function pointers that are used to integrate with the platform-
|
||||
// specific crash reporting libraries.
|
||||
BASE_EXPORT void SetCrashKeyReportingFunctions(
|
||||
SetCrashKeyValueFuncT set_key_func,
|
||||
ClearCrashKeyValueFuncT clear_key_func);
|
||||
|
||||
// Helper function that breaks up a value according to the parameters
|
||||
// specified by the crash key object.
|
||||
BASE_EXPORT std::vector<std::string> ChunkCrashKeyValue(
|
||||
const CrashKey& crash_key,
|
||||
const base::StringPiece& value,
|
||||
size_t chunk_max_length);
|
||||
|
||||
// Resets the crash key system so it can be reinitialized. For testing only.
|
||||
BASE_EXPORT void ResetCrashLoggingForTesting();
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_CRASH_LOGGING_H_
|
||||
42
base/debug/debugger.cc
Normal file
42
base/debug/debugger.cc
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2011 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/debugger.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
static bool is_debug_ui_suppressed = false;
|
||||
|
||||
bool WaitForDebugger(int wait_seconds, bool silent) {
|
||||
#if defined(OS_ANDROID)
|
||||
// The pid from which we know which process to attach to are not output by
|
||||
// android ddms, so we have to print it out explicitly.
|
||||
DLOG(INFO) << "DebugUtil::WaitForDebugger(pid=" << static_cast<int>(getpid())
|
||||
<< ")";
|
||||
#endif
|
||||
for (int i = 0; i < wait_seconds * 10; ++i) {
|
||||
if (BeingDebugged()) {
|
||||
if (!silent)
|
||||
BreakDebugger();
|
||||
return true;
|
||||
}
|
||||
PlatformThread::Sleep(TimeDelta::FromMilliseconds(100));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetSuppressDebugUI(bool suppress) {
|
||||
is_debug_ui_suppressed = suppress;
|
||||
}
|
||||
|
||||
bool IsDebugUISuppressed() {
|
||||
return is_debug_ui_suppressed;
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
44
base/debug/debugger.h
Normal file
44
base/debug/debugger.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2011 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.
|
||||
|
||||
// This is a cross platform interface for helper functions related to
|
||||
// debuggers. You should use this to test if you're running under a debugger,
|
||||
// and if you would like to yield (breakpoint) into the debugger.
|
||||
|
||||
#ifndef BASE_DEBUG_DEBUGGER_H_
|
||||
#define BASE_DEBUG_DEBUGGER_H_
|
||||
|
||||
#include "base/base_export.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// Waits wait_seconds seconds for a debugger to attach to the current process.
|
||||
// When silent is false, an exception is thrown when a debugger is detected.
|
||||
BASE_EXPORT bool WaitForDebugger(int wait_seconds, bool silent);
|
||||
|
||||
// Returns true if the given process is being run under a debugger.
|
||||
//
|
||||
// On OS X, the underlying mechanism doesn't work when the sandbox is enabled.
|
||||
// To get around this, this function caches its value.
|
||||
//
|
||||
// WARNING: Because of this, on OS X, a call MUST be made to this function
|
||||
// BEFORE the sandbox is enabled.
|
||||
BASE_EXPORT bool BeingDebugged();
|
||||
|
||||
// Break into the debugger, assumes a debugger is present.
|
||||
BASE_EXPORT void BreakDebugger();
|
||||
|
||||
// Used in test code, this controls whether showing dialogs and breaking into
|
||||
// the debugger is suppressed for debug errors, even in debug mode (normally
|
||||
// release mode doesn't do this stuff -- this is controlled separately).
|
||||
// Normally UI is not suppressed. This is normally used when running automated
|
||||
// tests where we want a crash rather than a dialog or a debugger.
|
||||
BASE_EXPORT void SetSuppressDebugUI(bool suppress);
|
||||
BASE_EXPORT bool IsDebugUISuppressed();
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_DEBUGGER_H_
|
||||
272
base/debug/debugger_posix.cc
Normal file
272
base/debug/debugger_posix.cc
Normal file
@@ -0,0 +1,272 @@
|
||||
// Copyright (c) 2012 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/debugger.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
#include "base/time/time.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(__GLIBCXX__)
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_MACOSX) || defined(OS_BSD)
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_FREEBSD)
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "base/debug/alias.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "base/strings/string_piece.h"
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
#include "base/third_party/symbolize/symbolize.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#include "base/threading/platform_thread.h"
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
#if defined(OS_MACOSX) || defined(OS_BSD)
|
||||
|
||||
// Based on Apple's recommended method as described in
|
||||
// http://developer.apple.com/qa/qa2004/qa1361.html
|
||||
bool BeingDebugged() {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
//
|
||||
// While some code used below may be async-signal unsafe, note how
|
||||
// the result is cached (see |is_set| and |being_debugged| static variables
|
||||
// right below). If this code is properly warmed-up early
|
||||
// in the start-up process, it should be safe to use later.
|
||||
|
||||
// If the process is sandboxed then we can't use the sysctl, so cache the
|
||||
// value.
|
||||
static bool is_set = false;
|
||||
static bool being_debugged = false;
|
||||
|
||||
if (is_set)
|
||||
return being_debugged;
|
||||
|
||||
// Initialize mib, which tells sysctl what info we want. In this case,
|
||||
// we're looking for information about a specific process ID.
|
||||
int mib[] = {
|
||||
CTL_KERN,
|
||||
KERN_PROC,
|
||||
KERN_PROC_PID,
|
||||
getpid()
|
||||
#if defined(OS_OPENBSD)
|
||||
, sizeof(struct kinfo_proc),
|
||||
0
|
||||
#endif
|
||||
};
|
||||
|
||||
// Caution: struct kinfo_proc is marked __APPLE_API_UNSTABLE. The source and
|
||||
// binary interfaces may change.
|
||||
struct kinfo_proc info;
|
||||
size_t info_size = sizeof(info);
|
||||
|
||||
#if defined(OS_OPENBSD)
|
||||
if (sysctl(mib, arraysize(mib), NULL, &info_size, NULL, 0) < 0)
|
||||
return -1;
|
||||
|
||||
mib[5] = (info_size / sizeof(struct kinfo_proc));
|
||||
#endif
|
||||
|
||||
int sysctl_result = sysctl(mib, arraysize(mib), &info, &info_size, NULL, 0);
|
||||
DCHECK_EQ(sysctl_result, 0);
|
||||
if (sysctl_result != 0) {
|
||||
is_set = true;
|
||||
being_debugged = false;
|
||||
return being_debugged;
|
||||
}
|
||||
|
||||
// This process is being debugged if the P_TRACED flag is set.
|
||||
is_set = true;
|
||||
#if defined(OS_FREEBSD)
|
||||
being_debugged = (info.ki_flag & P_TRACED) != 0;
|
||||
#elif defined(OS_BSD)
|
||||
being_debugged = (info.p_flag & P_TRACED) != 0;
|
||||
#else
|
||||
being_debugged = (info.kp_proc.p_flag & P_TRACED) != 0;
|
||||
#endif
|
||||
return being_debugged;
|
||||
}
|
||||
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID) || defined(OS_AIX)
|
||||
|
||||
// We can look in /proc/self/status for TracerPid. We are likely used in crash
|
||||
// handling, so we are careful not to use the heap or have side effects.
|
||||
// Another option that is common is to try to ptrace yourself, but then we
|
||||
// can't detach without forking(), and that's not so great.
|
||||
// static
|
||||
bool BeingDebugged() {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
int status_fd = open("/proc/self/status", O_RDONLY);
|
||||
if (status_fd == -1)
|
||||
return false;
|
||||
|
||||
// We assume our line will be in the first 1024 characters and that we can
|
||||
// read this much all at once. In practice this will generally be true.
|
||||
// This simplifies and speeds up things considerably.
|
||||
char buf[1024];
|
||||
|
||||
ssize_t num_read = HANDLE_EINTR(read(status_fd, buf, sizeof(buf)));
|
||||
if (IGNORE_EINTR(close(status_fd)) < 0)
|
||||
return false;
|
||||
|
||||
if (num_read <= 0)
|
||||
return false;
|
||||
|
||||
StringPiece status(buf, num_read);
|
||||
StringPiece tracer("TracerPid:\t");
|
||||
|
||||
StringPiece::size_type pid_index = status.find(tracer);
|
||||
if (pid_index == StringPiece::npos)
|
||||
return false;
|
||||
|
||||
// Our pid is 0 without a debugger, assume this for any pid starting with 0.
|
||||
pid_index += tracer.size();
|
||||
return pid_index < status.size() && status[pid_index] != '0';
|
||||
}
|
||||
|
||||
#elif defined(OS_FUCHSIA)
|
||||
|
||||
bool BeingDebugged() {
|
||||
// TODO(fuchsia): No gdb/gdbserver in the SDK yet.
|
||||
return false;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool BeingDebugged() {
|
||||
NOTIMPLEMENTED();
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// We want to break into the debugger in Debug mode, and cause a crash dump in
|
||||
// Release mode. Breakpad behaves as follows:
|
||||
//
|
||||
// +-------+-----------------+-----------------+
|
||||
// | OS | Dump on SIGTRAP | Dump on SIGABRT |
|
||||
// +-------+-----------------+-----------------+
|
||||
// | Linux | N | Y |
|
||||
// | Mac | Y | N |
|
||||
// +-------+-----------------+-----------------+
|
||||
//
|
||||
// Thus we do the following:
|
||||
// Linux: Debug mode if a debugger is attached, send SIGTRAP; otherwise send
|
||||
// SIGABRT
|
||||
// Mac: Always send SIGTRAP.
|
||||
|
||||
#if defined(ARCH_CPU_ARMEL)
|
||||
#define DEBUG_BREAK_ASM() asm("bkpt 0")
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
#define DEBUG_BREAK_ASM() asm("brk 0")
|
||||
#elif defined(ARCH_CPU_MIPS_FAMILY)
|
||||
#define DEBUG_BREAK_ASM() asm("break 2")
|
||||
#elif defined(ARCH_CPU_X86_FAMILY)
|
||||
#define DEBUG_BREAK_ASM() asm("int3")
|
||||
#endif
|
||||
|
||||
#if defined(NDEBUG) && !defined(OS_MACOSX) && !defined(OS_ANDROID)
|
||||
#define DEBUG_BREAK() abort()
|
||||
#elif defined(OS_NACL)
|
||||
// The NaCl verifier doesn't let use use int3. For now, we call abort(). We
|
||||
// should ask for advice from some NaCl experts about the optimum thing here.
|
||||
// http://code.google.com/p/nativeclient/issues/detail?id=645
|
||||
#define DEBUG_BREAK() abort()
|
||||
#elif !defined(OS_MACOSX)
|
||||
// Though Android has a "helpful" process called debuggerd to catch native
|
||||
// signals on the general assumption that they are fatal errors. If no debugger
|
||||
// is attached, we call abort since Breakpad needs SIGABRT to create a dump.
|
||||
// When debugger is attached, for ARM platform the bkpt instruction appears
|
||||
// to cause SIGBUS which is trapped by debuggerd, and we've had great
|
||||
// difficulty continuing in a debugger once we stop from SIG triggered by native
|
||||
// code, use GDB to set |go| to 1 to resume execution; for X86 platform, use
|
||||
// "int3" to setup breakpiont and raise SIGTRAP.
|
||||
//
|
||||
// On other POSIX architectures, except Mac OS X, we use the same logic to
|
||||
// ensure that breakpad creates a dump on crashes while it is still possible to
|
||||
// use a debugger.
|
||||
namespace {
|
||||
void DebugBreak() {
|
||||
if (!BeingDebugged()) {
|
||||
abort();
|
||||
} else {
|
||||
#if defined(DEBUG_BREAK_ASM)
|
||||
DEBUG_BREAK_ASM();
|
||||
#else
|
||||
volatile int go = 0;
|
||||
while (!go) {
|
||||
base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
#define DEBUG_BREAK() DebugBreak()
|
||||
#elif defined(DEBUG_BREAK_ASM)
|
||||
#define DEBUG_BREAK() DEBUG_BREAK_ASM()
|
||||
#else
|
||||
#error "Don't know how to debug break on this architecture/OS"
|
||||
#endif
|
||||
|
||||
void BreakDebugger() {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
// Linker's ICF feature may merge this function with other functions with the
|
||||
// same definition (e.g. any function whose sole job is to call abort()) and
|
||||
// it may confuse the crash report processing system. http://crbug.com/508489
|
||||
static int static_variable_to_make_this_function_unique = 0;
|
||||
base::debug::Alias(&static_variable_to_make_this_function_unique);
|
||||
|
||||
DEBUG_BREAK();
|
||||
#if defined(OS_ANDROID) && !defined(OFFICIAL_BUILD)
|
||||
// For Android development we always build release (debug builds are
|
||||
// unmanageably large), so the unofficial build is used for debugging. It is
|
||||
// helpful to be able to insert BreakDebugger() statements in the source,
|
||||
// attach the debugger, inspect the state of the program and then resume it by
|
||||
// setting the 'go' variable above.
|
||||
#elif defined(NDEBUG)
|
||||
// Terminate the program after signaling the debug break.
|
||||
_exit(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
25
base/debug/debugger_win.cc
Normal file
25
base/debug/debugger_win.cc
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2010 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/debugger.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <windows.h>
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
bool BeingDebugged() {
|
||||
return ::IsDebuggerPresent() != 0;
|
||||
}
|
||||
|
||||
void BreakDebugger() {
|
||||
if (IsDebugUISuppressed())
|
||||
_exit(1);
|
||||
|
||||
__debugbreak();
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
41
base/debug/dump_without_crashing.cc
Normal file
41
base/debug/dump_without_crashing.cc
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2013 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/dump_without_crashing.h"
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Pointer to the function that's called by DumpWithoutCrashing() to dump the
|
||||
// process's memory.
|
||||
void(CDECL* dump_without_crashing_function_)() = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
|
||||
namespace debug {
|
||||
|
||||
bool DumpWithoutCrashing() {
|
||||
if (dump_without_crashing_function_) {
|
||||
(*dump_without_crashing_function_)();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SetDumpWithoutCrashingFunction(void (CDECL *function)()) {
|
||||
#if !defined(COMPONENT_BUILD)
|
||||
// In component builds, the same base is shared between modules
|
||||
// so might be initialized several times. However in non-
|
||||
// component builds this should never happen.
|
||||
DCHECK(!dump_without_crashing_function_);
|
||||
#endif
|
||||
dump_without_crashing_function_ = function;
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
|
||||
} // namespace base
|
||||
37
base/debug/dump_without_crashing.h
Normal file
37
base/debug/dump_without_crashing.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2013 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_DEBUG_DUMP_WITHOUT_CRASHING_H_
|
||||
#define BASE_DEBUG_DUMP_WITHOUT_CRASHING_H_
|
||||
|
||||
#include "base/base_export.h"
|
||||
#include "base/compiler_specific.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
namespace base {
|
||||
|
||||
namespace debug {
|
||||
|
||||
// Handler to silently dump the current process without crashing.
|
||||
// Before calling this function, call SetDumpWithoutCrashingFunction to pass a
|
||||
// function pointer.
|
||||
// Windows:
|
||||
// This must be done for each instance of base (i.e. module) and is normally
|
||||
// chrome_elf!DumpProcessWithoutCrash. See example code in chrome_main.cc that
|
||||
// does this for chrome.dll and chrome_child.dll. Note: Crashpad sets this up
|
||||
// for main chrome.exe as part of calling crash_reporter::InitializeCrashpad.
|
||||
// Mac/Linux:
|
||||
// Crashpad does this as part of crash_reporter::InitializeCrashpad.
|
||||
// Returns false if called before SetDumpWithoutCrashingFunction.
|
||||
BASE_EXPORT bool DumpWithoutCrashing();
|
||||
|
||||
// Sets a function that'll be invoked to dump the current process when
|
||||
// DumpWithoutCrashing() is called.
|
||||
BASE_EXPORT void SetDumpWithoutCrashingFunction(void (CDECL *function)());
|
||||
|
||||
} // namespace debug
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_DUMP_WITHOUT_CRASHING_H_
|
||||
141
base/debug/gdi_debug_util_win.cc
Normal file
141
base/debug/gdi_debug_util_win.cc
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright 2014 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/gdi_debug_util_win.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include <psapi.h>
|
||||
#include <stddef.h>
|
||||
#include <TlHelp32.h>
|
||||
|
||||
#include "base/debug/alias.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/win/scoped_handle.h"
|
||||
#include "base/win/win_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void CollectChildGDIUsageAndDie(DWORD parent_pid) {
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
CHECK_NE(INVALID_HANDLE_VALUE, snapshot);
|
||||
|
||||
int total_process_count = 0;
|
||||
base::debug::Alias(&total_process_count);
|
||||
int total_peak_gdi_count = 0;
|
||||
base::debug::Alias(&total_peak_gdi_count);
|
||||
int total_gdi_count = 0;
|
||||
base::debug::Alias(&total_gdi_count);
|
||||
int total_user_count = 0;
|
||||
base::debug::Alias(&total_user_count);
|
||||
|
||||
int child_count = 0;
|
||||
base::debug::Alias(&child_count);
|
||||
int peak_gdi_count = 0;
|
||||
base::debug::Alias(&peak_gdi_count);
|
||||
int sum_gdi_count = 0;
|
||||
base::debug::Alias(&sum_gdi_count);
|
||||
int sum_user_count = 0;
|
||||
base::debug::Alias(&sum_user_count);
|
||||
|
||||
PROCESSENTRY32 proc_entry = {0};
|
||||
proc_entry.dwSize = sizeof(PROCESSENTRY32);
|
||||
CHECK(Process32First(snapshot, &proc_entry));
|
||||
|
||||
do {
|
||||
base::win::ScopedHandle process(
|
||||
OpenProcess(PROCESS_QUERY_INFORMATION,
|
||||
FALSE,
|
||||
proc_entry.th32ProcessID));
|
||||
if (!process.IsValid())
|
||||
continue;
|
||||
|
||||
int num_gdi_handles = GetGuiResources(process.Get(), GR_GDIOBJECTS);
|
||||
int num_user_handles = GetGuiResources(process.Get(), GR_USEROBJECTS);
|
||||
|
||||
// Compute sum and peak counts for all processes.
|
||||
++total_process_count;
|
||||
total_user_count += num_user_handles;
|
||||
total_gdi_count += num_gdi_handles;
|
||||
total_peak_gdi_count = std::max(total_peak_gdi_count, num_gdi_handles);
|
||||
|
||||
if (parent_pid != proc_entry.th32ParentProcessID)
|
||||
continue;
|
||||
|
||||
// Compute sum and peak counts for child processes.
|
||||
++child_count;
|
||||
sum_user_count += num_user_handles;
|
||||
sum_gdi_count += num_gdi_handles;
|
||||
peak_gdi_count = std::max(peak_gdi_count, num_gdi_handles);
|
||||
|
||||
} while (Process32Next(snapshot, &proc_entry));
|
||||
|
||||
CloseHandle(snapshot);
|
||||
CHECK(false);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
void CollectGDIUsageAndDie(BITMAPINFOHEADER* header, HANDLE shared_section) {
|
||||
// Make sure parameters are saved in the minidump.
|
||||
DWORD last_error = GetLastError();
|
||||
bool is_gdi_available = base::win::IsUser32AndGdi32Available();
|
||||
|
||||
LONG width = header ? header->biWidth : 0;
|
||||
LONG height = header ? header->biHeight : 0;
|
||||
|
||||
base::debug::Alias(&last_error);
|
||||
base::debug::Alias(&is_gdi_available);
|
||||
base::debug::Alias(&width);
|
||||
base::debug::Alias(&height);
|
||||
base::debug::Alias(&shared_section);
|
||||
|
||||
DWORD num_user_handles = GetGuiResources(GetCurrentProcess(), GR_USEROBJECTS);
|
||||
|
||||
DWORD num_gdi_handles = GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS);
|
||||
if (num_gdi_handles == 0) {
|
||||
DWORD get_gui_resources_error = GetLastError();
|
||||
base::debug::Alias(&get_gui_resources_error);
|
||||
CHECK(false);
|
||||
}
|
||||
|
||||
base::debug::Alias(&num_gdi_handles);
|
||||
base::debug::Alias(&num_user_handles);
|
||||
|
||||
const DWORD kLotsOfHandles = 9990;
|
||||
CHECK_LE(num_gdi_handles, kLotsOfHandles);
|
||||
|
||||
PROCESS_MEMORY_COUNTERS_EX pmc;
|
||||
pmc.cb = sizeof(pmc);
|
||||
CHECK(GetProcessMemoryInfo(GetCurrentProcess(),
|
||||
reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(&pmc),
|
||||
sizeof(pmc)));
|
||||
const size_t kLotsOfMemory = 1500 * 1024 * 1024; // 1.5GB
|
||||
CHECK_LE(pmc.PagefileUsage, kLotsOfMemory);
|
||||
CHECK_LE(pmc.PrivateUsage, kLotsOfMemory);
|
||||
|
||||
void* small_data = nullptr;
|
||||
base::debug::Alias(&small_data);
|
||||
|
||||
if (std::abs(height) * width > 100) {
|
||||
// Huh, that's weird. We don't have crazy handle count, we don't have
|
||||
// ridiculous memory usage. Try to allocate a small bitmap and see if that
|
||||
// fails too.
|
||||
header->biWidth = 5;
|
||||
header->biHeight = -5;
|
||||
HBITMAP small_bitmap = CreateDIBSection(
|
||||
nullptr, reinterpret_cast<BITMAPINFO*>(&header),
|
||||
0, &small_data, shared_section, 0);
|
||||
CHECK(small_bitmap != nullptr);
|
||||
DeleteObject(small_bitmap);
|
||||
}
|
||||
// Maybe the child processes are the ones leaking GDI or USER resouces.
|
||||
CollectChildGDIUsageAndDie(GetCurrentProcessId());
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
25
base/debug/gdi_debug_util_win.h
Normal file
25
base/debug/gdi_debug_util_win.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2014 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_DEBUG_GDI_DEBUG_UTIL_WIN_H_
|
||||
#define BASE_DEBUG_GDI_DEBUG_UTIL_WIN_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include "base/base_export.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// Crashes the process, using base::debug::Alias to leave valuable debugging
|
||||
// information in the crash dump. Pass values for |header| and |shared_section|
|
||||
// in the event of a bitmap allocation failure, to gather information about
|
||||
// those as well.
|
||||
void BASE_EXPORT CollectGDIUsageAndDie(BITMAPINFOHEADER* header = nullptr,
|
||||
HANDLE shared_section = nullptr);
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_GDI_DEBUG_UTIL_WIN_H_
|
||||
46
base/debug/leak_annotations.h
Normal file
46
base/debug/leak_annotations.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright (c) 2011 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_DEBUG_LEAK_ANNOTATIONS_H_
|
||||
#define BASE_DEBUG_LEAK_ANNOTATIONS_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
// This file defines macros which can be used to annotate intentional memory
|
||||
// leaks. Support for annotations is implemented in LeakSanitizer. Annotated
|
||||
// objects will be treated as a source of live pointers, i.e. any heap objects
|
||||
// reachable by following pointers from an annotated object will not be
|
||||
// reported as leaks.
|
||||
//
|
||||
// ANNOTATE_SCOPED_MEMORY_LEAK: all allocations made in the current scope
|
||||
// will be annotated as leaks.
|
||||
// ANNOTATE_LEAKING_OBJECT_PTR(X): the heap object referenced by pointer X will
|
||||
// be annotated as a leak.
|
||||
|
||||
#if defined(LEAK_SANITIZER) && !defined(OS_NACL)
|
||||
|
||||
#include <sanitizer/lsan_interface.h>
|
||||
|
||||
class ScopedLeakSanitizerDisabler {
|
||||
public:
|
||||
ScopedLeakSanitizerDisabler() { __lsan_disable(); }
|
||||
~ScopedLeakSanitizerDisabler() { __lsan_enable(); }
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedLeakSanitizerDisabler);
|
||||
};
|
||||
|
||||
#define ANNOTATE_SCOPED_MEMORY_LEAK \
|
||||
ScopedLeakSanitizerDisabler leak_sanitizer_disabler; static_cast<void>(0)
|
||||
|
||||
#define ANNOTATE_LEAKING_OBJECT_PTR(X) __lsan_ignore_object(X);
|
||||
|
||||
#else
|
||||
|
||||
#define ANNOTATE_SCOPED_MEMORY_LEAK ((void)0)
|
||||
#define ANNOTATE_LEAKING_OBJECT_PTR(X) ((void)0)
|
||||
|
||||
#endif
|
||||
|
||||
#endif // BASE_DEBUG_LEAK_ANNOTATIONS_H_
|
||||
140
base/debug/leak_tracker.h
Normal file
140
base/debug/leak_tracker.h
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2012 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_DEBUG_LEAK_TRACKER_H_
|
||||
#define BASE_DEBUG_LEAK_TRACKER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "build/build_config.h"
|
||||
|
||||
// Only enable leak tracking in non-uClibc debug builds.
|
||||
#if !defined(NDEBUG) && !defined(__UCLIBC__)
|
||||
#define ENABLE_LEAK_TRACKER
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_LEAK_TRACKER
|
||||
#include "base/containers/linked_list.h"
|
||||
#include "base/debug/stack_trace.h"
|
||||
#include "base/logging.h"
|
||||
#endif // ENABLE_LEAK_TRACKER
|
||||
|
||||
// LeakTracker is a helper to verify that all instances of a class
|
||||
// have been destroyed.
|
||||
//
|
||||
// It is particularly useful for classes that are bound to a single thread --
|
||||
// before destroying that thread, one can check that there are no remaining
|
||||
// instances of that class.
|
||||
//
|
||||
// For example, to enable leak tracking for class net::URLRequest, start by
|
||||
// adding a member variable of type LeakTracker<net::URLRequest>.
|
||||
//
|
||||
// class URLRequest {
|
||||
// ...
|
||||
// private:
|
||||
// base::LeakTracker<URLRequest> leak_tracker_;
|
||||
// };
|
||||
//
|
||||
//
|
||||
// Next, when we believe all instances of net::URLRequest have been deleted:
|
||||
//
|
||||
// LeakTracker<net::URLRequest>::CheckForLeaks();
|
||||
//
|
||||
// Should the check fail (because there are live instances of net::URLRequest),
|
||||
// then the allocation callstack for each leaked instances is dumped to
|
||||
// the error log.
|
||||
//
|
||||
// If ENABLE_LEAK_TRACKER is not defined, then the check has no effect.
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
#ifndef ENABLE_LEAK_TRACKER
|
||||
|
||||
// If leak tracking is disabled, do nothing.
|
||||
template<typename T>
|
||||
class LeakTracker {
|
||||
public:
|
||||
~LeakTracker() {}
|
||||
static void CheckForLeaks() {}
|
||||
static int NumLiveInstances() { return -1; }
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
// If leak tracking is enabled we track where the object was allocated from.
|
||||
|
||||
template<typename T>
|
||||
class LeakTracker : public LinkNode<LeakTracker<T> > {
|
||||
public:
|
||||
LeakTracker() {
|
||||
instances()->Append(this);
|
||||
}
|
||||
|
||||
~LeakTracker() {
|
||||
this->RemoveFromList();
|
||||
}
|
||||
|
||||
static void CheckForLeaks() {
|
||||
// Walk the allocation list and print each entry it contains.
|
||||
size_t count = 0;
|
||||
|
||||
// Copy the first 3 leak allocation callstacks onto the stack.
|
||||
// This way if we hit the CHECK() in a release build, the leak
|
||||
// information will be available in mini-dump.
|
||||
const size_t kMaxStackTracesToCopyOntoStack = 3;
|
||||
StackTrace stacktraces[kMaxStackTracesToCopyOntoStack];
|
||||
|
||||
for (LinkNode<LeakTracker<T> >* node = instances()->head();
|
||||
node != instances()->end();
|
||||
node = node->next()) {
|
||||
StackTrace& allocation_stack = node->value()->allocation_stack_;
|
||||
|
||||
if (count < kMaxStackTracesToCopyOntoStack)
|
||||
stacktraces[count] = allocation_stack;
|
||||
|
||||
++count;
|
||||
if (LOG_IS_ON(ERROR)) {
|
||||
LOG_STREAM(ERROR) << "Leaked " << node << " which was allocated by:";
|
||||
allocation_stack.OutputToStream(&LOG_STREAM(ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_EQ(0u, count);
|
||||
|
||||
// Hack to keep |stacktraces| and |count| alive (so compiler
|
||||
// doesn't optimize it out, and it will appear in mini-dumps).
|
||||
if (count == 0x1234) {
|
||||
for (size_t i = 0; i < kMaxStackTracesToCopyOntoStack; ++i)
|
||||
stacktraces[i].Print();
|
||||
}
|
||||
}
|
||||
|
||||
static int NumLiveInstances() {
|
||||
// Walk the allocation list and count how many entries it has.
|
||||
int count = 0;
|
||||
for (LinkNode<LeakTracker<T> >* node = instances()->head();
|
||||
node != instances()->end();
|
||||
node = node->next()) {
|
||||
++count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private:
|
||||
// Each specialization of LeakTracker gets its own static storage.
|
||||
static LinkedList<LeakTracker<T> >* instances() {
|
||||
static LinkedList<LeakTracker<T> > list;
|
||||
return &list;
|
||||
}
|
||||
|
||||
StackTrace allocation_stack_;
|
||||
};
|
||||
|
||||
#endif // ENABLE_LEAK_TRACKER
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_LEAK_TRACKER_H_
|
||||
169
base/debug/proc_maps_linux.cc
Normal file
169
base/debug/proc_maps_linux.cc
Normal file
@@ -0,0 +1,169 @@
|
||||
// Copyright (c) 2013 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/proc_maps_linux.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_ANDROID) && !defined(__LP64__)
|
||||
// In 32-bit mode, Bionic's inttypes.h defines PRI/SCNxPTR as an
|
||||
// unsigned long int, which is incompatible with Bionic's stdint.h
|
||||
// defining uintptr_t as an unsigned int:
|
||||
// https://code.google.com/p/android/issues/detail?id=57218
|
||||
#undef SCNxPTR
|
||||
#define SCNxPTR "x"
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// Scans |proc_maps| starting from |pos| returning true if the gate VMA was
|
||||
// found, otherwise returns false.
|
||||
static bool ContainsGateVMA(std::string* proc_maps, size_t pos) {
|
||||
#if defined(ARCH_CPU_ARM_FAMILY)
|
||||
// The gate VMA on ARM kernels is the interrupt vectors page.
|
||||
return proc_maps->find(" [vectors]\n", pos) != std::string::npos;
|
||||
#elif defined(ARCH_CPU_X86_64)
|
||||
// The gate VMA on x86 64-bit kernels is the virtual system call page.
|
||||
return proc_maps->find(" [vsyscall]\n", pos) != std::string::npos;
|
||||
#else
|
||||
// Otherwise assume there is no gate VMA in which case we shouldn't
|
||||
// get duplicate entires.
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ReadProcMaps(std::string* proc_maps) {
|
||||
// seq_file only writes out a page-sized amount on each call. Refer to header
|
||||
// file for details.
|
||||
const long kReadSize = sysconf(_SC_PAGESIZE);
|
||||
|
||||
base::ScopedFD fd(HANDLE_EINTR(open("/proc/self/maps", O_RDONLY)));
|
||||
if (!fd.is_valid()) {
|
||||
DPLOG(ERROR) << "Couldn't open /proc/self/maps";
|
||||
return false;
|
||||
}
|
||||
proc_maps->clear();
|
||||
|
||||
while (true) {
|
||||
// To avoid a copy, resize |proc_maps| so read() can write directly into it.
|
||||
// Compute |buffer| afterwards since resize() may reallocate.
|
||||
size_t pos = proc_maps->size();
|
||||
proc_maps->resize(pos + kReadSize);
|
||||
void* buffer = &(*proc_maps)[pos];
|
||||
|
||||
ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), buffer, kReadSize));
|
||||
if (bytes_read < 0) {
|
||||
DPLOG(ERROR) << "Couldn't read /proc/self/maps";
|
||||
proc_maps->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ... and don't forget to trim off excess bytes.
|
||||
proc_maps->resize(pos + bytes_read);
|
||||
|
||||
if (bytes_read == 0)
|
||||
break;
|
||||
|
||||
// The gate VMA is handled as a special case after seq_file has finished
|
||||
// iterating through all entries in the virtual memory table.
|
||||
//
|
||||
// Unfortunately, if additional entries are added at this point in time
|
||||
// seq_file gets confused and the next call to read() will return duplicate
|
||||
// entries including the gate VMA again.
|
||||
//
|
||||
// Avoid this by searching for the gate VMA and breaking early.
|
||||
if (ContainsGateVMA(proc_maps, pos))
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseProcMaps(const std::string& input,
|
||||
std::vector<MappedMemoryRegion>* regions_out) {
|
||||
CHECK(regions_out);
|
||||
std::vector<MappedMemoryRegion> regions;
|
||||
|
||||
// This isn't async safe nor terribly efficient, but it doesn't need to be at
|
||||
// this point in time.
|
||||
std::vector<std::string> lines = SplitString(
|
||||
input, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
|
||||
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
// Due to splitting on '\n' the last line should be empty.
|
||||
if (i == lines.size() - 1) {
|
||||
if (!lines[i].empty()) {
|
||||
DLOG(WARNING) << "Last line not empty";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
MappedMemoryRegion region;
|
||||
const char* line = lines[i].c_str();
|
||||
char permissions[5] = {'\0'}; // Ensure NUL-terminated string.
|
||||
uint8_t dev_major = 0;
|
||||
uint8_t dev_minor = 0;
|
||||
long inode = 0;
|
||||
int path_index = 0;
|
||||
|
||||
// Sample format from man 5 proc:
|
||||
//
|
||||
// address perms offset dev inode pathname
|
||||
// 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm
|
||||
//
|
||||
// The final %n term captures the offset in the input string, which is used
|
||||
// to determine the path name. It *does not* increment the return value.
|
||||
// Refer to man 3 sscanf for details.
|
||||
if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n",
|
||||
®ion.start, ®ion.end, permissions, ®ion.offset,
|
||||
&dev_major, &dev_minor, &inode, &path_index) < 7) {
|
||||
DPLOG(WARNING) << "sscanf failed for line: " << line;
|
||||
return false;
|
||||
}
|
||||
|
||||
region.permissions = 0;
|
||||
|
||||
if (permissions[0] == 'r')
|
||||
region.permissions |= MappedMemoryRegion::READ;
|
||||
else if (permissions[0] != '-')
|
||||
return false;
|
||||
|
||||
if (permissions[1] == 'w')
|
||||
region.permissions |= MappedMemoryRegion::WRITE;
|
||||
else if (permissions[1] != '-')
|
||||
return false;
|
||||
|
||||
if (permissions[2] == 'x')
|
||||
region.permissions |= MappedMemoryRegion::EXECUTE;
|
||||
else if (permissions[2] != '-')
|
||||
return false;
|
||||
|
||||
if (permissions[3] == 'p')
|
||||
region.permissions |= MappedMemoryRegion::PRIVATE;
|
||||
else if (permissions[3] != 's' && permissions[3] != 'S') // Shared memory.
|
||||
return false;
|
||||
|
||||
// Pushing then assigning saves us a string copy.
|
||||
regions.push_back(region);
|
||||
regions.back().path.assign(line + path_index);
|
||||
}
|
||||
|
||||
regions_out->swap(regions);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
94
base/debug/proc_maps_linux.h
Normal file
94
base/debug/proc_maps_linux.h
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2013 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_DEBUG_PROC_MAPS_LINUX_H_
|
||||
#define BASE_DEBUG_PROC_MAPS_LINUX_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/base_export.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// Describes a region of mapped memory and the path of the file mapped.
|
||||
struct MappedMemoryRegion {
|
||||
enum Permission {
|
||||
READ = 1 << 0,
|
||||
WRITE = 1 << 1,
|
||||
EXECUTE = 1 << 2,
|
||||
PRIVATE = 1 << 3, // If set, region is private, otherwise it is shared.
|
||||
};
|
||||
|
||||
// The address range [start,end) of mapped memory.
|
||||
uintptr_t start;
|
||||
uintptr_t end;
|
||||
|
||||
// Byte offset into |path| of the range mapped into memory.
|
||||
unsigned long long offset;
|
||||
|
||||
// Image base, if this mapping corresponds to an ELF image.
|
||||
uintptr_t base;
|
||||
|
||||
// Bitmask of read/write/execute/private/shared permissions.
|
||||
uint8_t permissions;
|
||||
|
||||
// Name of the file mapped into memory.
|
||||
//
|
||||
// NOTE: path names aren't guaranteed to point at valid files. For example,
|
||||
// "[heap]" and "[stack]" are used to represent the location of the process'
|
||||
// heap and stack, respectively.
|
||||
std::string path;
|
||||
};
|
||||
|
||||
// Reads the data from /proc/self/maps and stores the result in |proc_maps|.
|
||||
// Returns true if successful, false otherwise.
|
||||
//
|
||||
// There is *NO* guarantee that the resulting contents will be free of
|
||||
// duplicates or even contain valid entries by time the method returns.
|
||||
//
|
||||
//
|
||||
// THE GORY DETAILS
|
||||
//
|
||||
// Did you know it's next-to-impossible to atomically read the whole contents
|
||||
// of /proc/<pid>/maps? You would think that if we passed in a large-enough
|
||||
// buffer to read() that It Should Just Work(tm), but sadly that's not the case.
|
||||
//
|
||||
// Linux's procfs uses seq_file [1] for handling iteration, text formatting,
|
||||
// and dealing with resulting data that is larger than the size of a page. That
|
||||
// last bit is especially important because it means that seq_file will never
|
||||
// return more than the size of a page in a single call to read().
|
||||
//
|
||||
// Unfortunately for a program like Chrome the size of /proc/self/maps is
|
||||
// larger than the size of page so we're forced to call read() multiple times.
|
||||
// If the virtual memory table changed in any way between calls to read() (e.g.,
|
||||
// a different thread calling mprotect()), it can make seq_file generate
|
||||
// duplicate entries or skip entries.
|
||||
//
|
||||
// Even if seq_file was changed to keep flushing the contents of its page-sized
|
||||
// buffer to the usermode buffer inside a single call to read(), it has to
|
||||
// release its lock on the virtual memory table to handle page faults while
|
||||
// copying data to usermode. This puts us in the same situation where the table
|
||||
// can change while we're copying data.
|
||||
//
|
||||
// Alternatives such as fork()-and-suspend-the-parent-while-child-reads were
|
||||
// attempted, but they present more subtle problems than it's worth. Depending
|
||||
// on your use case your best bet may be to read /proc/<pid>/maps prior to
|
||||
// starting other threads.
|
||||
//
|
||||
// [1] http://kernelnewbies.org/Documents/SeqFileHowTo
|
||||
BASE_EXPORT bool ReadProcMaps(std::string* proc_maps);
|
||||
|
||||
// Parses /proc/<pid>/maps input data and stores in |regions|. Returns true
|
||||
// and updates |regions| if and only if all of |input| was successfully parsed.
|
||||
BASE_EXPORT bool ParseProcMaps(const std::string& input,
|
||||
std::vector<MappedMemoryRegion>* regions);
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_PROC_MAPS_LINUX_H_
|
||||
225
base/debug/profiler.cc
Normal file
225
base/debug/profiler.cc
Normal file
@@ -0,0 +1,225 @@
|
||||
// Copyright (c) 2012 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/profiler.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/debug/debugging_flags.h"
|
||||
#include "base/process/process_handle.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include "base/win/current_module.h"
|
||||
#include "base/win/pe_image.h"
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
// TODO(peria): Enable profiling on Windows.
|
||||
#if BUILDFLAG(ENABLE_PROFILING) && !defined(NO_TCMALLOC) && !defined(OS_WIN)
|
||||
#include "third_party/tcmalloc/chromium/src/gperftools/profiler.h"
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// TODO(peria): Enable profiling on Windows.
|
||||
#if BUILDFLAG(ENABLE_PROFILING) && !defined(NO_TCMALLOC) && !defined(OS_WIN)
|
||||
|
||||
static int profile_count = 0;
|
||||
|
||||
void StartProfiling(const std::string& name) {
|
||||
++profile_count;
|
||||
std::string full_name(name);
|
||||
std::string pid = IntToString(GetCurrentProcId());
|
||||
std::string count = IntToString(profile_count);
|
||||
ReplaceSubstringsAfterOffset(&full_name, 0, "{pid}", pid);
|
||||
ReplaceSubstringsAfterOffset(&full_name, 0, "{count}", count);
|
||||
ProfilerStart(full_name.c_str());
|
||||
}
|
||||
|
||||
void StopProfiling() {
|
||||
ProfilerFlush();
|
||||
ProfilerStop();
|
||||
}
|
||||
|
||||
void FlushProfiling() {
|
||||
ProfilerFlush();
|
||||
}
|
||||
|
||||
bool BeingProfiled() {
|
||||
return ProfilingIsEnabledForAllThreads();
|
||||
}
|
||||
|
||||
void RestartProfilingAfterFork() {
|
||||
ProfilerRegisterThread();
|
||||
}
|
||||
|
||||
bool IsProfilingSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void StartProfiling(const std::string& name) {
|
||||
}
|
||||
|
||||
void StopProfiling() {
|
||||
}
|
||||
|
||||
void FlushProfiling() {
|
||||
}
|
||||
|
||||
bool BeingProfiled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void RestartProfilingAfterFork() {
|
||||
}
|
||||
|
||||
bool IsProfilingSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !defined(OS_WIN)
|
||||
|
||||
bool IsBinaryInstrumented() {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DynamicFunctionEntryHook GetProfilerDynamicFunctionEntryHookFunc() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AddDynamicSymbol GetProfilerAddDynamicSymbolFunc() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#else // defined(OS_WIN)
|
||||
|
||||
bool IsBinaryInstrumented() {
|
||||
enum InstrumentationCheckState {
|
||||
UNINITIALIZED,
|
||||
INSTRUMENTED_IMAGE,
|
||||
NON_INSTRUMENTED_IMAGE,
|
||||
};
|
||||
|
||||
static InstrumentationCheckState state = UNINITIALIZED;
|
||||
|
||||
if (state == UNINITIALIZED) {
|
||||
base::win::PEImage image(CURRENT_MODULE());
|
||||
|
||||
// Check to be sure our image is structured as we'd expect.
|
||||
DCHECK(image.VerifyMagic());
|
||||
|
||||
// Syzygy-instrumented binaries contain a PE image section named ".thunks",
|
||||
// and all Syzygy-modified binaries contain the ".syzygy" image section.
|
||||
// This is a very fast check, as it only looks at the image header.
|
||||
if ((image.GetImageSectionHeaderByName(".thunks") != NULL) &&
|
||||
(image.GetImageSectionHeaderByName(".syzygy") != NULL)) {
|
||||
state = INSTRUMENTED_IMAGE;
|
||||
} else {
|
||||
state = NON_INSTRUMENTED_IMAGE;
|
||||
}
|
||||
}
|
||||
DCHECK(state != UNINITIALIZED);
|
||||
|
||||
return state == INSTRUMENTED_IMAGE;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct FunctionSearchContext {
|
||||
const char* name;
|
||||
FARPROC function;
|
||||
};
|
||||
|
||||
// Callback function to PEImage::EnumImportChunks.
|
||||
bool FindResolutionFunctionInImports(
|
||||
const base::win::PEImage &image, const char* module_name,
|
||||
PIMAGE_THUNK_DATA unused_name_table, PIMAGE_THUNK_DATA import_address_table,
|
||||
PVOID cookie) {
|
||||
FunctionSearchContext* context =
|
||||
reinterpret_cast<FunctionSearchContext*>(cookie);
|
||||
|
||||
DCHECK(context);
|
||||
DCHECK(!context->function);
|
||||
|
||||
// Our import address table contains pointers to the functions we import
|
||||
// at this point. Let's retrieve the first such function and use it to
|
||||
// find the module this import was resolved to by the loader.
|
||||
const wchar_t* function_in_module =
|
||||
reinterpret_cast<const wchar_t*>(import_address_table->u1.Function);
|
||||
|
||||
// Retrieve the module by a function in the module.
|
||||
const DWORD kFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
|
||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
|
||||
HMODULE module = NULL;
|
||||
if (!::GetModuleHandleEx(kFlags, function_in_module, &module)) {
|
||||
// This can happen if someone IAT patches us to a thunk.
|
||||
return true;
|
||||
}
|
||||
|
||||
// See whether this module exports the function we're looking for.
|
||||
FARPROC exported_func = ::GetProcAddress(module, context->name);
|
||||
if (exported_func != NULL) {
|
||||
// We found it, return the function and terminate the enumeration.
|
||||
context->function = exported_func;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep going.
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename FunctionType>
|
||||
FunctionType FindFunctionInImports(const char* function_name) {
|
||||
if (!IsBinaryInstrumented())
|
||||
return NULL;
|
||||
|
||||
base::win::PEImage image(CURRENT_MODULE());
|
||||
|
||||
FunctionSearchContext ctx = { function_name, NULL };
|
||||
image.EnumImportChunks(FindResolutionFunctionInImports, &ctx);
|
||||
|
||||
return reinterpret_cast<FunctionType>(ctx.function);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc() {
|
||||
return FindFunctionInImports<ReturnAddressLocationResolver>(
|
||||
"ResolveReturnAddressLocation");
|
||||
}
|
||||
|
||||
DynamicFunctionEntryHook GetProfilerDynamicFunctionEntryHookFunc() {
|
||||
return FindFunctionInImports<DynamicFunctionEntryHook>(
|
||||
"OnDynamicFunctionEntry");
|
||||
}
|
||||
|
||||
AddDynamicSymbol GetProfilerAddDynamicSymbolFunc() {
|
||||
return FindFunctionInImports<AddDynamicSymbol>(
|
||||
"AddDynamicSymbol");
|
||||
}
|
||||
|
||||
MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc() {
|
||||
return FindFunctionInImports<MoveDynamicSymbol>(
|
||||
"MoveDynamicSymbol");
|
||||
}
|
||||
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
94
base/debug/profiler.h
Normal file
94
base/debug/profiler.h
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2012 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_DEBUG_PROFILER_H_
|
||||
#define BASE_DEBUG_PROFILER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/base_export.h"
|
||||
|
||||
// The Profiler functions allow usage of the underlying sampling based
|
||||
// profiler. If the application has not been built with the necessary
|
||||
// flags (-DENABLE_PROFILING and not -DNO_TCMALLOC) then these functions
|
||||
// are noops.
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// Start profiling with the supplied name.
|
||||
// {pid} will be replaced by the process' pid and {count} will be replaced
|
||||
// by the count of the profile run (starts at 1 with each process).
|
||||
BASE_EXPORT void StartProfiling(const std::string& name);
|
||||
|
||||
// Stop profiling and write out data.
|
||||
BASE_EXPORT void StopProfiling();
|
||||
|
||||
// Force data to be written to file.
|
||||
BASE_EXPORT void FlushProfiling();
|
||||
|
||||
// Returns true if process is being profiled.
|
||||
BASE_EXPORT bool BeingProfiled();
|
||||
|
||||
// Reset profiling after a fork, which disables timers.
|
||||
BASE_EXPORT void RestartProfilingAfterFork();
|
||||
|
||||
// Returns true iff this executable is instrumented with the Syzygy profiler.
|
||||
BASE_EXPORT bool IsBinaryInstrumented();
|
||||
|
||||
// Returns true iff this executable supports profiling.
|
||||
BASE_EXPORT bool IsProfilingSupported();
|
||||
|
||||
// There's a class of profilers that use "return address swizzling" to get a
|
||||
// hook on function exits. This class of profilers uses some form of entry hook,
|
||||
// like e.g. binary instrumentation, or a compiler flag, that calls a hook each
|
||||
// time a function is invoked. The hook then switches the return address on the
|
||||
// stack for the address of an exit hook function, and pushes the original
|
||||
// return address to a shadow stack of some type. When in due course the CPU
|
||||
// executes a return to the exit hook, the exit hook will do whatever work it
|
||||
// does on function exit, then arrange to return to the original return address.
|
||||
// This class of profiler does not play well with programs that look at the
|
||||
// return address, as does e.g. V8. V8 uses the return address to certain
|
||||
// runtime functions to find the JIT code that called it, and from there finds
|
||||
// the V8 data structures associated to the JS function involved.
|
||||
// A return address resolution function is used to fix this. It allows such
|
||||
// programs to resolve a location on stack where a return address originally
|
||||
// resided, to the shadow stack location where the profiler stashed it.
|
||||
typedef uintptr_t (*ReturnAddressLocationResolver)(
|
||||
uintptr_t return_addr_location);
|
||||
|
||||
// This type declaration must match V8's FunctionEntryHook.
|
||||
typedef void (*DynamicFunctionEntryHook)(uintptr_t function,
|
||||
uintptr_t return_addr_location);
|
||||
|
||||
// The functions below here are to support profiling V8-generated code.
|
||||
// V8 has provisions for generating a call to an entry hook for newly generated
|
||||
// JIT code, and it can push symbol information on code generation and advise
|
||||
// when the garbage collector moves code. The functions declarations below here
|
||||
// make glue between V8's facilities and a profiler.
|
||||
|
||||
// This type declaration must match V8's FunctionEntryHook.
|
||||
typedef void (*DynamicFunctionEntryHook)(uintptr_t function,
|
||||
uintptr_t return_addr_location);
|
||||
|
||||
typedef void (*AddDynamicSymbol)(const void* address,
|
||||
size_t length,
|
||||
const char* name,
|
||||
size_t name_len);
|
||||
typedef void (*MoveDynamicSymbol)(const void* address, const void* new_address);
|
||||
|
||||
|
||||
// If this binary is instrumented and the instrumentation supplies a function
|
||||
// for each of those purposes, find and return the function in question.
|
||||
// Otherwise returns NULL.
|
||||
BASE_EXPORT ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc();
|
||||
BASE_EXPORT DynamicFunctionEntryHook GetProfilerDynamicFunctionEntryHookFunc();
|
||||
BASE_EXPORT AddDynamicSymbol GetProfilerAddDynamicSymbolFunc();
|
||||
BASE_EXPORT MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc();
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_PROFILER_H_
|
||||
277
base/debug/stack_trace.cc
Normal file
277
base/debug/stack_trace.cc
Normal file
@@ -0,0 +1,277 @@
|
||||
// Copyright (c) 2012 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/stack_trace.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
|
||||
#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
#include <pthread.h>
|
||||
#include "base/process/process_handle.h"
|
||||
#include "base/threading/platform_thread.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_LINUX) && defined(__GLIBC__)
|
||||
extern "C" void* __libc_stack_end;
|
||||
#endif
|
||||
|
||||
#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
|
||||
#if defined(__arm__) && defined(__GNUC__) && !defined(__clang__)
|
||||
// GCC and LLVM generate slightly different frames on ARM, see
|
||||
// https://llvm.org/bugs/show_bug.cgi?id=18505 - LLVM generates
|
||||
// x86-compatible frame, while GCC needs adjustment.
|
||||
constexpr size_t kStackFrameAdjustment = sizeof(uintptr_t);
|
||||
#else
|
||||
constexpr size_t kStackFrameAdjustment = 0;
|
||||
#endif
|
||||
|
||||
uintptr_t GetNextStackFrame(uintptr_t fp) {
|
||||
return reinterpret_cast<const uintptr_t*>(fp)[0] - kStackFrameAdjustment;
|
||||
}
|
||||
|
||||
uintptr_t GetStackFramePC(uintptr_t fp) {
|
||||
return reinterpret_cast<const uintptr_t*>(fp)[1];
|
||||
}
|
||||
|
||||
bool IsStackFrameValid(uintptr_t fp, uintptr_t prev_fp, uintptr_t stack_end) {
|
||||
// With the stack growing downwards, older stack frame must be
|
||||
// at a greater address that the current one.
|
||||
if (fp <= prev_fp) return false;
|
||||
|
||||
// Assume huge stack frames are bogus.
|
||||
if (fp - prev_fp > 100000) return false;
|
||||
|
||||
// Check alignment.
|
||||
if (fp & (sizeof(uintptr_t) - 1)) return false;
|
||||
|
||||
if (stack_end) {
|
||||
// Both fp[0] and fp[1] must be within the stack.
|
||||
if (fp > stack_end - 2 * sizeof(uintptr_t)) return false;
|
||||
|
||||
// Additional check to filter out false positives.
|
||||
if (GetStackFramePC(fp) < 32768) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// ScanStackForNextFrame() scans the stack for a valid frame to allow unwinding
|
||||
// past system libraries. Only supported on Linux where system libraries are
|
||||
// usually in the middle of the trace:
|
||||
//
|
||||
// TraceStackFramePointers
|
||||
// <more frames from Chrome>
|
||||
// base::WorkSourceDispatch <-- unwinding stops (next frame is invalid),
|
||||
// g_main_context_dispatch ScanStackForNextFrame() is called
|
||||
// <more frames from glib>
|
||||
// g_main_context_iteration
|
||||
// base::MessagePumpGlib::Run <-- ScanStackForNextFrame() finds valid frame,
|
||||
// base::RunLoop::Run unwinding resumes
|
||||
// <more frames from Chrome>
|
||||
// __libc_start_main
|
||||
//
|
||||
// For stack scanning to be efficient it's very important for the thread to
|
||||
// be started by Chrome. In that case we naturally terminate unwinding once
|
||||
// we reach the origin of the stack (i.e. GetStackEnd()). If the thread is
|
||||
// not started by Chrome (e.g. Android's main thread), then we end up always
|
||||
// scanning area at the origin of the stack, wasting time and not finding any
|
||||
// frames (since Android libraries don't have frame pointers).
|
||||
//
|
||||
// ScanStackForNextFrame() returns 0 if it couldn't find a valid frame
|
||||
// (or if stack scanning is not supported on the current platform).
|
||||
uintptr_t ScanStackForNextFrame(uintptr_t fp, uintptr_t stack_end) {
|
||||
#if defined(OS_LINUX)
|
||||
// Enough to resume almost all prematurely terminated traces.
|
||||
constexpr size_t kMaxStackScanArea = 8192;
|
||||
|
||||
if (!stack_end) {
|
||||
// Too dangerous to scan without knowing where the stack ends.
|
||||
return 0;
|
||||
}
|
||||
|
||||
fp += sizeof(uintptr_t); // current frame is known to be invalid
|
||||
uintptr_t last_fp_to_scan = std::min(fp + kMaxStackScanArea, stack_end) -
|
||||
sizeof(uintptr_t);
|
||||
for (;fp <= last_fp_to_scan; fp += sizeof(uintptr_t)) {
|
||||
uintptr_t next_fp = GetNextStackFrame(fp);
|
||||
if (IsStackFrameValid(next_fp, fp, stack_end)) {
|
||||
// Check two frames deep. Since stack frame is just a pointer to
|
||||
// a higher address on the stack, it's relatively easy to find
|
||||
// something that looks like one. However two linked frames are
|
||||
// far less likely to be bogus.
|
||||
uintptr_t next2_fp = GetNextStackFrame(next_fp);
|
||||
if (IsStackFrameValid(next2_fp, next_fp, stack_end)) {
|
||||
return fp;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // defined(OS_LINUX)
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Links stack frame |fp| to |parent_fp|, so that during stack unwinding
|
||||
// TraceStackFramePointers() visits |parent_fp| after visiting |fp|.
|
||||
// Both frame pointers must come from __builtin_frame_address().
|
||||
// Returns previous stack frame |fp| was linked to.
|
||||
void* LinkStackFrames(void* fpp, void* parent_fp) {
|
||||
uintptr_t fp = reinterpret_cast<uintptr_t>(fpp) - kStackFrameAdjustment;
|
||||
void* prev_parent_fp = reinterpret_cast<void**>(fp)[0];
|
||||
reinterpret_cast<void**>(fp)[0] = parent_fp;
|
||||
return prev_parent_fp;
|
||||
}
|
||||
|
||||
#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
|
||||
} // namespace
|
||||
|
||||
#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
uintptr_t GetStackEnd() {
|
||||
#if defined(OS_ANDROID)
|
||||
// Bionic reads proc/maps on every call to pthread_getattr_np() when called
|
||||
// from the main thread. So we need to cache end of stack in that case to get
|
||||
// acceptable performance.
|
||||
// For all other threads pthread_getattr_np() is fast enough as it just reads
|
||||
// values from its pthread_t argument.
|
||||
static uintptr_t main_stack_end = 0;
|
||||
|
||||
bool is_main_thread = GetCurrentProcId() == PlatformThread::CurrentId();
|
||||
if (is_main_thread && main_stack_end) {
|
||||
return main_stack_end;
|
||||
}
|
||||
|
||||
uintptr_t stack_begin = 0;
|
||||
size_t stack_size = 0;
|
||||
pthread_attr_t attributes;
|
||||
int error = pthread_getattr_np(pthread_self(), &attributes);
|
||||
if (!error) {
|
||||
error = pthread_attr_getstack(
|
||||
&attributes, reinterpret_cast<void**>(&stack_begin), &stack_size);
|
||||
pthread_attr_destroy(&attributes);
|
||||
}
|
||||
DCHECK(!error);
|
||||
|
||||
uintptr_t stack_end = stack_begin + stack_size;
|
||||
if (is_main_thread) {
|
||||
main_stack_end = stack_end;
|
||||
}
|
||||
return stack_end; // 0 in case of error
|
||||
|
||||
#elif defined(OS_LINUX) && defined(__GLIBC__)
|
||||
|
||||
if (GetCurrentProcId() == PlatformThread::CurrentId()) {
|
||||
// For the main thread we have a shortcut.
|
||||
return reinterpret_cast<uintptr_t>(__libc_stack_end);
|
||||
}
|
||||
|
||||
// No easy way to get end of the stack for non-main threads,
|
||||
// see crbug.com/617730.
|
||||
#elif defined(OS_MACOSX)
|
||||
return reinterpret_cast<uintptr_t>(pthread_get_stackaddr_np(pthread_self()));
|
||||
#endif
|
||||
|
||||
// Don't know how to get end of the stack.
|
||||
return 0;
|
||||
}
|
||||
#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
|
||||
StackTrace::StackTrace() : StackTrace(arraysize(trace_)) {}
|
||||
|
||||
StackTrace::StackTrace(const void* const* trace, size_t count) {
|
||||
count = std::min(count, arraysize(trace_));
|
||||
if (count)
|
||||
memcpy(trace_, trace, count * sizeof(trace_[0]));
|
||||
count_ = count;
|
||||
}
|
||||
|
||||
const void *const *StackTrace::Addresses(size_t* count) const {
|
||||
*count = count_;
|
||||
if (count_)
|
||||
return trace_;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string StackTrace::ToString() const {
|
||||
std::stringstream stream;
|
||||
#if !defined(__UCLIBC__) && !defined(_AIX)
|
||||
OutputToStream(&stream);
|
||||
#endif
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
|
||||
size_t TraceStackFramePointers(const void** out_trace,
|
||||
size_t max_depth,
|
||||
size_t skip_initial) {
|
||||
// Usage of __builtin_frame_address() enables frame pointers in this
|
||||
// function even if they are not enabled globally. So 'fp' will always
|
||||
// be valid.
|
||||
uintptr_t fp = reinterpret_cast<uintptr_t>(__builtin_frame_address(0)) -
|
||||
kStackFrameAdjustment;
|
||||
|
||||
uintptr_t stack_end = GetStackEnd();
|
||||
|
||||
size_t depth = 0;
|
||||
while (depth < max_depth) {
|
||||
if (skip_initial != 0) {
|
||||
skip_initial--;
|
||||
} else {
|
||||
out_trace[depth++] = reinterpret_cast<const void*>(GetStackFramePC(fp));
|
||||
}
|
||||
|
||||
uintptr_t next_fp = GetNextStackFrame(fp);
|
||||
if (IsStackFrameValid(next_fp, fp, stack_end)) {
|
||||
fp = next_fp;
|
||||
continue;
|
||||
}
|
||||
|
||||
next_fp = ScanStackForNextFrame(fp, stack_end);
|
||||
if (next_fp) {
|
||||
fp = next_fp;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Failed to find next frame.
|
||||
break;
|
||||
}
|
||||
|
||||
return depth;
|
||||
}
|
||||
|
||||
ScopedStackFrameLinker::ScopedStackFrameLinker(void* fp, void* parent_fp)
|
||||
: fp_(fp),
|
||||
parent_fp_(parent_fp),
|
||||
original_parent_fp_(LinkStackFrames(fp, parent_fp)) {}
|
||||
|
||||
ScopedStackFrameLinker::~ScopedStackFrameLinker() {
|
||||
void* previous_parent_fp = LinkStackFrames(fp_, original_parent_fp_);
|
||||
CHECK_EQ(parent_fp_, previous_parent_fp)
|
||||
<< "Stack frame's parent pointer has changed!";
|
||||
}
|
||||
|
||||
#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
197
base/debug/stack_trace.h
Normal file
197
base/debug/stack_trace.h
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) 2012 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_DEBUG_STACK_TRACE_H_
|
||||
#define BASE_DEBUG_STACK_TRACE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#include "base/base_export.h"
|
||||
#include "base/debug/debugging_flags.h"
|
||||
#include "base/macros.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
struct _EXCEPTION_POINTERS;
|
||||
struct _CONTEXT;
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
// Enables stack dump to console output on exception and signals.
|
||||
// When enabled, the process will quit immediately. This is meant to be used in
|
||||
// unit_tests only! This is not thread-safe: only call from main thread.
|
||||
// In sandboxed processes, this has to be called before the sandbox is turned
|
||||
// on.
|
||||
// Calling this function on Linux opens /proc/self/maps and caches its
|
||||
// contents. In non-official builds, this function also opens the object files
|
||||
// that are loaded in memory and caches their file descriptors (this cannot be
|
||||
// done in official builds because it has security implications).
|
||||
BASE_EXPORT bool EnableInProcessStackDumping();
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
BASE_EXPORT void SetStackDumpFirstChanceCallback(bool (*handler)(int,
|
||||
void*,
|
||||
void*));
|
||||
#endif
|
||||
|
||||
// Returns end of the stack, or 0 if we couldn't get it.
|
||||
#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
BASE_EXPORT uintptr_t GetStackEnd();
|
||||
#endif
|
||||
|
||||
// A stacktrace can be helpful in debugging. For example, you can include a
|
||||
// stacktrace member in a object (probably around #ifndef NDEBUG) so that you
|
||||
// can later see where the given object was created from.
|
||||
class BASE_EXPORT StackTrace {
|
||||
public:
|
||||
// Creates a stacktrace from the current location.
|
||||
StackTrace();
|
||||
|
||||
// Creates a stacktrace from the current location, of up to |count| entries.
|
||||
// |count| will be limited to at most |kMaxTraces|.
|
||||
explicit StackTrace(size_t count);
|
||||
|
||||
// Creates a stacktrace from an existing array of instruction
|
||||
// pointers (such as returned by Addresses()). |count| will be
|
||||
// limited to at most |kMaxTraces|.
|
||||
StackTrace(const void* const* trace, size_t count);
|
||||
|
||||
#if defined(OS_WIN)
|
||||
// Creates a stacktrace for an exception.
|
||||
// Note: this function will throw an import not found (StackWalk64) exception
|
||||
// on system without dbghelp 5.1.
|
||||
StackTrace(_EXCEPTION_POINTERS* exception_pointers);
|
||||
StackTrace(const _CONTEXT* context);
|
||||
#endif
|
||||
|
||||
// Copying and assignment are allowed with the default functions.
|
||||
|
||||
// Gets an array of instruction pointer values. |*count| will be set to the
|
||||
// number of elements in the returned array.
|
||||
const void* const* Addresses(size_t* count) const;
|
||||
|
||||
// Prints the stack trace to stderr.
|
||||
void Print() const;
|
||||
|
||||
#if !defined(__UCLIBC__) & !defined(_AIX)
|
||||
// Resolves backtrace to symbols and write to stream.
|
||||
void OutputToStream(std::ostream* os) const;
|
||||
#endif
|
||||
|
||||
// Resolves backtrace to symbols and returns as string.
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
#if defined(OS_WIN)
|
||||
void InitTrace(const _CONTEXT* context_record);
|
||||
#endif
|
||||
|
||||
// From http://msdn.microsoft.com/en-us/library/bb204633.aspx,
|
||||
// the sum of FramesToSkip and FramesToCapture must be less than 63,
|
||||
// so set it to 62. Even if on POSIX it could be a larger value, it usually
|
||||
// doesn't give much more information.
|
||||
static const int kMaxTraces = 62;
|
||||
|
||||
void* trace_[kMaxTraces];
|
||||
|
||||
// The number of valid frames in |trace_|.
|
||||
size_t count_;
|
||||
};
|
||||
|
||||
#if BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
// Traces the stack by using frame pointers. This function is faster but less
|
||||
// reliable than StackTrace. It should work for debug and profiling builds,
|
||||
// but not for release builds (although there are some exceptions).
|
||||
//
|
||||
// Writes at most |max_depth| frames (instruction pointers) into |out_trace|
|
||||
// after skipping |skip_initial| frames. Note that the function itself is not
|
||||
// added to the trace so |skip_initial| should be 0 in most cases.
|
||||
// Returns number of frames written.
|
||||
BASE_EXPORT size_t TraceStackFramePointers(const void** out_trace,
|
||||
size_t max_depth,
|
||||
size_t skip_initial);
|
||||
|
||||
// Links stack frame |fp| to |parent_fp|, so that during stack unwinding
|
||||
// TraceStackFramePointers() visits |parent_fp| after visiting |fp|.
|
||||
// Both frame pointers must come from __builtin_frame_address().
|
||||
// Destructor restores original linkage of |fp| to avoid corrupting caller's
|
||||
// frame register on return.
|
||||
//
|
||||
// This class can be used to repair broken stack frame chain in cases
|
||||
// when execution flow goes into code built without frame pointers:
|
||||
//
|
||||
// void DoWork() {
|
||||
// Call_SomeLibrary();
|
||||
// }
|
||||
// static __thread void* g_saved_fp;
|
||||
// void Call_SomeLibrary() {
|
||||
// g_saved_fp = __builtin_frame_address(0);
|
||||
// some_library_call(...); // indirectly calls SomeLibrary_Callback()
|
||||
// }
|
||||
// void SomeLibrary_Callback() {
|
||||
// ScopedStackFrameLinker linker(__builtin_frame_address(0), g_saved_fp);
|
||||
// ...
|
||||
// TraceStackFramePointers(...);
|
||||
// }
|
||||
//
|
||||
// This produces the following trace:
|
||||
//
|
||||
// #0 SomeLibrary_Callback()
|
||||
// #1 <address of the code inside SomeLibrary that called #0>
|
||||
// #2 DoWork()
|
||||
// ...rest of the trace...
|
||||
//
|
||||
// SomeLibrary doesn't use frame pointers, so when SomeLibrary_Callback()
|
||||
// is called, stack frame register contains bogus value that becomes callback'
|
||||
// parent frame address. Without ScopedStackFrameLinker unwinding would've
|
||||
// stopped at that bogus frame address yielding just two first frames (#0, #1).
|
||||
// ScopedStackFrameLinker overwrites callback's parent frame address with
|
||||
// Call_SomeLibrary's frame, so unwinder produces full trace without even
|
||||
// noticing that stack frame chain was broken.
|
||||
class BASE_EXPORT ScopedStackFrameLinker {
|
||||
public:
|
||||
ScopedStackFrameLinker(void* fp, void* parent_fp);
|
||||
~ScopedStackFrameLinker();
|
||||
|
||||
private:
|
||||
void* fp_;
|
||||
void* parent_fp_;
|
||||
void* original_parent_fp_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedStackFrameLinker);
|
||||
};
|
||||
|
||||
#endif // BUILDFLAG(CAN_UNWIND_WITH_FRAME_POINTERS)
|
||||
|
||||
namespace internal {
|
||||
|
||||
#if defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
// POSIX doesn't define any async-signal safe function for converting
|
||||
// an integer to ASCII. We'll have to define our own version.
|
||||
// itoa_r() converts a (signed) integer to ASCII. It returns "buf", if the
|
||||
// conversion was successful or NULL otherwise. It never writes more than "sz"
|
||||
// bytes. Output will be truncated as needed, and a NUL character is always
|
||||
// appended.
|
||||
BASE_EXPORT char *itoa_r(intptr_t i,
|
||||
char *buf,
|
||||
size_t sz,
|
||||
int base,
|
||||
size_t padding);
|
||||
#endif // defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_STACK_TRACE_H_
|
||||
134
base/debug/stack_trace_android.cc
Normal file
134
base/debug/stack_trace_android.cc
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2012 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/stack_trace.h"
|
||||
|
||||
#include <android/log.h>
|
||||
#include <stddef.h>
|
||||
#include <unwind.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
|
||||
#include "base/debug/proc_maps_linux.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
|
||||
#ifdef __LP64__
|
||||
#define FMT_ADDR "0x%016lx"
|
||||
#else
|
||||
#define FMT_ADDR "0x%08x"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
struct StackCrawlState {
|
||||
StackCrawlState(uintptr_t* frames, size_t max_depth)
|
||||
: frames(frames),
|
||||
frame_count(0),
|
||||
max_depth(max_depth),
|
||||
have_skipped_self(false) {}
|
||||
|
||||
uintptr_t* frames;
|
||||
size_t frame_count;
|
||||
size_t max_depth;
|
||||
bool have_skipped_self;
|
||||
};
|
||||
|
||||
_Unwind_Reason_Code TraceStackFrame(_Unwind_Context* context, void* arg) {
|
||||
StackCrawlState* state = static_cast<StackCrawlState*>(arg);
|
||||
uintptr_t ip = _Unwind_GetIP(context);
|
||||
|
||||
// The first stack frame is this function itself. Skip it.
|
||||
if (ip != 0 && !state->have_skipped_self) {
|
||||
state->have_skipped_self = true;
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
|
||||
state->frames[state->frame_count++] = ip;
|
||||
if (state->frame_count >= state->max_depth)
|
||||
return _URC_END_OF_STACK;
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
bool EnableInProcessStackDumping() {
|
||||
// When running in an application, our code typically expects SIGPIPE
|
||||
// to be ignored. Therefore, when testing that same code, it should run
|
||||
// with SIGPIPE ignored as well.
|
||||
// TODO(phajdan.jr): De-duplicate this SIGPIPE code.
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_handler = SIG_IGN;
|
||||
sigemptyset(&action.sa_mask);
|
||||
return (sigaction(SIGPIPE, &action, NULL) == 0);
|
||||
}
|
||||
|
||||
StackTrace::StackTrace(size_t count) {
|
||||
count = std::min(arraysize(trace_), count);
|
||||
|
||||
StackCrawlState state(reinterpret_cast<uintptr_t*>(trace_), count);
|
||||
_Unwind_Backtrace(&TraceStackFrame, &state);
|
||||
count_ = state.frame_count;
|
||||
}
|
||||
|
||||
void StackTrace::Print() const {
|
||||
std::string backtrace = ToString();
|
||||
__android_log_write(ANDROID_LOG_ERROR, "chromium", backtrace.c_str());
|
||||
}
|
||||
|
||||
// NOTE: Native libraries in APKs are stripped before installing. Print out the
|
||||
// relocatable address and library names so host computers can use tools to
|
||||
// symbolize and demangle (e.g., addr2line, c++filt).
|
||||
void StackTrace::OutputToStream(std::ostream* os) const {
|
||||
std::string proc_maps;
|
||||
std::vector<MappedMemoryRegion> regions;
|
||||
// Allow IO to read /proc/self/maps. Reading this file doesn't hit the disk
|
||||
// since it lives in procfs, and this is currently used to print a stack trace
|
||||
// on fatal log messages in debug builds only. If the restriction is enabled
|
||||
// then it will recursively trigger fatal failures when this enters on the
|
||||
// UI thread.
|
||||
base::ThreadRestrictions::ScopedAllowIO allow_io;
|
||||
if (!ReadProcMaps(&proc_maps)) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "chromium", "Failed to read /proc/self/maps");
|
||||
} else if (!ParseProcMaps(proc_maps, ®ions)) {
|
||||
__android_log_write(
|
||||
ANDROID_LOG_ERROR, "chromium", "Failed to parse /proc/self/maps");
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count_; ++i) {
|
||||
// Subtract one as return address of function may be in the next
|
||||
// function when a function is annotated as noreturn.
|
||||
uintptr_t address = reinterpret_cast<uintptr_t>(trace_[i]) - 1;
|
||||
|
||||
std::vector<MappedMemoryRegion>::iterator iter = regions.begin();
|
||||
while (iter != regions.end()) {
|
||||
if (address >= iter->start && address < iter->end &&
|
||||
!iter->path.empty()) {
|
||||
break;
|
||||
}
|
||||
++iter;
|
||||
}
|
||||
|
||||
*os << base::StringPrintf("#%02zd " FMT_ADDR " ", i, address);
|
||||
|
||||
if (iter != regions.end()) {
|
||||
uintptr_t rel_pc = address - iter->start + iter->offset;
|
||||
const char* path = iter->path.c_str();
|
||||
*os << base::StringPrintf("%s+" FMT_ADDR, path, rel_pc);
|
||||
} else {
|
||||
*os << "<unknown>";
|
||||
}
|
||||
|
||||
*os << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
212
base/debug/stack_trace_fuchsia.cc
Normal file
212
base/debug/stack_trace_fuchsia.cc
Normal file
@@ -0,0 +1,212 @@
|
||||
// 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 "base/debug/stack_trace.h"
|
||||
|
||||
#include <link.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <threads.h>
|
||||
#include <unwind.h>
|
||||
#include <zircon/crashlogger.h>
|
||||
#include <zircon/process.h>
|
||||
#include <zircon/syscalls.h>
|
||||
#include <zircon/syscalls/definitions.h>
|
||||
#include <zircon/syscalls/port.h>
|
||||
#include <zircon/types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include "base/logging.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
const char kProcessNamePrefix[] = "app:";
|
||||
const size_t kProcessNamePrefixLen = arraysize(kProcessNamePrefix) - 1;
|
||||
|
||||
struct BacktraceData {
|
||||
void** trace_array;
|
||||
size_t* count;
|
||||
size_t max;
|
||||
};
|
||||
|
||||
_Unwind_Reason_Code UnwindStore(struct _Unwind_Context* context,
|
||||
void* user_data) {
|
||||
BacktraceData* data = reinterpret_cast<BacktraceData*>(user_data);
|
||||
uintptr_t pc = _Unwind_GetIP(context);
|
||||
data->trace_array[*data->count] = reinterpret_cast<void*>(pc);
|
||||
*data->count += 1;
|
||||
if (*data->count == data->max)
|
||||
return _URC_END_OF_STACK;
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
|
||||
// Stores and queries debugging symbol map info for the current process.
|
||||
class SymbolMap {
|
||||
public:
|
||||
struct Entry {
|
||||
void* addr;
|
||||
char name[ZX_MAX_NAME_LEN + kProcessNamePrefixLen];
|
||||
};
|
||||
|
||||
SymbolMap();
|
||||
~SymbolMap() = default;
|
||||
|
||||
// Gets the symbol map entry for |address|. Returns null if no entry could be
|
||||
// found for the address, or if the symbol map could not be queried.
|
||||
Entry* GetForAddress(void* address);
|
||||
|
||||
private:
|
||||
static const size_t kMaxMapEntries = 64;
|
||||
|
||||
void Populate();
|
||||
|
||||
// Sorted in descending order by address, for lookup purposes.
|
||||
Entry entries_[kMaxMapEntries];
|
||||
|
||||
size_t count_ = 0;
|
||||
bool valid_ = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SymbolMap);
|
||||
};
|
||||
|
||||
SymbolMap::SymbolMap() {
|
||||
Populate();
|
||||
}
|
||||
|
||||
SymbolMap::Entry* SymbolMap::GetForAddress(void* address) {
|
||||
if (!valid_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Working backwards in the address space, return the first map entry whose
|
||||
// address comes before |address| (thereby enclosing it.)
|
||||
for (size_t i = 0; i < count_; ++i) {
|
||||
if (address >= entries_[i].addr) {
|
||||
return &entries_[i];
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void SymbolMap::Populate() {
|
||||
zx_handle_t process = zx_process_self();
|
||||
|
||||
// Try to fetch the name of the process' main executable, which was set as the
|
||||
// name of the |process| kernel object.
|
||||
// TODO(wez): Object names can only have up to ZX_MAX_NAME_LEN characters, so
|
||||
// if we keep hitting problems with truncation, find a way to plumb argv[0]
|
||||
// through to here instead, e.g. using CommandLine::GetProgramName().
|
||||
char app_name[arraysize(SymbolMap::Entry::name)];
|
||||
strcpy(app_name, kProcessNamePrefix);
|
||||
zx_status_t status = zx_object_get_property(
|
||||
process, ZX_PROP_NAME, app_name + kProcessNamePrefixLen,
|
||||
sizeof(app_name) - kProcessNamePrefixLen);
|
||||
if (status != ZX_OK) {
|
||||
DPLOG(WARNING)
|
||||
<< "Couldn't get name, falling back to 'app' for program name: "
|
||||
<< status;
|
||||
strlcat(app_name, "app", sizeof(app_name));
|
||||
}
|
||||
|
||||
// Retrieve the debug info struct.
|
||||
constexpr size_t map_capacity = sizeof(entries_);
|
||||
uintptr_t debug_addr;
|
||||
status = zx_object_get_property(process, ZX_PROP_PROCESS_DEBUG_ADDR,
|
||||
&debug_addr, sizeof(debug_addr));
|
||||
if (status != ZX_OK) {
|
||||
DPLOG(ERROR) << "Couldn't get symbol map for process: " << status;
|
||||
return;
|
||||
}
|
||||
r_debug* debug_info = reinterpret_cast<r_debug*>(debug_addr);
|
||||
|
||||
// Get the link map from the debug info struct.
|
||||
link_map* lmap = reinterpret_cast<link_map*>(debug_info->r_map);
|
||||
if (!lmap) {
|
||||
DPLOG(ERROR) << "Null link_map for process.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy the contents of the link map linked list to |entries_|.
|
||||
while (lmap != nullptr) {
|
||||
if (count_ == map_capacity) {
|
||||
break;
|
||||
}
|
||||
SymbolMap::Entry* next_entry = &entries_[count_];
|
||||
count_++;
|
||||
|
||||
next_entry->addr = reinterpret_cast<void*>(lmap->l_addr);
|
||||
char* name_to_use = lmap->l_name[0] ? lmap->l_name : app_name;
|
||||
strlcpy(next_entry->name, name_to_use, sizeof(next_entry->name));
|
||||
lmap = lmap->l_next;
|
||||
}
|
||||
|
||||
std::sort(
|
||||
&entries_[0], &entries_[count_ - 1],
|
||||
[](const Entry& a, const Entry& b) -> bool { return a.addr >= b.addr; });
|
||||
|
||||
valid_ = true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
bool EnableInProcessStackDumping() {
|
||||
// StackTrace works to capture the current stack (e.g. for diagnostics added
|
||||
// to code), but for local capture and print of backtraces, we just let the
|
||||
// system crashlogger take over. It handles printing out a nicely formatted
|
||||
// backtrace with dso information, relative offsets, etc. that we can then
|
||||
// filter with addr2line in the run script to get file/line info.
|
||||
return true;
|
||||
}
|
||||
|
||||
StackTrace::StackTrace(size_t count) : count_(0) {
|
||||
BacktraceData data = {&trace_[0], &count_,
|
||||
std::min(count, static_cast<size_t>(kMaxTraces))};
|
||||
_Unwind_Backtrace(&UnwindStore, &data);
|
||||
}
|
||||
|
||||
void StackTrace::Print() const {
|
||||
OutputToStream(&std::cerr);
|
||||
}
|
||||
|
||||
// Sample stack trace output is designed to be similar to Fuchsia's crashlogger:
|
||||
// bt#00: pc 0x1527a058aa00 (app:/system/base_unittests,0x18bda00)
|
||||
// bt#01: pc 0x1527a0254b5c (app:/system/base_unittests,0x1587b5c)
|
||||
// bt#02: pc 0x15279f446ece (app:/system/base_unittests,0x779ece)
|
||||
// ...
|
||||
// bt#21: pc 0x1527a05b51b4 (app:/system/base_unittests,0x18e81b4)
|
||||
// bt#22: pc 0x54fdbf3593de (libc.so,0x1c3de)
|
||||
// bt#23: end
|
||||
void StackTrace::OutputToStream(std::ostream* os) const {
|
||||
SymbolMap map;
|
||||
|
||||
size_t i = 0;
|
||||
for (; (i < count_) && os->good(); ++i) {
|
||||
SymbolMap::Entry* entry = map.GetForAddress(trace_[i]);
|
||||
if (entry) {
|
||||
size_t offset = reinterpret_cast<uintptr_t>(trace_[i]) -
|
||||
reinterpret_cast<uintptr_t>(entry->addr);
|
||||
*os << "bt#" << std::setw(2) << std::setfill('0') << i << std::setw(0)
|
||||
<< ": pc " << trace_[i] << " (" << entry->name << ",0x" << std::hex
|
||||
<< offset << std::dec << std::setw(0) << ")\n";
|
||||
} else {
|
||||
// Fallback if the DSO map isn't available.
|
||||
// Logged PC values are absolute memory addresses, and the shared object
|
||||
// name is not emitted.
|
||||
*os << "bt#" << std::setw(2) << std::setfill('0') << i << std::setw(0)
|
||||
<< ": pc " << trace_[i] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
(*os) << "bt#" << std::setw(2) << i << ": end\n";
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
891
base/debug/stack_trace_posix.cc
Normal file
891
base/debug/stack_trace_posix.cc
Normal file
@@ -0,0 +1,891 @@
|
||||
// Copyright (c) 2012 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/stack_trace.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if !defined(USE_SYMBOLIZE)
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
#if !defined(__UCLIBC__) && !defined(_AIX)
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
#include "base/debug/proc_maps_linux.h"
|
||||
#endif
|
||||
|
||||
#include "base/cfi_flags.h"
|
||||
#include "base/debug/debugger.h"
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/free_deleter.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/posix/eintr_wrapper.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
#include "base/third_party/symbolize/symbolize.h"
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
volatile sig_atomic_t in_signal_handler = 0;
|
||||
|
||||
bool (*try_handle_signal)(int, void*, void*) = nullptr;
|
||||
|
||||
#if !defined(USE_SYMBOLIZE)
|
||||
// The prefix used for mangled symbols, per the Itanium C++ ABI:
|
||||
// http://www.codesourcery.com/cxx-abi/abi.html#mangling
|
||||
const char kMangledSymbolPrefix[] = "_Z";
|
||||
|
||||
// Characters that can be used for symbols, generated by Ruby:
|
||||
// (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join
|
||||
const char kSymbolCharacters[] =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||
#endif // !defined(USE_SYMBOLIZE)
|
||||
|
||||
#if !defined(USE_SYMBOLIZE)
|
||||
// Demangles C++ symbols in the given text. Example:
|
||||
//
|
||||
// "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]"
|
||||
// =>
|
||||
// "out/Debug/base_unittests(StackTrace::StackTrace()+0x20) [0x817778c]"
|
||||
void DemangleSymbols(std::string* text) {
|
||||
// Note: code in this function is NOT async-signal safe (std::string uses
|
||||
// malloc internally).
|
||||
|
||||
#if !defined(__UCLIBC__) && !defined(_AIX)
|
||||
std::string::size_type search_from = 0;
|
||||
while (search_from < text->size()) {
|
||||
// Look for the start of a mangled symbol, from search_from.
|
||||
std::string::size_type mangled_start =
|
||||
text->find(kMangledSymbolPrefix, search_from);
|
||||
if (mangled_start == std::string::npos) {
|
||||
break; // Mangled symbol not found.
|
||||
}
|
||||
|
||||
// Look for the end of the mangled symbol.
|
||||
std::string::size_type mangled_end =
|
||||
text->find_first_not_of(kSymbolCharacters, mangled_start);
|
||||
if (mangled_end == std::string::npos) {
|
||||
mangled_end = text->size();
|
||||
}
|
||||
std::string mangled_symbol =
|
||||
text->substr(mangled_start, mangled_end - mangled_start);
|
||||
|
||||
// Try to demangle the mangled symbol candidate.
|
||||
int status = 0;
|
||||
std::unique_ptr<char, base::FreeDeleter> demangled_symbol(
|
||||
abi::__cxa_demangle(mangled_symbol.c_str(), nullptr, 0, &status));
|
||||
if (status == 0) { // Demangling is successful.
|
||||
// Remove the mangled symbol.
|
||||
text->erase(mangled_start, mangled_end - mangled_start);
|
||||
// Insert the demangled symbol.
|
||||
text->insert(mangled_start, demangled_symbol.get());
|
||||
// Next time, we'll start right after the demangled symbol we inserted.
|
||||
search_from = mangled_start + strlen(demangled_symbol.get());
|
||||
} else {
|
||||
// Failed to demangle. Retry after the "_Z" we just found.
|
||||
search_from = mangled_start + 2;
|
||||
}
|
||||
}
|
||||
#endif // !defined(__UCLIBC__) && !defined(_AIX)
|
||||
}
|
||||
#endif // !defined(USE_SYMBOLIZE)
|
||||
|
||||
class BacktraceOutputHandler {
|
||||
public:
|
||||
virtual void HandleOutput(const char* output) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~BacktraceOutputHandler() = default;
|
||||
};
|
||||
|
||||
#if !defined(__UCLIBC__) && !defined(_AIX)
|
||||
void OutputPointer(void* pointer, BacktraceOutputHandler* handler) {
|
||||
// This should be more than enough to store a 64-bit number in hex:
|
||||
// 16 hex digits + 1 for null-terminator.
|
||||
char buf[17] = { '\0' };
|
||||
handler->HandleOutput("0x");
|
||||
internal::itoa_r(reinterpret_cast<intptr_t>(pointer),
|
||||
buf, sizeof(buf), 16, 12);
|
||||
handler->HandleOutput(buf);
|
||||
}
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
void OutputFrameId(intptr_t frame_id, BacktraceOutputHandler* handler) {
|
||||
// Max unsigned 64-bit number in decimal has 20 digits (18446744073709551615).
|
||||
// Hence, 30 digits should be more than enough to represent it in decimal
|
||||
// (including the null-terminator).
|
||||
char buf[30] = { '\0' };
|
||||
handler->HandleOutput("#");
|
||||
internal::itoa_r(frame_id, buf, sizeof(buf), 10, 1);
|
||||
handler->HandleOutput(buf);
|
||||
}
|
||||
#endif // defined(USE_SYMBOLIZE)
|
||||
|
||||
void ProcessBacktrace(void *const *trace,
|
||||
size_t size,
|
||||
BacktraceOutputHandler* handler) {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
OutputFrameId(i, handler);
|
||||
handler->HandleOutput(" ");
|
||||
OutputPointer(trace[i], handler);
|
||||
handler->HandleOutput(" ");
|
||||
|
||||
char buf[1024] = { '\0' };
|
||||
|
||||
// Subtract by one as return address of function may be in the next
|
||||
// function when a function is annotated as noreturn.
|
||||
void* address = static_cast<char*>(trace[i]) - 1;
|
||||
if (google::Symbolize(address, buf, sizeof(buf)))
|
||||
handler->HandleOutput(buf);
|
||||
else
|
||||
handler->HandleOutput("<unknown>");
|
||||
|
||||
handler->HandleOutput("\n");
|
||||
}
|
||||
#else
|
||||
bool printed = false;
|
||||
|
||||
// Below part is async-signal unsafe (uses malloc), so execute it only
|
||||
// when we are not executing the signal handler.
|
||||
if (in_signal_handler == 0) {
|
||||
std::unique_ptr<char*, FreeDeleter> trace_symbols(
|
||||
backtrace_symbols(trace, size));
|
||||
if (trace_symbols.get()) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
std::string trace_symbol = trace_symbols.get()[i];
|
||||
DemangleSymbols(&trace_symbol);
|
||||
handler->HandleOutput(trace_symbol.c_str());
|
||||
handler->HandleOutput("\n");
|
||||
}
|
||||
|
||||
printed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!printed) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
handler->HandleOutput(" [");
|
||||
OutputPointer(trace[i], handler);
|
||||
handler->HandleOutput("]\n");
|
||||
}
|
||||
}
|
||||
#endif // defined(USE_SYMBOLIZE)
|
||||
}
|
||||
#endif // !defined(__UCLIBC__) && !defined(_AIX)
|
||||
|
||||
void PrintToStderr(const char* output) {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
ignore_result(HANDLE_EINTR(write(STDERR_FILENO, output, strlen(output))));
|
||||
}
|
||||
|
||||
void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) {
|
||||
// NOTE: This code MUST be async-signal safe.
|
||||
// NO malloc or stdio is allowed here.
|
||||
|
||||
// Give a registered callback a chance to recover from this signal
|
||||
//
|
||||
// V8 uses guard regions to guarantee memory safety in WebAssembly. This means
|
||||
// some signals might be expected if they originate from Wasm code while
|
||||
// accessing the guard region. We give V8 the chance to handle and recover
|
||||
// from these signals first.
|
||||
if (try_handle_signal != nullptr &&
|
||||
try_handle_signal(signal, info, void_context)) {
|
||||
// The first chance handler took care of this. The SA_RESETHAND flag
|
||||
// replaced this signal handler upon entry, but we want to stay
|
||||
// installed. Thus, we reinstall ourselves before returning.
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_flags = SA_RESETHAND | SA_SIGINFO;
|
||||
action.sa_sigaction = &StackDumpSignalHandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
sigaction(signal, &action, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Record the fact that we are in the signal handler now, so that the rest
|
||||
// of StackTrace can behave in an async-signal-safe manner.
|
||||
in_signal_handler = 1;
|
||||
|
||||
if (BeingDebugged())
|
||||
BreakDebugger();
|
||||
|
||||
PrintToStderr("Received signal ");
|
||||
char buf[1024] = { 0 };
|
||||
internal::itoa_r(signal, buf, sizeof(buf), 10, 0);
|
||||
PrintToStderr(buf);
|
||||
if (signal == SIGBUS) {
|
||||
if (info->si_code == BUS_ADRALN)
|
||||
PrintToStderr(" BUS_ADRALN ");
|
||||
else if (info->si_code == BUS_ADRERR)
|
||||
PrintToStderr(" BUS_ADRERR ");
|
||||
else if (info->si_code == BUS_OBJERR)
|
||||
PrintToStderr(" BUS_OBJERR ");
|
||||
else
|
||||
PrintToStderr(" <unknown> ");
|
||||
} else if (signal == SIGFPE) {
|
||||
if (info->si_code == FPE_FLTDIV)
|
||||
PrintToStderr(" FPE_FLTDIV ");
|
||||
else if (info->si_code == FPE_FLTINV)
|
||||
PrintToStderr(" FPE_FLTINV ");
|
||||
else if (info->si_code == FPE_FLTOVF)
|
||||
PrintToStderr(" FPE_FLTOVF ");
|
||||
else if (info->si_code == FPE_FLTRES)
|
||||
PrintToStderr(" FPE_FLTRES ");
|
||||
else if (info->si_code == FPE_FLTSUB)
|
||||
PrintToStderr(" FPE_FLTSUB ");
|
||||
else if (info->si_code == FPE_FLTUND)
|
||||
PrintToStderr(" FPE_FLTUND ");
|
||||
else if (info->si_code == FPE_INTDIV)
|
||||
PrintToStderr(" FPE_INTDIV ");
|
||||
else if (info->si_code == FPE_INTOVF)
|
||||
PrintToStderr(" FPE_INTOVF ");
|
||||
else
|
||||
PrintToStderr(" <unknown> ");
|
||||
} else if (signal == SIGILL) {
|
||||
if (info->si_code == ILL_BADSTK)
|
||||
PrintToStderr(" ILL_BADSTK ");
|
||||
else if (info->si_code == ILL_COPROC)
|
||||
PrintToStderr(" ILL_COPROC ");
|
||||
else if (info->si_code == ILL_ILLOPN)
|
||||
PrintToStderr(" ILL_ILLOPN ");
|
||||
else if (info->si_code == ILL_ILLADR)
|
||||
PrintToStderr(" ILL_ILLADR ");
|
||||
else if (info->si_code == ILL_ILLTRP)
|
||||
PrintToStderr(" ILL_ILLTRP ");
|
||||
else if (info->si_code == ILL_PRVOPC)
|
||||
PrintToStderr(" ILL_PRVOPC ");
|
||||
else if (info->si_code == ILL_PRVREG)
|
||||
PrintToStderr(" ILL_PRVREG ");
|
||||
else
|
||||
PrintToStderr(" <unknown> ");
|
||||
} else if (signal == SIGSEGV) {
|
||||
if (info->si_code == SEGV_MAPERR)
|
||||
PrintToStderr(" SEGV_MAPERR ");
|
||||
else if (info->si_code == SEGV_ACCERR)
|
||||
PrintToStderr(" SEGV_ACCERR ");
|
||||
else
|
||||
PrintToStderr(" <unknown> ");
|
||||
}
|
||||
if (signal == SIGBUS || signal == SIGFPE ||
|
||||
signal == SIGILL || signal == SIGSEGV) {
|
||||
internal::itoa_r(reinterpret_cast<intptr_t>(info->si_addr),
|
||||
buf, sizeof(buf), 16, 12);
|
||||
PrintToStderr(buf);
|
||||
}
|
||||
PrintToStderr("\n");
|
||||
|
||||
#if BUILDFLAG(CFI_ENFORCEMENT_TRAP)
|
||||
if (signal == SIGILL && info->si_code == ILL_ILLOPN) {
|
||||
PrintToStderr(
|
||||
"CFI: Most likely a control flow integrity violation; for more "
|
||||
"information see:\n");
|
||||
PrintToStderr(
|
||||
"https://www.chromium.org/developers/testing/control-flow-integrity\n");
|
||||
}
|
||||
#endif // BUILDFLAG(CFI_ENFORCEMENT_TRAP)
|
||||
|
||||
debug::StackTrace().Print();
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
#if ARCH_CPU_X86_FAMILY
|
||||
ucontext_t* context = reinterpret_cast<ucontext_t*>(void_context);
|
||||
const struct {
|
||||
const char* label;
|
||||
greg_t value;
|
||||
} registers[] = {
|
||||
#if ARCH_CPU_32_BITS
|
||||
{ " gs: ", context->uc_mcontext.gregs[REG_GS] },
|
||||
{ " fs: ", context->uc_mcontext.gregs[REG_FS] },
|
||||
{ " es: ", context->uc_mcontext.gregs[REG_ES] },
|
||||
{ " ds: ", context->uc_mcontext.gregs[REG_DS] },
|
||||
{ " edi: ", context->uc_mcontext.gregs[REG_EDI] },
|
||||
{ " esi: ", context->uc_mcontext.gregs[REG_ESI] },
|
||||
{ " ebp: ", context->uc_mcontext.gregs[REG_EBP] },
|
||||
{ " esp: ", context->uc_mcontext.gregs[REG_ESP] },
|
||||
{ " ebx: ", context->uc_mcontext.gregs[REG_EBX] },
|
||||
{ " edx: ", context->uc_mcontext.gregs[REG_EDX] },
|
||||
{ " ecx: ", context->uc_mcontext.gregs[REG_ECX] },
|
||||
{ " eax: ", context->uc_mcontext.gregs[REG_EAX] },
|
||||
{ " trp: ", context->uc_mcontext.gregs[REG_TRAPNO] },
|
||||
{ " err: ", context->uc_mcontext.gregs[REG_ERR] },
|
||||
{ " ip: ", context->uc_mcontext.gregs[REG_EIP] },
|
||||
{ " cs: ", context->uc_mcontext.gregs[REG_CS] },
|
||||
{ " efl: ", context->uc_mcontext.gregs[REG_EFL] },
|
||||
{ " usp: ", context->uc_mcontext.gregs[REG_UESP] },
|
||||
{ " ss: ", context->uc_mcontext.gregs[REG_SS] },
|
||||
#elif ARCH_CPU_64_BITS
|
||||
{ " r8: ", context->uc_mcontext.gregs[REG_R8] },
|
||||
{ " r9: ", context->uc_mcontext.gregs[REG_R9] },
|
||||
{ " r10: ", context->uc_mcontext.gregs[REG_R10] },
|
||||
{ " r11: ", context->uc_mcontext.gregs[REG_R11] },
|
||||
{ " r12: ", context->uc_mcontext.gregs[REG_R12] },
|
||||
{ " r13: ", context->uc_mcontext.gregs[REG_R13] },
|
||||
{ " r14: ", context->uc_mcontext.gregs[REG_R14] },
|
||||
{ " r15: ", context->uc_mcontext.gregs[REG_R15] },
|
||||
{ " di: ", context->uc_mcontext.gregs[REG_RDI] },
|
||||
{ " si: ", context->uc_mcontext.gregs[REG_RSI] },
|
||||
{ " bp: ", context->uc_mcontext.gregs[REG_RBP] },
|
||||
{ " bx: ", context->uc_mcontext.gregs[REG_RBX] },
|
||||
{ " dx: ", context->uc_mcontext.gregs[REG_RDX] },
|
||||
{ " ax: ", context->uc_mcontext.gregs[REG_RAX] },
|
||||
{ " cx: ", context->uc_mcontext.gregs[REG_RCX] },
|
||||
{ " sp: ", context->uc_mcontext.gregs[REG_RSP] },
|
||||
{ " ip: ", context->uc_mcontext.gregs[REG_RIP] },
|
||||
{ " efl: ", context->uc_mcontext.gregs[REG_EFL] },
|
||||
{ " cgf: ", context->uc_mcontext.gregs[REG_CSGSFS] },
|
||||
{ " erf: ", context->uc_mcontext.gregs[REG_ERR] },
|
||||
{ " trp: ", context->uc_mcontext.gregs[REG_TRAPNO] },
|
||||
{ " msk: ", context->uc_mcontext.gregs[REG_OLDMASK] },
|
||||
{ " cr2: ", context->uc_mcontext.gregs[REG_CR2] },
|
||||
#endif // ARCH_CPU_32_BITS
|
||||
};
|
||||
|
||||
#if ARCH_CPU_32_BITS
|
||||
const int kRegisterPadding = 8;
|
||||
#elif ARCH_CPU_64_BITS
|
||||
const int kRegisterPadding = 16;
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < arraysize(registers); i++) {
|
||||
PrintToStderr(registers[i].label);
|
||||
internal::itoa_r(registers[i].value, buf, sizeof(buf),
|
||||
16, kRegisterPadding);
|
||||
PrintToStderr(buf);
|
||||
|
||||
if ((i + 1) % 4 == 0)
|
||||
PrintToStderr("\n");
|
||||
}
|
||||
PrintToStderr("\n");
|
||||
#endif // ARCH_CPU_X86_FAMILY
|
||||
#endif // defined(OS_LINUX)
|
||||
|
||||
PrintToStderr("[end of stack trace]\n");
|
||||
|
||||
#if defined(OS_MACOSX) && !defined(OS_IOS)
|
||||
if (::signal(signal, SIG_DFL) == SIG_ERR)
|
||||
_exit(1);
|
||||
#else
|
||||
// Non-Mac OSes should probably reraise the signal as well, but the Linux
|
||||
// sandbox tests break on CrOS devices.
|
||||
// https://code.google.com/p/chromium/issues/detail?id=551681
|
||||
PrintToStderr("Calling _exit(1). Core file will not be generated.\n");
|
||||
_exit(1);
|
||||
#endif // defined(OS_MACOSX) && !defined(OS_IOS)
|
||||
}
|
||||
|
||||
class PrintBacktraceOutputHandler : public BacktraceOutputHandler {
|
||||
public:
|
||||
PrintBacktraceOutputHandler() = default;
|
||||
|
||||
void HandleOutput(const char* output) override {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
PrintToStderr(output);
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(PrintBacktraceOutputHandler);
|
||||
};
|
||||
|
||||
class StreamBacktraceOutputHandler : public BacktraceOutputHandler {
|
||||
public:
|
||||
explicit StreamBacktraceOutputHandler(std::ostream* os) : os_(os) {
|
||||
}
|
||||
|
||||
void HandleOutput(const char* output) override { (*os_) << output; }
|
||||
|
||||
private:
|
||||
std::ostream* os_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StreamBacktraceOutputHandler);
|
||||
};
|
||||
|
||||
void WarmUpBacktrace() {
|
||||
// Warm up stack trace infrastructure. It turns out that on the first
|
||||
// call glibc initializes some internal data structures using pthread_once,
|
||||
// and even backtrace() can call malloc(), leading to hangs.
|
||||
//
|
||||
// Example stack trace snippet (with tcmalloc):
|
||||
//
|
||||
// #8 0x0000000000a173b5 in tc_malloc
|
||||
// at ./third_party/tcmalloc/chromium/src/debugallocation.cc:1161
|
||||
// #9 0x00007ffff7de7900 in _dl_map_object_deps at dl-deps.c:517
|
||||
// #10 0x00007ffff7ded8a9 in dl_open_worker at dl-open.c:262
|
||||
// #11 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178
|
||||
// #12 0x00007ffff7ded31a in _dl_open (file=0x7ffff625e298 "libgcc_s.so.1")
|
||||
// at dl-open.c:639
|
||||
// #13 0x00007ffff6215602 in do_dlopen at dl-libc.c:89
|
||||
// #14 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178
|
||||
// #15 0x00007ffff62156c4 in dlerror_run at dl-libc.c:48
|
||||
// #16 __GI___libc_dlopen_mode at dl-libc.c:165
|
||||
// #17 0x00007ffff61ef8f5 in init
|
||||
// at ../sysdeps/x86_64/../ia64/backtrace.c:53
|
||||
// #18 0x00007ffff6aad400 in pthread_once
|
||||
// at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_once.S:104
|
||||
// #19 0x00007ffff61efa14 in __GI___backtrace
|
||||
// at ../sysdeps/x86_64/../ia64/backtrace.c:104
|
||||
// #20 0x0000000000752a54 in base::debug::StackTrace::StackTrace
|
||||
// at base/debug/stack_trace_posix.cc:175
|
||||
// #21 0x00000000007a4ae5 in
|
||||
// base::(anonymous namespace)::StackDumpSignalHandler
|
||||
// at base/process_util_posix.cc:172
|
||||
// #22 <signal handler called>
|
||||
StackTrace stack_trace;
|
||||
}
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
|
||||
// class SandboxSymbolizeHelper.
|
||||
//
|
||||
// The purpose of this class is to prepare and install a "file open" callback
|
||||
// needed by the stack trace symbolization code
|
||||
// (base/third_party/symbolize/symbolize.h) so that it can function properly
|
||||
// in a sandboxed process. The caveat is that this class must be instantiated
|
||||
// before the sandboxing is enabled so that it can get the chance to open all
|
||||
// the object files that are loaded in the virtual address space of the current
|
||||
// process.
|
||||
class SandboxSymbolizeHelper {
|
||||
public:
|
||||
// Returns the singleton instance.
|
||||
static SandboxSymbolizeHelper* GetInstance() {
|
||||
return Singleton<SandboxSymbolizeHelper,
|
||||
LeakySingletonTraits<SandboxSymbolizeHelper>>::get();
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct DefaultSingletonTraits<SandboxSymbolizeHelper>;
|
||||
|
||||
SandboxSymbolizeHelper()
|
||||
: is_initialized_(false) {
|
||||
Init();
|
||||
}
|
||||
|
||||
~SandboxSymbolizeHelper() {
|
||||
UnregisterCallback();
|
||||
CloseObjectFiles();
|
||||
}
|
||||
|
||||
// Returns a O_RDONLY file descriptor for |file_path| if it was opened
|
||||
// successfully during the initialization. The file is repositioned at
|
||||
// offset 0.
|
||||
// IMPORTANT: This function must be async-signal-safe because it can be
|
||||
// called from a signal handler (symbolizing stack frames for a crash).
|
||||
int GetFileDescriptor(const char* file_path) {
|
||||
int fd = -1;
|
||||
|
||||
#if !defined(OFFICIAL_BUILD)
|
||||
if (file_path) {
|
||||
// The assumption here is that iterating over std::map<std::string, int>
|
||||
// using a const_iterator does not allocate dynamic memory, hense it is
|
||||
// async-signal-safe.
|
||||
std::map<std::string, int>::const_iterator it;
|
||||
for (it = modules_.begin(); it != modules_.end(); ++it) {
|
||||
if (strcmp((it->first).c_str(), file_path) == 0) {
|
||||
// POSIX.1-2004 requires an implementation to guarantee that dup()
|
||||
// is async-signal-safe.
|
||||
fd = HANDLE_EINTR(dup(it->second));
|
||||
break;
|
||||
}
|
||||
}
|
||||
// POSIX.1-2004 requires an implementation to guarantee that lseek()
|
||||
// is async-signal-safe.
|
||||
if (fd >= 0 && lseek(fd, 0, SEEK_SET) < 0) {
|
||||
// Failed to seek.
|
||||
fd = -1;
|
||||
}
|
||||
}
|
||||
#endif // !defined(OFFICIAL_BUILD)
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Searches for the object file (from /proc/self/maps) that contains
|
||||
// the specified pc. If found, sets |start_address| to the start address
|
||||
// of where this object file is mapped in memory, sets the module base
|
||||
// address into |base_address|, copies the object file name into
|
||||
// |out_file_name|, and attempts to open the object file. If the object
|
||||
// file is opened successfully, returns the file descriptor. Otherwise,
|
||||
// returns -1. |out_file_name_size| is the size of the file name buffer
|
||||
// (including the null terminator).
|
||||
// IMPORTANT: This function must be async-signal-safe because it can be
|
||||
// called from a signal handler (symbolizing stack frames for a crash).
|
||||
static int OpenObjectFileContainingPc(uint64_t pc, uint64_t& start_address,
|
||||
uint64_t& base_address, char* file_path,
|
||||
int file_path_size) {
|
||||
// This method can only be called after the singleton is instantiated.
|
||||
// This is ensured by the following facts:
|
||||
// * This is the only static method in this class, it is private, and
|
||||
// the class has no friends (except for the DefaultSingletonTraits).
|
||||
// The compiler guarantees that it can only be called after the
|
||||
// singleton is instantiated.
|
||||
// * This method is used as a callback for the stack tracing code and
|
||||
// the callback registration is done in the constructor, so logically
|
||||
// it cannot be called before the singleton is created.
|
||||
SandboxSymbolizeHelper* instance = GetInstance();
|
||||
|
||||
// The assumption here is that iterating over
|
||||
// std::vector<MappedMemoryRegion> using a const_iterator does not allocate
|
||||
// dynamic memory, hence it is async-signal-safe.
|
||||
for (const MappedMemoryRegion& region : instance->regions_) {
|
||||
if (region.start <= pc && pc < region.end) {
|
||||
start_address = region.start;
|
||||
base_address = region.base;
|
||||
if (file_path && file_path_size > 0) {
|
||||
strncpy(file_path, region.path.c_str(), file_path_size);
|
||||
// Ensure null termination.
|
||||
file_path[file_path_size - 1] = '\0';
|
||||
}
|
||||
return instance->GetFileDescriptor(region.path.c_str());
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Set the base address for each memory region by reading ELF headers in
|
||||
// process memory.
|
||||
void SetBaseAddressesForMemoryRegions() {
|
||||
base::ScopedFD mem_fd(
|
||||
HANDLE_EINTR(open("/proc/self/mem", O_RDONLY | O_CLOEXEC)));
|
||||
if (!mem_fd.is_valid())
|
||||
return;
|
||||
|
||||
auto safe_memcpy = [&mem_fd](void* dst, uintptr_t src, size_t size) {
|
||||
return HANDLE_EINTR(pread(mem_fd.get(), dst, size, src)) == ssize_t(size);
|
||||
};
|
||||
|
||||
uintptr_t cur_base = 0;
|
||||
for (auto& r : regions_) {
|
||||
ElfW(Ehdr) ehdr;
|
||||
static_assert(SELFMAG <= sizeof(ElfW(Ehdr)), "SELFMAG too large");
|
||||
if ((r.permissions & MappedMemoryRegion::READ) &&
|
||||
safe_memcpy(&ehdr, r.start, sizeof(ElfW(Ehdr))) &&
|
||||
memcmp(ehdr.e_ident, ELFMAG, SELFMAG) == 0) {
|
||||
switch (ehdr.e_type) {
|
||||
case ET_EXEC:
|
||||
cur_base = 0;
|
||||
break;
|
||||
case ET_DYN:
|
||||
// Find the segment containing file offset 0. This will correspond
|
||||
// to the ELF header that we just read. Normally this will have
|
||||
// virtual address 0, but this is not guaranteed. We must subtract
|
||||
// the virtual address from the address where the ELF header was
|
||||
// mapped to get the base address.
|
||||
//
|
||||
// If we fail to find a segment for file offset 0, use the address
|
||||
// of the ELF header as the base address.
|
||||
cur_base = r.start;
|
||||
for (unsigned i = 0; i != ehdr.e_phnum; ++i) {
|
||||
ElfW(Phdr) phdr;
|
||||
if (safe_memcpy(&phdr, r.start + ehdr.e_phoff + i * sizeof(phdr),
|
||||
sizeof(phdr)) &&
|
||||
phdr.p_type == PT_LOAD && phdr.p_offset == 0) {
|
||||
cur_base = r.start - phdr.p_vaddr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// ET_REL or ET_CORE. These aren't directly executable, so they
|
||||
// don't affect the base address.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
r.base = cur_base;
|
||||
}
|
||||
}
|
||||
|
||||
// Parses /proc/self/maps in order to compile a list of all object file names
|
||||
// for the modules that are loaded in the current process.
|
||||
// Returns true on success.
|
||||
bool CacheMemoryRegions() {
|
||||
// Reads /proc/self/maps.
|
||||
std::string contents;
|
||||
if (!ReadProcMaps(&contents)) {
|
||||
LOG(ERROR) << "Failed to read /proc/self/maps";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parses /proc/self/maps.
|
||||
if (!ParseProcMaps(contents, ®ions_)) {
|
||||
LOG(ERROR) << "Failed to parse the contents of /proc/self/maps";
|
||||
return false;
|
||||
}
|
||||
|
||||
SetBaseAddressesForMemoryRegions();
|
||||
|
||||
is_initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Opens all object files and caches their file descriptors.
|
||||
void OpenSymbolFiles() {
|
||||
// Pre-opening and caching the file descriptors of all loaded modules is
|
||||
// not safe for production builds. Hence it is only done in non-official
|
||||
// builds. For more details, take a look at: http://crbug.com/341966.
|
||||
#if !defined(OFFICIAL_BUILD)
|
||||
// Open the object files for all read-only executable regions and cache
|
||||
// their file descriptors.
|
||||
std::vector<MappedMemoryRegion>::const_iterator it;
|
||||
for (it = regions_.begin(); it != regions_.end(); ++it) {
|
||||
const MappedMemoryRegion& region = *it;
|
||||
// Only interesed in read-only executable regions.
|
||||
if ((region.permissions & MappedMemoryRegion::READ) ==
|
||||
MappedMemoryRegion::READ &&
|
||||
(region.permissions & MappedMemoryRegion::WRITE) == 0 &&
|
||||
(region.permissions & MappedMemoryRegion::EXECUTE) ==
|
||||
MappedMemoryRegion::EXECUTE) {
|
||||
if (region.path.empty()) {
|
||||
// Skip regions with empty file names.
|
||||
continue;
|
||||
}
|
||||
if (region.path[0] == '[') {
|
||||
// Skip pseudo-paths, like [stack], [vdso], [heap], etc ...
|
||||
continue;
|
||||
}
|
||||
// Avoid duplicates.
|
||||
if (modules_.find(region.path) == modules_.end()) {
|
||||
int fd = open(region.path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (fd >= 0) {
|
||||
modules_.insert(std::make_pair(region.path, fd));
|
||||
} else {
|
||||
LOG(WARNING) << "Failed to open file: " << region.path
|
||||
<< "\n Error: " << strerror(errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(OFFICIAL_BUILD)
|
||||
}
|
||||
|
||||
// Initializes and installs the symbolization callback.
|
||||
void Init() {
|
||||
if (CacheMemoryRegions()) {
|
||||
OpenSymbolFiles();
|
||||
google::InstallSymbolizeOpenObjectFileCallback(
|
||||
&OpenObjectFileContainingPc);
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister symbolization callback.
|
||||
void UnregisterCallback() {
|
||||
if (is_initialized_) {
|
||||
google::InstallSymbolizeOpenObjectFileCallback(nullptr);
|
||||
is_initialized_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Closes all file descriptors owned by this instance.
|
||||
void CloseObjectFiles() {
|
||||
#if !defined(OFFICIAL_BUILD)
|
||||
std::map<std::string, int>::iterator it;
|
||||
for (it = modules_.begin(); it != modules_.end(); ++it) {
|
||||
int ret = IGNORE_EINTR(close(it->second));
|
||||
DCHECK(!ret);
|
||||
it->second = -1;
|
||||
}
|
||||
modules_.clear();
|
||||
#endif // !defined(OFFICIAL_BUILD)
|
||||
}
|
||||
|
||||
// Set to true upon successful initialization.
|
||||
bool is_initialized_;
|
||||
|
||||
#if !defined(OFFICIAL_BUILD)
|
||||
// Mapping from file name to file descriptor. Includes file descriptors
|
||||
// for all successfully opened object files and the file descriptor for
|
||||
// /proc/self/maps. This code is not safe for production builds.
|
||||
std::map<std::string, int> modules_;
|
||||
#endif // !defined(OFFICIAL_BUILD)
|
||||
|
||||
// Cache for the process memory regions. Produced by parsing the contents
|
||||
// of /proc/self/maps cache.
|
||||
std::vector<MappedMemoryRegion> regions_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SandboxSymbolizeHelper);
|
||||
};
|
||||
#endif // USE_SYMBOLIZE
|
||||
|
||||
} // namespace
|
||||
|
||||
bool EnableInProcessStackDumping() {
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
SandboxSymbolizeHelper::GetInstance();
|
||||
#endif // USE_SYMBOLIZE
|
||||
|
||||
// When running in an application, our code typically expects SIGPIPE
|
||||
// to be ignored. Therefore, when testing that same code, it should run
|
||||
// with SIGPIPE ignored as well.
|
||||
struct sigaction sigpipe_action;
|
||||
memset(&sigpipe_action, 0, sizeof(sigpipe_action));
|
||||
sigpipe_action.sa_handler = SIG_IGN;
|
||||
sigemptyset(&sigpipe_action.sa_mask);
|
||||
bool success = (sigaction(SIGPIPE, &sigpipe_action, nullptr) == 0);
|
||||
|
||||
// Avoid hangs during backtrace initialization, see above.
|
||||
WarmUpBacktrace();
|
||||
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_flags = SA_RESETHAND | SA_SIGINFO;
|
||||
action.sa_sigaction = &StackDumpSignalHandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
success &= (sigaction(SIGILL, &action, nullptr) == 0);
|
||||
success &= (sigaction(SIGABRT, &action, nullptr) == 0);
|
||||
success &= (sigaction(SIGFPE, &action, nullptr) == 0);
|
||||
success &= (sigaction(SIGBUS, &action, nullptr) == 0);
|
||||
success &= (sigaction(SIGSEGV, &action, nullptr) == 0);
|
||||
// On Linux, SIGSYS is reserved by the kernel for seccomp-bpf sandboxing.
|
||||
#if !defined(OS_LINUX)
|
||||
success &= (sigaction(SIGSYS, &action, nullptr) == 0);
|
||||
#endif // !defined(OS_LINUX)
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void SetStackDumpFirstChanceCallback(bool (*handler)(int, void*, void*)) {
|
||||
DCHECK(try_handle_signal == nullptr || handler == nullptr);
|
||||
try_handle_signal = handler;
|
||||
}
|
||||
|
||||
StackTrace::StackTrace(size_t count) {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
#if !defined(__UCLIBC__) && !defined(_AIX)
|
||||
count = std::min(arraysize(trace_), count);
|
||||
|
||||
// Though the backtrace API man page does not list any possible negative
|
||||
// return values, we take no chance.
|
||||
count_ = base::saturated_cast<size_t>(backtrace(trace_, count));
|
||||
#else
|
||||
count_ = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void StackTrace::Print() const {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
#if !defined(__UCLIBC__) && !defined(_AIX)
|
||||
PrintBacktraceOutputHandler handler;
|
||||
ProcessBacktrace(trace_, count_, &handler);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(__UCLIBC__) && !defined(_AIX)
|
||||
void StackTrace::OutputToStream(std::ostream* os) const {
|
||||
StreamBacktraceOutputHandler handler(os);
|
||||
ProcessBacktrace(trace_, count_, &handler);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace internal {
|
||||
|
||||
// NOTE: code from sandbox/linux/seccomp-bpf/demo.cc.
|
||||
char* itoa_r(intptr_t i, char* buf, size_t sz, int base, size_t padding) {
|
||||
// Make sure we can write at least one NUL byte.
|
||||
size_t n = 1;
|
||||
if (n > sz)
|
||||
return nullptr;
|
||||
|
||||
if (base < 2 || base > 16) {
|
||||
buf[0] = '\000';
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char* start = buf;
|
||||
|
||||
uintptr_t j = i;
|
||||
|
||||
// Handle negative numbers (only for base 10).
|
||||
if (i < 0 && base == 10) {
|
||||
// This does "j = -i" while avoiding integer overflow.
|
||||
j = static_cast<uintptr_t>(-(i + 1)) + 1;
|
||||
|
||||
// Make sure we can write the '-' character.
|
||||
if (++n > sz) {
|
||||
buf[0] = '\000';
|
||||
return nullptr;
|
||||
}
|
||||
*start++ = '-';
|
||||
}
|
||||
|
||||
// Loop until we have converted the entire number. Output at least one
|
||||
// character (i.e. '0').
|
||||
char* ptr = start;
|
||||
do {
|
||||
// Make sure there is still enough space left in our output buffer.
|
||||
if (++n > sz) {
|
||||
buf[0] = '\000';
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Output the next digit.
|
||||
*ptr++ = "0123456789abcdef"[j % base];
|
||||
j /= base;
|
||||
|
||||
if (padding > 0)
|
||||
padding--;
|
||||
} while (j > 0 || padding > 0);
|
||||
|
||||
// Terminate the output with a NUL character.
|
||||
*ptr = '\000';
|
||||
|
||||
// Conversion to ASCII actually resulted in the digits being in reverse
|
||||
// order. We can't easily generate them in forward order, as we can't tell
|
||||
// the number of characters needed until we are done converting.
|
||||
// So, now, we reverse the string (except for the possible "-" sign).
|
||||
while (--ptr > start) {
|
||||
char ch = *ptr;
|
||||
*ptr = *start;
|
||||
*start++ = ch;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
365
base/debug/stack_trace_win.cc
Normal file
365
base/debug/stack_trace_win.cc
Normal file
@@ -0,0 +1,365 @@
|
||||
// Copyright (c) 2012 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/stack_trace.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
// Previous unhandled filter. Will be called if not NULL when we intercept an
|
||||
// exception. Only used in unit tests.
|
||||
LPTOP_LEVEL_EXCEPTION_FILTER g_previous_filter = NULL;
|
||||
|
||||
bool g_initialized_symbols = false;
|
||||
DWORD g_init_error = ERROR_SUCCESS;
|
||||
|
||||
// Prints the exception call stack.
|
||||
// This is the unit tests exception filter.
|
||||
long WINAPI StackDumpExceptionFilter(EXCEPTION_POINTERS* info) {
|
||||
DWORD exc_code = info->ExceptionRecord->ExceptionCode;
|
||||
std::cerr << "Received fatal exception ";
|
||||
switch (exc_code) {
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
std::cerr << "EXCEPTION_ACCESS_VIOLATION";
|
||||
break;
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
std::cerr << "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
|
||||
break;
|
||||
case EXCEPTION_BREAKPOINT:
|
||||
std::cerr << "EXCEPTION_BREAKPOINT";
|
||||
break;
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
std::cerr << "EXCEPTION_DATATYPE_MISALIGNMENT";
|
||||
break;
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||
std::cerr << "EXCEPTION_FLT_DENORMAL_OPERAND";
|
||||
break;
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
std::cerr << "EXCEPTION_FLT_DIVIDE_BY_ZERO";
|
||||
break;
|
||||
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||
std::cerr << "EXCEPTION_FLT_INEXACT_RESULT";
|
||||
break;
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
std::cerr << "EXCEPTION_FLT_INVALID_OPERATION";
|
||||
break;
|
||||
case EXCEPTION_FLT_OVERFLOW:
|
||||
std::cerr << "EXCEPTION_FLT_OVERFLOW";
|
||||
break;
|
||||
case EXCEPTION_FLT_STACK_CHECK:
|
||||
std::cerr << "EXCEPTION_FLT_STACK_CHECK";
|
||||
break;
|
||||
case EXCEPTION_FLT_UNDERFLOW:
|
||||
std::cerr << "EXCEPTION_FLT_UNDERFLOW";
|
||||
break;
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
std::cerr << "EXCEPTION_ILLEGAL_INSTRUCTION";
|
||||
break;
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
std::cerr << "EXCEPTION_IN_PAGE_ERROR";
|
||||
break;
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
std::cerr << "EXCEPTION_INT_DIVIDE_BY_ZERO";
|
||||
break;
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
std::cerr << "EXCEPTION_INT_OVERFLOW";
|
||||
break;
|
||||
case EXCEPTION_INVALID_DISPOSITION:
|
||||
std::cerr << "EXCEPTION_INVALID_DISPOSITION";
|
||||
break;
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||
std::cerr << "EXCEPTION_NONCONTINUABLE_EXCEPTION";
|
||||
break;
|
||||
case EXCEPTION_PRIV_INSTRUCTION:
|
||||
std::cerr << "EXCEPTION_PRIV_INSTRUCTION";
|
||||
break;
|
||||
case EXCEPTION_SINGLE_STEP:
|
||||
std::cerr << "EXCEPTION_SINGLE_STEP";
|
||||
break;
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
std::cerr << "EXCEPTION_STACK_OVERFLOW";
|
||||
break;
|
||||
default:
|
||||
std::cerr << "0x" << std::hex << exc_code;
|
||||
break;
|
||||
}
|
||||
std::cerr << "\n";
|
||||
|
||||
debug::StackTrace(info).Print();
|
||||
if (g_previous_filter)
|
||||
return g_previous_filter(info);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
FilePath GetExePath() {
|
||||
wchar_t system_buffer[MAX_PATH];
|
||||
GetModuleFileName(NULL, system_buffer, MAX_PATH);
|
||||
system_buffer[MAX_PATH - 1] = L'\0';
|
||||
return FilePath(system_buffer);
|
||||
}
|
||||
|
||||
bool InitializeSymbols() {
|
||||
if (g_initialized_symbols)
|
||||
return g_init_error == ERROR_SUCCESS;
|
||||
g_initialized_symbols = true;
|
||||
// Defer symbol load until they're needed, use undecorated names, and get line
|
||||
// numbers.
|
||||
SymSetOptions(SYMOPT_DEFERRED_LOADS |
|
||||
SYMOPT_UNDNAME |
|
||||
SYMOPT_LOAD_LINES);
|
||||
if (!SymInitialize(GetCurrentProcess(), NULL, TRUE)) {
|
||||
g_init_error = GetLastError();
|
||||
// TODO(awong): Handle error: SymInitialize can fail with
|
||||
// ERROR_INVALID_PARAMETER.
|
||||
// When it fails, we should not call debugbreak since it kills the current
|
||||
// process (prevents future tests from running or kills the browser
|
||||
// process).
|
||||
DLOG(ERROR) << "SymInitialize failed: " << g_init_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
// When transferring the binaries e.g. between bots, path put
|
||||
// into the executable will get off. To still retrieve symbols correctly,
|
||||
// add the directory of the executable to symbol search path.
|
||||
// All following errors are non-fatal.
|
||||
const size_t kSymbolsArraySize = 1024;
|
||||
std::unique_ptr<wchar_t[]> symbols_path(new wchar_t[kSymbolsArraySize]);
|
||||
|
||||
// Note: The below function takes buffer size as number of characters,
|
||||
// not number of bytes!
|
||||
if (!SymGetSearchPathW(GetCurrentProcess(),
|
||||
symbols_path.get(),
|
||||
kSymbolsArraySize)) {
|
||||
g_init_error = GetLastError();
|
||||
DLOG(WARNING) << "SymGetSearchPath failed: " << g_init_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring new_path(std::wstring(symbols_path.get()) +
|
||||
L";" + GetExePath().DirName().value());
|
||||
if (!SymSetSearchPathW(GetCurrentProcess(), new_path.c_str())) {
|
||||
g_init_error = GetLastError();
|
||||
DLOG(WARNING) << "SymSetSearchPath failed." << g_init_error;
|
||||
return false;
|
||||
}
|
||||
|
||||
g_init_error = ERROR_SUCCESS;
|
||||
return true;
|
||||
}
|
||||
|
||||
// SymbolContext is a threadsafe singleton that wraps the DbgHelp Sym* family
|
||||
// of functions. The Sym* family of functions may only be invoked by one
|
||||
// thread at a time. SymbolContext code may access a symbol server over the
|
||||
// network while holding the lock for this singleton. In the case of high
|
||||
// latency, this code will adversely affect performance.
|
||||
//
|
||||
// There is also a known issue where this backtrace code can interact
|
||||
// badly with breakpad if breakpad is invoked in a separate thread while
|
||||
// we are using the Sym* functions. This is because breakpad does now
|
||||
// share a lock with this function. See this related bug:
|
||||
//
|
||||
// https://crbug.com/google-breakpad/311
|
||||
//
|
||||
// This is a very unlikely edge case, and the current solution is to
|
||||
// just ignore it.
|
||||
class SymbolContext {
|
||||
public:
|
||||
static SymbolContext* GetInstance() {
|
||||
// We use a leaky singleton because code may call this during process
|
||||
// termination.
|
||||
return
|
||||
Singleton<SymbolContext, LeakySingletonTraits<SymbolContext> >::get();
|
||||
}
|
||||
|
||||
// For the given trace, attempts to resolve the symbols, and output a trace
|
||||
// to the ostream os. The format for each line of the backtrace is:
|
||||
//
|
||||
// <tab>SymbolName[0xAddress+Offset] (FileName:LineNo)
|
||||
//
|
||||
// This function should only be called if Init() has been called. We do not
|
||||
// LOG(FATAL) here because this code is called might be triggered by a
|
||||
// LOG(FATAL) itself. Also, it should not be calling complex code that is
|
||||
// extensible like PathService since that can in turn fire CHECKs.
|
||||
void OutputTraceToStream(const void* const* trace,
|
||||
size_t count,
|
||||
std::ostream* os) {
|
||||
base::AutoLock lock(lock_);
|
||||
|
||||
for (size_t i = 0; (i < count) && os->good(); ++i) {
|
||||
const int kMaxNameLength = 256;
|
||||
DWORD_PTR frame = reinterpret_cast<DWORD_PTR>(trace[i]);
|
||||
|
||||
// Code adapted from MSDN example:
|
||||
// http://msdn.microsoft.com/en-us/library/ms680578(VS.85).aspx
|
||||
ULONG64 buffer[
|
||||
(sizeof(SYMBOL_INFO) +
|
||||
kMaxNameLength * sizeof(wchar_t) +
|
||||
sizeof(ULONG64) - 1) /
|
||||
sizeof(ULONG64)];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
// Initialize symbol information retrieval structures.
|
||||
DWORD64 sym_displacement = 0;
|
||||
PSYMBOL_INFO symbol = reinterpret_cast<PSYMBOL_INFO>(&buffer[0]);
|
||||
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
symbol->MaxNameLen = kMaxNameLength - 1;
|
||||
BOOL has_symbol = SymFromAddr(GetCurrentProcess(), frame,
|
||||
&sym_displacement, symbol);
|
||||
|
||||
// Attempt to retrieve line number information.
|
||||
DWORD line_displacement = 0;
|
||||
IMAGEHLP_LINE64 line = {};
|
||||
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
|
||||
BOOL has_line = SymGetLineFromAddr64(GetCurrentProcess(), frame,
|
||||
&line_displacement, &line);
|
||||
|
||||
// Output the backtrace line.
|
||||
(*os) << "\t";
|
||||
if (has_symbol) {
|
||||
(*os) << symbol->Name << " [0x" << trace[i] << "+"
|
||||
<< sym_displacement << "]";
|
||||
} else {
|
||||
// If there is no symbol information, add a spacer.
|
||||
(*os) << "(No symbol) [0x" << trace[i] << "]";
|
||||
}
|
||||
if (has_line) {
|
||||
(*os) << " (" << line.FileName << ":" << line.LineNumber << ")";
|
||||
}
|
||||
(*os) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct DefaultSingletonTraits<SymbolContext>;
|
||||
|
||||
SymbolContext() {
|
||||
InitializeSymbols();
|
||||
}
|
||||
|
||||
base::Lock lock_;
|
||||
DISALLOW_COPY_AND_ASSIGN(SymbolContext);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool EnableInProcessStackDumping() {
|
||||
// Add stack dumping support on exception on windows. Similar to OS_POSIX
|
||||
// signal() handling in process_util_posix.cc.
|
||||
g_previous_filter = SetUnhandledExceptionFilter(&StackDumpExceptionFilter);
|
||||
|
||||
// Need to initialize symbols early in the process or else this fails on
|
||||
// swarming (since symbols are in different directory than in the exes) and
|
||||
// also release x64.
|
||||
return InitializeSymbols();
|
||||
}
|
||||
|
||||
// Disable optimizations for the StackTrace::StackTrace function. It is
|
||||
// important to disable at least frame pointer optimization ("y"), since
|
||||
// that breaks CaptureStackBackTrace() and prevents StackTrace from working
|
||||
// in Release builds (it may still be janky if other frames are using FPO,
|
||||
// but at least it will make it further).
|
||||
#if defined(COMPILER_MSVC)
|
||||
#pragma optimize("", off)
|
||||
#endif
|
||||
|
||||
StackTrace::StackTrace(size_t count) {
|
||||
count = std::min(arraysize(trace_), count);
|
||||
|
||||
// When walking our own stack, use CaptureStackBackTrace().
|
||||
count_ = CaptureStackBackTrace(0, count, trace_, NULL);
|
||||
}
|
||||
|
||||
#if defined(COMPILER_MSVC)
|
||||
#pragma optimize("", on)
|
||||
#endif
|
||||
|
||||
StackTrace::StackTrace(EXCEPTION_POINTERS* exception_pointers) {
|
||||
InitTrace(exception_pointers->ContextRecord);
|
||||
}
|
||||
|
||||
StackTrace::StackTrace(const CONTEXT* context) {
|
||||
InitTrace(context);
|
||||
}
|
||||
|
||||
void StackTrace::InitTrace(const CONTEXT* context_record) {
|
||||
// StackWalk64 modifies the register context in place, so we have to copy it
|
||||
// so that downstream exception handlers get the right context. The incoming
|
||||
// context may have had more register state (YMM, etc) than we need to unwind
|
||||
// the stack. Typically StackWalk64 only needs integer and control registers.
|
||||
CONTEXT context_copy;
|
||||
memcpy(&context_copy, context_record, sizeof(context_copy));
|
||||
context_copy.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
|
||||
|
||||
// When walking an exception stack, we need to use StackWalk64().
|
||||
count_ = 0;
|
||||
// Initialize stack walking.
|
||||
STACKFRAME64 stack_frame;
|
||||
memset(&stack_frame, 0, sizeof(stack_frame));
|
||||
#if defined(_WIN64)
|
||||
int machine_type = IMAGE_FILE_MACHINE_AMD64;
|
||||
stack_frame.AddrPC.Offset = context_record->Rip;
|
||||
stack_frame.AddrFrame.Offset = context_record->Rbp;
|
||||
stack_frame.AddrStack.Offset = context_record->Rsp;
|
||||
#else
|
||||
int machine_type = IMAGE_FILE_MACHINE_I386;
|
||||
stack_frame.AddrPC.Offset = context_record->Eip;
|
||||
stack_frame.AddrFrame.Offset = context_record->Ebp;
|
||||
stack_frame.AddrStack.Offset = context_record->Esp;
|
||||
#endif
|
||||
stack_frame.AddrPC.Mode = AddrModeFlat;
|
||||
stack_frame.AddrFrame.Mode = AddrModeFlat;
|
||||
stack_frame.AddrStack.Mode = AddrModeFlat;
|
||||
while (StackWalk64(machine_type,
|
||||
GetCurrentProcess(),
|
||||
GetCurrentThread(),
|
||||
&stack_frame,
|
||||
&context_copy,
|
||||
NULL,
|
||||
&SymFunctionTableAccess64,
|
||||
&SymGetModuleBase64,
|
||||
NULL) &&
|
||||
count_ < arraysize(trace_)) {
|
||||
trace_[count_++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset);
|
||||
}
|
||||
|
||||
for (size_t i = count_; i < arraysize(trace_); ++i)
|
||||
trace_[i] = NULL;
|
||||
}
|
||||
|
||||
void StackTrace::Print() const {
|
||||
OutputToStream(&std::cerr);
|
||||
}
|
||||
|
||||
void StackTrace::OutputToStream(std::ostream* os) const {
|
||||
SymbolContext* context = SymbolContext::GetInstance();
|
||||
if (g_init_error != ERROR_SUCCESS) {
|
||||
(*os) << "Error initializing symbols (" << g_init_error
|
||||
<< "). Dumping unresolved backtrace:\n";
|
||||
for (size_t i = 0; (i < count_) && os->good(); ++i) {
|
||||
(*os) << "\t" << trace_[i] << "\n";
|
||||
}
|
||||
} else {
|
||||
(*os) << "Backtrace:\n";
|
||||
context->OutputTraceToStream(trace_, count_, os);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
65
base/debug/task_annotator.cc
Normal file
65
base/debug/task_annotator.cc
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2014 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/task_annotator.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "base/debug/activity_tracker.h"
|
||||
#include "base/debug/alias.h"
|
||||
#include "base/pending_task.h"
|
||||
#include "base/trace_event/trace_event.h"
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
TaskAnnotator::TaskAnnotator() = default;
|
||||
|
||||
TaskAnnotator::~TaskAnnotator() = default;
|
||||
|
||||
void TaskAnnotator::DidQueueTask(const char* queue_function,
|
||||
const PendingTask& pending_task) {
|
||||
TRACE_EVENT_WITH_FLOW0(
|
||||
TRACE_DISABLED_BY_DEFAULT("toplevel.flow"), queue_function,
|
||||
TRACE_ID_MANGLE(GetTaskTraceID(pending_task)), TRACE_EVENT_FLAG_FLOW_OUT);
|
||||
}
|
||||
|
||||
void TaskAnnotator::RunTask(const char* queue_function,
|
||||
PendingTask* pending_task) {
|
||||
ScopedTaskRunActivity task_activity(*pending_task);
|
||||
|
||||
TRACE_EVENT_WITH_FLOW0(
|
||||
TRACE_DISABLED_BY_DEFAULT("toplevel.flow"), queue_function,
|
||||
TRACE_ID_MANGLE(GetTaskTraceID(*pending_task)), TRACE_EVENT_FLAG_FLOW_IN);
|
||||
|
||||
// Before running the task, store the task backtrace with the chain of
|
||||
// PostTasks that resulted in this call and deliberately alias it to ensure
|
||||
// it is on the stack if the task crashes. Be careful not to assume that the
|
||||
// variable itself will have the expected value when displayed by the
|
||||
// optimizer in an optimized build. Look at a memory dump of the stack.
|
||||
static constexpr int kStackTaskTraceSnapshotSize =
|
||||
std::tuple_size<decltype(pending_task->task_backtrace)>::value + 3;
|
||||
std::array<const void*, kStackTaskTraceSnapshotSize> task_backtrace;
|
||||
|
||||
// Store a marker to locate |task_backtrace| content easily on a memory
|
||||
// dump.
|
||||
task_backtrace.front() = reinterpret_cast<void*>(0xefefefefefefefef);
|
||||
task_backtrace.back() = reinterpret_cast<void*>(0xfefefefefefefefe);
|
||||
|
||||
task_backtrace[1] = pending_task->posted_from.program_counter();
|
||||
std::copy(pending_task->task_backtrace.begin(),
|
||||
pending_task->task_backtrace.end(), task_backtrace.begin() + 2);
|
||||
debug::Alias(&task_backtrace);
|
||||
|
||||
std::move(pending_task->task).Run();
|
||||
}
|
||||
|
||||
uint64_t TaskAnnotator::GetTaskTraceID(const PendingTask& task) const {
|
||||
return (static_cast<uint64_t>(task.sequence_num) << 32) |
|
||||
((static_cast<uint64_t>(reinterpret_cast<intptr_t>(this)) << 32) >>
|
||||
32);
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
45
base/debug/task_annotator.h
Normal file
45
base/debug/task_annotator.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2014 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_DEBUG_TASK_ANNOTATOR_H_
|
||||
#define BASE_DEBUG_TASK_ANNOTATOR_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/base_export.h"
|
||||
#include "base/macros.h"
|
||||
|
||||
namespace base {
|
||||
struct PendingTask;
|
||||
namespace debug {
|
||||
|
||||
// Implements common debug annotations for posted tasks. This includes data
|
||||
// such as task origins, queueing durations and memory usage.
|
||||
class BASE_EXPORT TaskAnnotator {
|
||||
public:
|
||||
TaskAnnotator();
|
||||
~TaskAnnotator();
|
||||
|
||||
// Called to indicate that a task has been queued to run in the future.
|
||||
// |queue_function| is used as the trace flow event name.
|
||||
void DidQueueTask(const char* queue_function,
|
||||
const PendingTask& pending_task);
|
||||
|
||||
// Run a previously queued task. |queue_function| should match what was
|
||||
// passed into |DidQueueTask| for this task.
|
||||
void RunTask(const char* queue_function, PendingTask* pending_task);
|
||||
|
||||
private:
|
||||
// Creates a process-wide unique ID to represent this task in trace events.
|
||||
// This will be mangled with a Process ID hash to reduce the likelyhood of
|
||||
// colliding with TaskAnnotator pointers on other processes.
|
||||
uint64_t GetTaskTraceID(const PendingTask& task) const;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(TaskAnnotator);
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_TASK_ANNOTATOR_H_
|
||||
340
base/debug/thread_heap_usage_tracker.cc
Normal file
340
base/debug/thread_heap_usage_tracker.cc
Normal file
@@ -0,0 +1,340 @@
|
||||
// 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/thread_heap_usage_tracker.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
#include "base/allocator/allocator_shim.h"
|
||||
#include "base/allocator/features.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/threading/thread_local_storage.h"
|
||||
#include "build/build_config.h"
|
||||
|
||||
#if defined(OS_MACOSX) || defined(OS_IOS)
|
||||
#include <malloc/malloc.h>
|
||||
#else
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
namespace base {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
using base::allocator::AllocatorDispatch;
|
||||
|
||||
ThreadLocalStorage::StaticSlot g_thread_allocator_usage = TLS_INITIALIZER;
|
||||
|
||||
const uintptr_t kSentinelMask = std::numeric_limits<uintptr_t>::max() - 1;
|
||||
ThreadHeapUsage* const kInitializationSentinel =
|
||||
reinterpret_cast<ThreadHeapUsage*>(kSentinelMask);
|
||||
ThreadHeapUsage* const kTeardownSentinel =
|
||||
reinterpret_cast<ThreadHeapUsage*>(kSentinelMask | 1);
|
||||
|
||||
bool g_heap_tracking_enabled = false;
|
||||
|
||||
// Forward declared as it needs to delegate memory allocation to the next
|
||||
// lower shim.
|
||||
ThreadHeapUsage* GetOrCreateThreadUsage();
|
||||
|
||||
size_t GetAllocSizeEstimate(const AllocatorDispatch* next,
|
||||
void* ptr,
|
||||
void* context) {
|
||||
if (ptr == nullptr)
|
||||
return 0U;
|
||||
|
||||
return next->get_size_estimate_function(next, ptr, context);
|
||||
}
|
||||
|
||||
void RecordAlloc(const AllocatorDispatch* next,
|
||||
void* ptr,
|
||||
size_t size,
|
||||
void* context) {
|
||||
ThreadHeapUsage* usage = GetOrCreateThreadUsage();
|
||||
if (usage == nullptr)
|
||||
return;
|
||||
|
||||
usage->alloc_ops++;
|
||||
size_t estimate = GetAllocSizeEstimate(next, ptr, context);
|
||||
if (size && estimate) {
|
||||
// Only keep track of the net number of bytes allocated in the scope if the
|
||||
// size estimate function returns sane values, e.g. non-zero.
|
||||
usage->alloc_bytes += estimate;
|
||||
usage->alloc_overhead_bytes += estimate - size;
|
||||
|
||||
// Record the max outstanding number of bytes, but only if the difference
|
||||
// is net positive (e.g. more bytes allocated than freed in the scope).
|
||||
if (usage->alloc_bytes > usage->free_bytes) {
|
||||
uint64_t allocated_bytes = usage->alloc_bytes - usage->free_bytes;
|
||||
if (allocated_bytes > usage->max_allocated_bytes)
|
||||
usage->max_allocated_bytes = allocated_bytes;
|
||||
}
|
||||
} else {
|
||||
usage->alloc_bytes += size;
|
||||
}
|
||||
}
|
||||
|
||||
void RecordFree(const AllocatorDispatch* next, void* ptr, void* context) {
|
||||
ThreadHeapUsage* usage = GetOrCreateThreadUsage();
|
||||
if (usage == nullptr)
|
||||
return;
|
||||
|
||||
size_t estimate = GetAllocSizeEstimate(next, ptr, context);
|
||||
usage->free_ops++;
|
||||
usage->free_bytes += estimate;
|
||||
}
|
||||
|
||||
void* AllocFn(const AllocatorDispatch* self, size_t size, void* context) {
|
||||
void* ret = self->next->alloc_function(self->next, size, context);
|
||||
if (ret != nullptr)
|
||||
RecordAlloc(self->next, ret, size, context);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* AllocZeroInitializedFn(const AllocatorDispatch* self,
|
||||
size_t n,
|
||||
size_t size,
|
||||
void* context) {
|
||||
void* ret =
|
||||
self->next->alloc_zero_initialized_function(self->next, n, size, context);
|
||||
if (ret != nullptr)
|
||||
RecordAlloc(self->next, ret, size, context);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* AllocAlignedFn(const AllocatorDispatch* self,
|
||||
size_t alignment,
|
||||
size_t size,
|
||||
void* context) {
|
||||
void* ret =
|
||||
self->next->alloc_aligned_function(self->next, alignment, size, context);
|
||||
if (ret != nullptr)
|
||||
RecordAlloc(self->next, ret, size, context);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* ReallocFn(const AllocatorDispatch* self,
|
||||
void* address,
|
||||
size_t size,
|
||||
void* context) {
|
||||
if (address != nullptr)
|
||||
RecordFree(self->next, address, context);
|
||||
|
||||
void* ret = self->next->realloc_function(self->next, address, size, context);
|
||||
if (ret != nullptr && size != 0)
|
||||
RecordAlloc(self->next, ret, size, context);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void FreeFn(const AllocatorDispatch* self, void* address, void* context) {
|
||||
if (address != nullptr)
|
||||
RecordFree(self->next, address, context);
|
||||
self->next->free_function(self->next, address, context);
|
||||
}
|
||||
|
||||
size_t GetSizeEstimateFn(const AllocatorDispatch* self,
|
||||
void* address,
|
||||
void* context) {
|
||||
return self->next->get_size_estimate_function(self->next, address, context);
|
||||
}
|
||||
|
||||
unsigned BatchMallocFn(const AllocatorDispatch* self,
|
||||
size_t size,
|
||||
void** results,
|
||||
unsigned num_requested,
|
||||
void* context) {
|
||||
unsigned count = self->next->batch_malloc_function(self->next, size, results,
|
||||
num_requested, context);
|
||||
for (unsigned i = 0; i < count; ++i) {
|
||||
RecordAlloc(self->next, results[i], size, context);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void BatchFreeFn(const AllocatorDispatch* self,
|
||||
void** to_be_freed,
|
||||
unsigned num_to_be_freed,
|
||||
void* context) {
|
||||
for (unsigned i = 0; i < num_to_be_freed; ++i) {
|
||||
if (to_be_freed[i] != nullptr) {
|
||||
RecordFree(self->next, to_be_freed[i], context);
|
||||
}
|
||||
}
|
||||
self->next->batch_free_function(self->next, to_be_freed, num_to_be_freed,
|
||||
context);
|
||||
}
|
||||
|
||||
void FreeDefiniteSizeFn(const AllocatorDispatch* self,
|
||||
void* ptr,
|
||||
size_t size,
|
||||
void* context) {
|
||||
if (ptr != nullptr)
|
||||
RecordFree(self->next, ptr, context);
|
||||
self->next->free_definite_size_function(self->next, ptr, size, context);
|
||||
}
|
||||
|
||||
// The allocator dispatch used to intercept heap operations.
|
||||
AllocatorDispatch allocator_dispatch = {&AllocFn,
|
||||
&AllocZeroInitializedFn,
|
||||
&AllocAlignedFn,
|
||||
&ReallocFn,
|
||||
&FreeFn,
|
||||
&GetSizeEstimateFn,
|
||||
&BatchMallocFn,
|
||||
&BatchFreeFn,
|
||||
&FreeDefiniteSizeFn,
|
||||
nullptr};
|
||||
|
||||
ThreadHeapUsage* GetOrCreateThreadUsage() {
|
||||
auto tls_ptr = reinterpret_cast<uintptr_t>(g_thread_allocator_usage.Get());
|
||||
if ((tls_ptr & kSentinelMask) == kSentinelMask)
|
||||
return nullptr; // Re-entrancy case.
|
||||
|
||||
auto* allocator_usage = reinterpret_cast<ThreadHeapUsage*>(tls_ptr);
|
||||
if (allocator_usage == nullptr) {
|
||||
// Prevent reentrancy due to the allocation below.
|
||||
g_thread_allocator_usage.Set(kInitializationSentinel);
|
||||
|
||||
allocator_usage = new ThreadHeapUsage();
|
||||
static_assert(std::is_pod<ThreadHeapUsage>::value,
|
||||
"AllocatorDispatch must be POD");
|
||||
memset(allocator_usage, 0, sizeof(*allocator_usage));
|
||||
g_thread_allocator_usage.Set(allocator_usage);
|
||||
}
|
||||
|
||||
return allocator_usage;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ThreadHeapUsageTracker::ThreadHeapUsageTracker() : thread_usage_(nullptr) {
|
||||
static_assert(std::is_pod<ThreadHeapUsage>::value, "Must be POD.");
|
||||
}
|
||||
|
||||
ThreadHeapUsageTracker::~ThreadHeapUsageTracker() {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
|
||||
if (thread_usage_ != nullptr) {
|
||||
// If this tracker wasn't stopped, make it inclusive so that the
|
||||
// usage isn't lost.
|
||||
Stop(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadHeapUsageTracker::Start() {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
DCHECK(g_thread_allocator_usage.initialized());
|
||||
|
||||
thread_usage_ = GetOrCreateThreadUsage();
|
||||
usage_ = *thread_usage_;
|
||||
|
||||
// Reset the stats for our current scope.
|
||||
// The per-thread usage instance now tracks this scope's usage, while this
|
||||
// instance persists the outer scope's usage stats. On destruction, this
|
||||
// instance will restore the outer scope's usage stats with this scope's
|
||||
// usage added.
|
||||
memset(thread_usage_, 0, sizeof(*thread_usage_));
|
||||
}
|
||||
|
||||
void ThreadHeapUsageTracker::Stop(bool usage_is_exclusive) {
|
||||
DCHECK(thread_checker_.CalledOnValidThread());
|
||||
DCHECK_NE(nullptr, thread_usage_);
|
||||
|
||||
ThreadHeapUsage current = *thread_usage_;
|
||||
if (usage_is_exclusive) {
|
||||
// Restore the outer scope.
|
||||
*thread_usage_ = usage_;
|
||||
} else {
|
||||
// Update the outer scope with the accrued inner usage.
|
||||
if (thread_usage_->max_allocated_bytes) {
|
||||
uint64_t outer_net_alloc_bytes = usage_.alloc_bytes - usage_.free_bytes;
|
||||
|
||||
thread_usage_->max_allocated_bytes =
|
||||
std::max(usage_.max_allocated_bytes,
|
||||
outer_net_alloc_bytes + thread_usage_->max_allocated_bytes);
|
||||
}
|
||||
|
||||
thread_usage_->alloc_ops += usage_.alloc_ops;
|
||||
thread_usage_->alloc_bytes += usage_.alloc_bytes;
|
||||
thread_usage_->alloc_overhead_bytes += usage_.alloc_overhead_bytes;
|
||||
thread_usage_->free_ops += usage_.free_ops;
|
||||
thread_usage_->free_bytes += usage_.free_bytes;
|
||||
}
|
||||
|
||||
thread_usage_ = nullptr;
|
||||
usage_ = current;
|
||||
}
|
||||
|
||||
ThreadHeapUsage ThreadHeapUsageTracker::GetUsageSnapshot() {
|
||||
DCHECK(g_thread_allocator_usage.initialized());
|
||||
|
||||
ThreadHeapUsage* usage = GetOrCreateThreadUsage();
|
||||
DCHECK_NE(nullptr, usage);
|
||||
return *usage;
|
||||
}
|
||||
|
||||
void ThreadHeapUsageTracker::EnableHeapTracking() {
|
||||
EnsureTLSInitialized();
|
||||
|
||||
CHECK_EQ(false, g_heap_tracking_enabled) << "No double-enabling.";
|
||||
g_heap_tracking_enabled = true;
|
||||
#if BUILDFLAG(USE_ALLOCATOR_SHIM)
|
||||
base::allocator::InsertAllocatorDispatch(&allocator_dispatch);
|
||||
#else
|
||||
CHECK(false) << "Can't enable heap tracking without the shim.";
|
||||
#endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
|
||||
}
|
||||
|
||||
bool ThreadHeapUsageTracker::IsHeapTrackingEnabled() {
|
||||
return g_heap_tracking_enabled;
|
||||
}
|
||||
|
||||
void ThreadHeapUsageTracker::DisableHeapTrackingForTesting() {
|
||||
#if BUILDFLAG(USE_ALLOCATOR_SHIM)
|
||||
base::allocator::RemoveAllocatorDispatchForTesting(&allocator_dispatch);
|
||||
#else
|
||||
CHECK(false) << "Can't disable heap tracking without the shim.";
|
||||
#endif // BUILDFLAG(USE_ALLOCATOR_SHIM)
|
||||
DCHECK_EQ(true, g_heap_tracking_enabled) << "Heap tracking not enabled.";
|
||||
g_heap_tracking_enabled = false;
|
||||
}
|
||||
|
||||
base::allocator::AllocatorDispatch*
|
||||
ThreadHeapUsageTracker::GetDispatchForTesting() {
|
||||
return &allocator_dispatch;
|
||||
}
|
||||
|
||||
void ThreadHeapUsageTracker::EnsureTLSInitialized() {
|
||||
if (!g_thread_allocator_usage.initialized()) {
|
||||
g_thread_allocator_usage.Initialize([](void* thread_heap_usage) {
|
||||
// This destructor will be called twice. Once to destroy the actual
|
||||
// ThreadHeapUsage instance and a second time, immediately after, for the
|
||||
// sentinel. Re-setting the TLS slow (below) does re-initialize the TLS
|
||||
// slot. The ThreadLocalStorage code is designed to deal with this use
|
||||
// case (see comments in ThreadHeapUsageTracker::EnsureTLSInitialized) and
|
||||
// will re-call the destructor with the kTeardownSentinel as arg.
|
||||
if (thread_heap_usage == kTeardownSentinel)
|
||||
return;
|
||||
DCHECK(thread_heap_usage != kInitializationSentinel);
|
||||
|
||||
// Deleting the ThreadHeapUsage TLS object will re-enter the shim and hit
|
||||
// RecordFree() above. The sentinel prevents RecordFree() from re-creating
|
||||
// another ThreadHeapUsage object.
|
||||
g_thread_allocator_usage.Set(kTeardownSentinel);
|
||||
delete static_cast<ThreadHeapUsage*>(thread_heap_usage);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
117
base/debug/thread_heap_usage_tracker.h
Normal file
117
base/debug/thread_heap_usage_tracker.h
Normal file
@@ -0,0 +1,117 @@
|
||||
// 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.
|
||||
|
||||
#ifndef BASE_DEBUG_THREAD_HEAP_USAGE_TRACKER_H_
|
||||
#define BASE_DEBUG_THREAD_HEAP_USAGE_TRACKER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "base/allocator/features.h"
|
||||
#include "base/base_export.h"
|
||||
#include "base/threading/thread_checker.h"
|
||||
|
||||
namespace base {
|
||||
namespace allocator {
|
||||
struct AllocatorDispatch;
|
||||
} // namespace allocator
|
||||
|
||||
namespace debug {
|
||||
|
||||
// Used to store the heap allocator usage in a scope.
|
||||
struct ThreadHeapUsage {
|
||||
// The cumulative number of allocation operations.
|
||||
uint64_t alloc_ops;
|
||||
|
||||
// The cumulative number of allocated bytes. Where available, this is
|
||||
// inclusive heap padding and estimated or actual heap overhead.
|
||||
uint64_t alloc_bytes;
|
||||
|
||||
// Where available, cumulative number of heap padding and overhead bytes.
|
||||
uint64_t alloc_overhead_bytes;
|
||||
|
||||
// The cumulative number of free operations.
|
||||
uint64_t free_ops;
|
||||
|
||||
// The cumulative number of bytes freed.
|
||||
// Only recorded if the underlying heap shim can return the size of an
|
||||
// allocation.
|
||||
uint64_t free_bytes;
|
||||
|
||||
// The maximal value of |alloc_bytes| - |free_bytes| seen for this thread.
|
||||
// Only recorded if the underlying heap shim supports returning the size of
|
||||
// an allocation.
|
||||
uint64_t max_allocated_bytes;
|
||||
};
|
||||
|
||||
// By keeping a tally on heap operations, it's possible to track:
|
||||
// - the number of alloc/free operations, where a realloc is zero or one
|
||||
// of each, depending on the input parameters (see man realloc).
|
||||
// - the number of bytes allocated/freed.
|
||||
// - the number of estimated bytes of heap overhead used.
|
||||
// - the high-watermark amount of bytes allocated in the scope.
|
||||
// This in turn allows measuring the memory usage and memory usage churn over
|
||||
// a scope. Scopes must be cleanly nested, and each scope must be
|
||||
// destroyed on the thread where it's created.
|
||||
//
|
||||
// Note that this depends on the capabilities of the underlying heap shim. If
|
||||
// that shim can not yield a size estimate for an allocation, it's not possible
|
||||
// to keep track of overhead, freed bytes and the allocation high water mark.
|
||||
class BASE_EXPORT ThreadHeapUsageTracker {
|
||||
public:
|
||||
ThreadHeapUsageTracker();
|
||||
~ThreadHeapUsageTracker();
|
||||
|
||||
// Start tracking heap usage on this thread.
|
||||
// This may only be called on the thread where the instance is created.
|
||||
// Note IsHeapTrackingEnabled() must be true.
|
||||
void Start();
|
||||
|
||||
// Stop tracking heap usage on this thread and store the usage tallied.
|
||||
// If |usage_is_exclusive| is true, the usage tallied won't be added to the
|
||||
// outer scope's usage. If |usage_is_exclusive| is false, the usage tallied
|
||||
// in this scope will also tally to any outer scope.
|
||||
// This may only be called on the thread where the instance is created.
|
||||
void Stop(bool usage_is_exclusive);
|
||||
|
||||
// After Stop() returns the usage tallied from Start() to Stop().
|
||||
const ThreadHeapUsage& usage() const { return usage_; }
|
||||
|
||||
// Returns this thread's heap usage from the start of the innermost
|
||||
// enclosing ThreadHeapUsageTracker instance, if any.
|
||||
static ThreadHeapUsage GetUsageSnapshot();
|
||||
|
||||
// Enables the heap intercept. May only be called once, and only if the heap
|
||||
// shim is available, e.g. if BUILDFLAG(USE_ALLOCATOR_SHIM) is
|
||||
// true.
|
||||
static void EnableHeapTracking();
|
||||
|
||||
// Returns true iff heap tracking is enabled.
|
||||
static bool IsHeapTrackingEnabled();
|
||||
|
||||
protected:
|
||||
// Exposed for testing only - note that it's safe to re-EnableHeapTracking()
|
||||
// after calling this function in tests.
|
||||
static void DisableHeapTrackingForTesting();
|
||||
|
||||
// Exposed for testing only.
|
||||
static void EnsureTLSInitialized();
|
||||
|
||||
// Exposed to allow testing the shim without inserting it in the allocator
|
||||
// shim chain.
|
||||
static base::allocator::AllocatorDispatch* GetDispatchForTesting();
|
||||
|
||||
private:
|
||||
ThreadChecker thread_checker_;
|
||||
|
||||
// The heap usage at Start(), or the difference from Start() to Stop().
|
||||
ThreadHeapUsage usage_;
|
||||
|
||||
// This thread's heap usage, non-null from Start() to Stop().
|
||||
ThreadHeapUsage* thread_usage_;
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
} // namespace base
|
||||
|
||||
#endif // BASE_DEBUG_THREAD_HEAP_USAGE_TRACKER_H_
|
||||
Reference in New Issue
Block a user