Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,2 @@
# For activity tracking:
per-file activity_*=bcwhite@chromium.org

View file

@ -0,0 +1,405 @@
// 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/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
namespace base {
namespace debug {
namespace {
const ActivityUserData::Snapshot& GetEmptyUserDataSnapshot() {
// An empty snapshot that can be returned when there otherwise is none.
static const NoDestructor<ActivityUserData::Snapshot> empty_snapshot;
return *empty_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) {
UmaHistogramEnumeration("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 std::make_unique<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());
if (!mmfile->Initialize(file_path, MemoryMappedFile::READ_WRITE)) {
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(
base::ReadOnlySharedMemoryMapping mapping) {
if (!mapping.IsValid() ||
!ReadOnlySharedPersistentMemoryAllocator::IsSharedMemoryAcceptable(
mapping)) {
return nullptr;
}
return CreateWithAllocator(
std::make_unique<ReadOnlySharedPersistentMemoryAllocator>(
std::move(mapping), 0, StringPiece()));
}
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 GetEmptyUserDataSnapshot();
if (iter->second.create_stamp > analysis_stamp_)
return GetEmptyUserDataSnapshot();
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::Contains(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::Contains(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

View file

@ -0,0 +1,257 @@
// 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"
#include "base/memory/shared_memory_mapping.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(
base::ReadOnlySharedMemoryMapping mapping);
// 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_

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,16 @@
// 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 {
// This file/function should be excluded from LTO/LTCG to ensure that the
// compiler can't see this function's implementation when compiling calls to it.
NOINLINE void Alias(const void* var) {}
} // namespace debug
} // namespace base

View 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.
#ifndef BASE_DEBUG_ALIAS_H_
#define BASE_DEBUG_ALIAS_H_
#include "base/base_export.h"
#include "base/stl_util.h"
#include "base/strings/string_util.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);
// DEBUG_ALIAS_FOR_CSTR(name_copy, p->name, 16);
// CHECK(false);
void BASE_EXPORT Alias(const void* var);
} // namespace debug
} // namespace base
// Convenience macro that copies the null-terminated string from |c_str| into a
// stack-allocated char array named |var_name| that holds up to |char_count|
// characters and should be preserved in memory dumps.
#define DEBUG_ALIAS_FOR_CSTR(var_name, c_str, char_count) \
char var_name[char_count]; \
::base::strlcpy(var_name, (c_str), base::size(var_name)); \
::base::debug::Alias(var_name);
#endif // BASE_DEBUG_ALIAS_H_

View file

@ -0,0 +1,104 @@
// 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(OS_WIN) && defined(ADDRESS_SANITIZER)
// 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;
LONG* array = new LONG[kArraySize];
// Explicitly call out to a kernel32 function to perform the memory access.
// This way the underflow won't be detected but the corruption will (as the
// allocator will still be hooked).
auto InterlockedIncrementFn =
reinterpret_cast<LONG (*)(LONG volatile * addend)>(
GetProcAddress(GetModuleHandle(L"kernel32"), "InterlockedIncrement"));
CHECK(InterlockedIncrementFn);
LONG volatile dummy = InterlockedIncrementFn(array - 1);
base::debug::Alias(const_cast<LONG*>(&dummy));
if (induce_crash)
CHECK(false);
delete[] array;
}
#endif // OS_WIN && ADDRESS_SANITIZER
} // namespace
#if defined(ADDRESS_SANITIZER) || BUILDFLAG(IS_HWASAN)
// NOTE(sebmarchand): We intentionally perform some invalid heap access here in
// order to trigger an AddressSanitizer (ASan) error report.
// This variable is used to size an array of ints. It needs to be a multiple of
// 4 so that off-by-one overflows are detected by HWASan, which has a shadow
// granularity of 16 bytes.
static const size_t kArraySize = 4;
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);
}
#if defined(OS_WIN)
void AsanCorruptHeapBlock() {
CorruptMemoryBlock(false);
}
void AsanCorruptHeap() {
CorruptMemoryBlock(true);
}
#endif // OS_WIN
#endif // ADDRESS_SANITIZER
} // namespace debug
} // namespace base

View 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"
#include "base/sanitizer_buildflags.h"
#include "build/build_config.h"
namespace base {
namespace debug {
#if defined(ADDRESS_SANITIZER) || BUILDFLAG(IS_HWASAN)
// 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();
// The "corrupt-block" and "corrupt-heap" classes of bugs is specific to
// Windows.
#if defined(OS_WIN)
// 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 // OS_WIN
#endif // ADDRESS_SANITIZER
} // namespace debug
} // namespace base
#endif // BASE_DEBUG_ASAN_INVALID_ACCESS_H_

View file

@ -0,0 +1,259 @@
// 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.
*eat_entry = static_cast<DWORD>(reinterpret_cast<uintptr_t>(new_function) -
reinterpret_cast<uintptr_t>(module));
}
// 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", reinterpret_cast<void*>(&CloseHandleHook),
reinterpret_cast<void**>(&g_close_function));
if (!patch)
return;
hooks_.push_back(patch);
patch = IATPatch(module, "DuplicateHandle",
reinterpret_cast<void*>(&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",
reinterpret_cast<void*>(&CloseHandleHook),
reinterpret_cast<void**>(&g_close_function));
EATPatch(GetModuleHandleA("kernel32.dll"), "DuplicateHandle",
reinterpret_cast<void*>(&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

View 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_

View file

@ -0,0 +1,54 @@
// 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"
namespace base {
namespace debug {
namespace {
CrashKeyImplementation* g_crash_key_impl = nullptr;
} // 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);
}
ScopedCrashKeyString::ScopedCrashKeyString(CrashKeyString* crash_key,
base::StringPiece value)
: crash_key_(crash_key) {
SetCrashKeyString(crash_key_, value);
}
ScopedCrashKeyString::~ScopedCrashKeyString() {
ClearCrashKeyString(crash_key_);
}
void SetCrashKeyImplementation(std::unique_ptr<CrashKeyImplementation> impl) {
delete g_crash_key_impl;
g_crash_key_impl = impl.release();
}
} // namespace debug
} // namespace base

View file

@ -0,0 +1,104 @@
// 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 "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);
// A scoper that sets the specified key to value for the lifetime of the
// object, and clears it on destruction.
class BASE_EXPORT ScopedCrashKeyString {
public:
ScopedCrashKeyString(CrashKeyString* crash_key, base::StringPiece value);
~ScopedCrashKeyString();
private:
CrashKeyString* const crash_key_;
DISALLOW_COPY_AND_ASSIGN(ScopedCrashKeyString);
};
////////////////////////////////////////////////////////////////////////////////
// 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() = default;
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;
};
} // namespace debug
} // namespace base
#endif // BASE_DEBUG_CRASH_LOGGING_H_

View 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

View file

@ -0,0 +1,50 @@
// 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();
// If a debugger is present, verifies that it is properly set up, and DCHECK()s
// if misconfigured. Currently only verifies that //tools/gdb/gdbinit has been
// sourced when using gdb on Linux and //tools/lldb/lldbinit.py has been sourced
// when using lldb on macOS.
BASE_EXPORT void VerifyDebugger();
} // namespace debug
} // namespace base
#endif // BASE_DEBUG_DEBUGGER_H_

View file

@ -0,0 +1,360 @@
// 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/clang_profiling_buildflags.h"
#include "base/stl_util.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
#if defined(OS_FUCHSIA)
#include <zircon/process.h>
#include <zircon/syscalls.h>
#endif
#include <ostream>
#include "base/debug/alias.h"
#include "base/debug/debugging_buildflags.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/process.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#if BUILDFLAG(CLANG_PROFILING)
#include "base/test/clang_profiling.h"
#endif
#if defined(USE_SYMBOLIZE)
#include "base/third_party/symbolize/symbolize.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, base::size(mib), NULL, &info_size, NULL, 0) < 0)
return -1;
mib[5] = (info_size / sizeof(struct kinfo_proc));
#endif
int sysctl_result = sysctl(mib, base::size(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;
}
void VerifyDebugger() {
#if BUILDFLAG(ENABLE_LLDBINIT_WARNING)
if (Environment::Create()->HasVar("CHROMIUM_LLDBINIT_SOURCED"))
return;
if (!BeingDebugged())
return;
DCHECK(false)
<< "Detected lldb without sourcing //tools/lldb/lldbinit.py. lldb may "
"not be able to find debug symbols. Please see debug instructions for "
"using //tools/lldb/lldbinit.py:\n"
"https://chromium.googlesource.com/chromium/src/+/master/docs/"
"lldbinit.md\n"
"To continue anyway, type 'continue' in lldb. To always skip this "
"check, define an environment variable CHROMIUM_LLDBINIT_SOURCED=1";
#endif
}
#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
Process GetDebuggerProcess() {
// 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 Process();
// 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 Process();
if (num_read <= 0)
return Process();
StringPiece status(buf, num_read);
StringPiece tracer("TracerPid:\t");
StringPiece::size_type pid_index = status.find(tracer);
if (pid_index == StringPiece::npos)
return Process();
pid_index += tracer.size();
StringPiece::size_type pid_end_index = status.find('\n', pid_index);
if (pid_end_index == StringPiece::npos)
return Process();
StringPiece pid_str(buf + pid_index, pid_end_index - pid_index);
int pid = 0;
if (!StringToInt(pid_str, &pid))
return Process();
return Process(pid);
}
bool BeingDebugged() {
return GetDebuggerProcess().IsValid();
}
void VerifyDebugger() {
#if BUILDFLAG(ENABLE_GDBINIT_WARNING)
// Quick check before potentially slower GetDebuggerProcess().
if (Environment::Create()->HasVar("CHROMIUM_GDBINIT_SOURCED"))
return;
Process proc = GetDebuggerProcess();
if (!proc.IsValid())
return;
FilePath cmdline_file =
FilePath("/proc").Append(NumberToString(proc.Handle())).Append("cmdline");
std::string cmdline;
if (!ReadFileToString(cmdline_file, &cmdline))
return;
// /proc/*/cmdline separates arguments with null bytes, but we only care about
// the executable name, so interpret |cmdline| as a null-terminated C string
// to extract the exe portion.
StringPiece exe(cmdline.c_str());
DCHECK(ToLowerASCII(exe).find("gdb") == std::string::npos)
<< "Detected gdb without sourcing //tools/gdb/gdbinit. gdb may not be "
"able to find debug symbols, and pretty-printing of STL types may not "
"work. Please see debug instructions for using //tools/gdb/gdbinit:\n"
"https://chromium.googlesource.com/chromium/src/+/master/docs/"
"gdbinit.md\n"
"To continue anyway, type 'continue' in gdb. To always skip this "
"check, define an environment variable CHROMIUM_GDBINIT_SOURCED=1";
#endif
}
#elif defined(OS_FUCHSIA)
bool BeingDebugged() {
zx_info_process_t info = {};
// Ignore failures. The 0-initialization above will result in "false" for
// error cases.
zx_object_get_info(zx_process_self(), ZX_INFO_PROCESS, &info, sizeof(info),
nullptr, nullptr);
return info.debugger_attached;
}
void VerifyDebugger() {}
#else
bool BeingDebugged() {
NOTIMPLEMENTED();
return false;
}
void VerifyDebugger() {}
#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)
PlatformThread::Sleep(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() {
#if BUILDFLAG(CLANG_PROFILING)
WriteClangProfilingProfile();
#endif
// 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;
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.
// When DEBUG_BREAK() expands to abort(), this is unreachable code. Rather
// than carefully tracking in which cases DEBUG_BREAK()s is noreturn, just
// disable the unreachable code warning here.
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunreachable-code"
_exit(1);
#pragma GCC diagnostic pop
#endif
}
} // namespace debug
} // namespace base

View file

@ -0,0 +1,37 @@
// 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>
#include "base/clang_profiling_buildflags.h"
#if BUILDFLAG(CLANG_PROFILING)
#include "base/test/clang_profiling.h"
#endif
namespace base {
namespace debug {
bool BeingDebugged() {
return ::IsDebuggerPresent() != 0;
}
void BreakDebugger() {
#if BUILDFLAG(CLANG_PROFILING)
WriteClangProfilingProfile();
#endif
if (IsDebugUISuppressed())
_exit(1);
__debugbreak();
}
void VerifyDebugger() {}
} // namespace debug
} // namespace base

View 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

View 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_

View file

@ -0,0 +1,189 @@
// Copyright 2018 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/elf_reader.h"
#include <arpa/inet.h>
#include <elf.h>
#include "base/bits.h"
#include "base/containers/span.h"
#include "base/hash/sha1.h"
#include "base/strings/safe_sprintf.h"
#include "build/build_config.h"
// NOTE: This code may be used in crash handling code, so the implementation
// must avoid dynamic memory allocation or using data structures which rely on
// dynamic allocation.
namespace base {
namespace debug {
namespace {
#if __SIZEOF_POINTER__ == 4
using Ehdr = Elf32_Ehdr;
using Dyn = Elf32_Dyn;
using Half = Elf32_Half;
using Nhdr = Elf32_Nhdr;
using Word = Elf32_Word;
#else
using Ehdr = Elf64_Ehdr;
using Dyn = Elf64_Dyn;
using Half = Elf64_Half;
using Nhdr = Elf64_Nhdr;
using Word = Elf64_Word;
#endif
constexpr char kGnuNoteName[] = "GNU";
// Returns a pointer to the header of the ELF binary mapped into memory,
// or a null pointer if the header is invalid.
const Ehdr* GetElfHeader(const void* elf_mapped_base) {
const char* elf_base = reinterpret_cast<const char*>(elf_mapped_base);
if (strncmp(elf_base, ELFMAG, SELFMAG) != 0)
return nullptr;
const Ehdr* elf_header = reinterpret_cast<const Ehdr*>(elf_base);
return elf_header;
}
// Returns the ELF base address that should be used as a starting point to
// access other segments.
const char* GetElfBaseVirtualAddress(const void* elf_mapped_base) {
const char* elf_base = reinterpret_cast<const char*>(elf_mapped_base);
for (const Phdr& header : GetElfProgramHeaders(elf_mapped_base)) {
if (header.p_type == PT_LOAD) {
size_t load_bias = static_cast<size_t>(header.p_vaddr);
CHECK_GE(reinterpret_cast<uintptr_t>(elf_base), load_bias);
return elf_base - load_bias;
}
}
return elf_base;
}
} // namespace
span<const Phdr> GetElfProgramHeaders(const void* elf_mapped_base) {
// NOTE: Function should use async signal safe calls only.
const char* elf_base = reinterpret_cast<const char*>(elf_mapped_base);
const Ehdr* elf_header = GetElfHeader(elf_mapped_base);
if (!elf_header)
return {};
return span<const Phdr>(
reinterpret_cast<const Phdr*>(elf_base + elf_header->e_phoff),
elf_header->e_phnum);
}
size_t ReadElfBuildId(const void* elf_mapped_base,
bool uppercase,
ElfBuildIdBuffer build_id) {
// NOTE: Function should use async signal safe calls only.
const char* elf_virtual_base = GetElfBaseVirtualAddress(elf_mapped_base);
const Ehdr* elf_header = GetElfHeader(elf_mapped_base);
if (!elf_header)
return 0;
for (const Phdr& header : GetElfProgramHeaders(elf_mapped_base)) {
if (header.p_type != PT_NOTE)
continue;
// Look for a NT_GNU_BUILD_ID note with name == "GNU".
const char* current_section = elf_virtual_base + header.p_vaddr;
const char* section_end = current_section + header.p_memsz;
const Nhdr* current_note = nullptr;
bool found = false;
while (current_section < section_end) {
current_note = reinterpret_cast<const Nhdr*>(current_section);
if (current_note->n_type == NT_GNU_BUILD_ID) {
StringPiece note_name(current_section + sizeof(Nhdr),
current_note->n_namesz);
// Explicit constructor is used to include the '\0' character.
if (note_name == StringPiece(kGnuNoteName, sizeof(kGnuNoteName))) {
found = true;
break;
}
}
size_t section_size = bits::Align(current_note->n_namesz, 4) +
bits::Align(current_note->n_descsz, 4) +
sizeof(Nhdr);
if (section_size > static_cast<size_t>(section_end - current_section))
return 0;
current_section += section_size;
}
if (!found)
continue;
// Validate that the serialized build ID will fit inside |build_id|.
size_t note_size = current_note->n_descsz;
if ((note_size * 2) > kMaxBuildIdStringLength)
continue;
// Write out the build ID as a null-terminated hex string.
const uint8_t* build_id_raw =
reinterpret_cast<const uint8_t*>(current_note) + sizeof(Nhdr) +
bits::Align(current_note->n_namesz, 4);
size_t i = 0;
for (i = 0; i < current_note->n_descsz; ++i) {
strings::SafeSNPrintf(&build_id[i * 2], 3, (uppercase ? "%02X" : "%02x"),
build_id_raw[i]);
}
build_id[i * 2] = '\0';
// Return the length of the string.
return i * 2;
}
return 0;
}
Optional<StringPiece> ReadElfLibraryName(const void* elf_mapped_base) {
// NOTE: Function should use async signal safe calls only.
const char* elf_base = reinterpret_cast<const char*>(elf_mapped_base);
const Ehdr* elf_header = GetElfHeader(elf_mapped_base);
if (!elf_header)
return {};
for (const Phdr& header : GetElfProgramHeaders(elf_mapped_base)) {
if (header.p_type != PT_DYNAMIC)
continue;
// Read through the ELF dynamic sections to find the string table and
// SONAME offsets, which are used to compute the offset of the library
// name string.
const Dyn* dynamic_start =
reinterpret_cast<const Dyn*>(elf_base + header.p_vaddr);
const Dyn* dynamic_end = reinterpret_cast<const Dyn*>(
elf_base + header.p_vaddr + header.p_memsz);
Word soname_strtab_offset = 0;
const char* strtab_addr = 0;
for (const Dyn* dynamic_iter = dynamic_start; dynamic_iter < dynamic_end;
++dynamic_iter) {
if (dynamic_iter->d_tag == DT_STRTAB) {
#if defined(OS_FUCHSIA) || defined(OS_ANDROID)
// Fuchsia and Android executables are position-independent, so treat
// pointers in the ELF header as offsets into the address space instead
// of absolute addresses.
strtab_addr = (size_t)dynamic_iter->d_un.d_ptr + (const char*)elf_base;
#else
strtab_addr = (const char*)dynamic_iter->d_un.d_ptr;
#endif
} else if (dynamic_iter->d_tag == DT_SONAME) {
soname_strtab_offset = dynamic_iter->d_un.d_val;
}
}
if (soname_strtab_offset && strtab_addr)
return StringPiece(strtab_addr + soname_strtab_offset);
}
return nullopt;
}
} // namespace debug
} // namespace base

View file

@ -0,0 +1,50 @@
// Copyright 2018 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_ELF_READER_H_
#define BASE_DEBUG_ELF_READER_H_
#include <elf.h>
#include "base/base_export.h"
#include "base/containers/span.h"
#include "base/hash/sha1.h"
#include "base/optional.h"
#include "base/strings/string_piece.h"
// Functions for querying metadata from ELF binaries.
// All functions require that the file be fully memory mapped.
#if __SIZEOF_POINTER__ == 4
using Phdr = Elf32_Phdr;
#else
using Phdr = Elf64_Phdr;
#endif
namespace base {
namespace debug {
// Hex-encodes the build ID from the ELF binary located at |elf_base|.
// Returns the length of the build ID in bytes, or zero if the build ID couldn't
// be read.
// When |uppercase| is |true|, the output string is written using uppercase hex
// characters. Otherwise, the output is lowercased.
constexpr size_t kMaxBuildIdStringLength = kSHA1Length * 2;
using ElfBuildIdBuffer = char[kMaxBuildIdStringLength + 1];
size_t BASE_EXPORT ReadElfBuildId(const void* elf_base,
bool uppercase,
ElfBuildIdBuffer build_id);
// Returns the library name from the ELF file mapped at |elf_base|.
// Returns an empty result if the name could not be read.
Optional<StringPiece> BASE_EXPORT ReadElfLibraryName(const void* elf_base);
// Returns a span of ELF program headers for the ELF file mapped at
// |elf_base|, or an empty span if the header couldn't be read.
span<const Phdr> BASE_EXPORT GetElfProgramHeaders(const void* elf_base);
} // namespace debug
} // namespace base
#endif // BASE_DEBUG_ELF_READER_H_

View file

@ -0,0 +1,509 @@
// 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 <TlHelp32.h>
#include <psapi.h>
#include <stddef.h>
#include <windows.h>
#include <winternl.h>
#include "base/debug/alias.h"
#include "base/logging.h"
#include "base/optional.h"
#include "base/process/process.h"
#include "base/win/scoped_handle.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
namespace {
// A partial PEB up until GdiSharedHandleTable.
// Derived from the ntdll symbols (ntdll!_PEB).
template <typename PointerType>
struct PartialWinPeb {
unsigned char InheritedAddressSpace;
unsigned char ReadImageFileExecOptions;
unsigned char BeingDebugged;
unsigned char ImageUsesLargePages : 1;
unsigned char IsProtectedProcess : 1;
unsigned char IsLegacyProcess : 1;
unsigned char IsImageDynamicallyRelocated : 1;
unsigned char SkipPatchingUser32Forwarders : 1;
unsigned char IsAppContainer : 1;
unsigned char IsProtectedProcessLight : 1;
unsigned char IsLongPathAwareProcess : 1;
PointerType Mutant;
PointerType ImageBaseAddress;
PointerType Ldr;
PointerType ProcessParamters;
PointerType SubSystemData;
PointerType ProcessHeap;
PointerType FastPebLock;
PointerType AtlThunkSListPtr;
PointerType IFEOKey;
uint32_t ProcessInJob : 1;
uint32_t ProcessInitializing : 1;
uint32_t ProcessUsingVEH : 1;
uint32_t ProcessUsingVCH : 1;
uint32_t ProcessUsingFTH : 1;
uint32_t ProcessPreviouslyThrottled : 1;
uint32_t ProcessCurrentlyThrottled : 1;
uint32_t ProcessImagesHotPatched : 1;
PointerType KernelCallbackTable;
uint32_t SystemReserved;
uint32_t AtlThunkSListPtr32;
PointerType ApiSetMap;
uint32_t TlsExpansionCounter;
PointerType TlsBitmap;
uint32_t TlsBitmapBits[2];
PointerType ReadOnlySharedMemoryBase;
PointerType HotpatchInformation;
PointerType ReadOnlyStaticServerData;
PointerType AnsiCodePageData;
PointerType OemCodePageData;
PointerType UnicodeCaseTableData;
uint32_t NumberOfProcessors;
uint32_t NtGlobalFlag;
uint64_t CriticalSectionTimeout;
PointerType HeapSegmentReserve;
PointerType HeapSegmentCommit;
PointerType HeapDeCommitTotalFreeThreshold;
PointerType HeapDeCommitFreeBlockThreshold;
uint32_t NumberOfHeaps;
uint32_t MaximumNumberOfHeaps;
PointerType ProcessHeaps;
PointerType GdiSharedHandleTable;
};
// Found from
// https://stackoverflow.com/questions/13905661/how-to-get-list-of-gdi-handles.
enum GdiHandleType : USHORT {
kDC = 1,
kRegion = 4,
kBitmap = 5,
kPalette = 8,
kFont = 10,
kBrush = 16,
kPen = 48,
};
// Adapted from GDICELL.
template <typename PointerType>
struct GdiTableEntry {
PointerType pKernelAddress;
USHORT wProcessId;
USHORT wCount;
USHORT wUpper;
GdiHandleType wType;
PointerType pUserAddress;
};
// Types and names used for regular processes.
struct RegularProcessTypes {
using QueryInformationProcessFunc = decltype(NtQueryInformationProcess);
static const char* query_information_process_name;
// PROCESS_BASIC_INFORMATION
struct ProcessBasicInformation {
PVOID Reserved1;
PVOID PebBaseAddress;
PVOID Reserved2[2];
ULONG_PTR UniqueProcessId;
PVOID Reserved3;
};
using ReadVirtualMemoryFunc = NTSTATUS NTAPI(IN HANDLE ProcessHandle,
IN PVOID BaseAddress,
OUT PVOID Buffer,
IN SIZE_T Size,
OUT PSIZE_T NumberOfBytesRead);
static const char* read_virtual_memory_func_name;
using NativePointerType = PVOID;
};
// static
const char* RegularProcessTypes::query_information_process_name =
"NtQueryInformationProcess";
// static
const char* RegularProcessTypes::read_virtual_memory_func_name =
"NtReadVirtualMemory";
// Types and names used for WOW based processes.
struct WowProcessTypes {
// http://crbug.com/972185: Clang doesn't handle PVOID64 correctly, so we use
// uint64_t as a substitute.
// NtWow64QueryInformationProcess64 and NtQueryInformationProcess share the
// same signature.
using QueryInformationProcessFunc = decltype(NtQueryInformationProcess);
static const char* query_information_process_name;
// PROCESS_BASIC_INFORMATION_WOW64
struct ProcessBasicInformation {
PVOID Reserved1[2];
uint64_t PebBaseAddress;
PVOID Reserved2[4];
ULONG_PTR UniqueProcessId[2];
PVOID Reserved3[2];
};
using ReadVirtualMemoryFunc = NTSTATUS NTAPI(IN HANDLE ProcessHandle,
IN uint64_t BaseAddress,
OUT PVOID Buffer,
IN ULONG64 Size,
OUT PULONG64 NumberOfBytesRead);
static const char* read_virtual_memory_func_name;
using NativePointerType = uint64_t;
};
// static
const char* WowProcessTypes::query_information_process_name =
"NtWow64QueryInformationProcess64";
// static
const char* WowProcessTypes::read_virtual_memory_func_name =
"NtWow64ReadVirtualMemory64";
// To prevent from having to write a regular and WOW codepaths that do the same
// thing with different structures and functions, GetGdiTableEntries is
// templated to expect either RegularProcessTypes or WowProcessTypes.
template <typename ProcessType>
std::vector<GdiTableEntry<typename ProcessType::NativePointerType>>
GetGdiTableEntries(const base::Process& process) {
using GdiTableEntryVector =
std::vector<GdiTableEntry<typename ProcessType::NativePointerType>>;
HMODULE ntdll = GetModuleHandle(L"ntdll.dll");
if (!ntdll)
return GdiTableEntryVector();
static auto query_information_process_func =
reinterpret_cast<typename ProcessType::QueryInformationProcessFunc*>(
GetProcAddress(ntdll, ProcessType::query_information_process_name));
if (!query_information_process_func) {
LOG(ERROR) << ProcessType::query_information_process_name << " Missing";
return GdiTableEntryVector();
}
typename ProcessType::ProcessBasicInformation basic_info;
NTSTATUS result =
query_information_process_func(process.Handle(), ProcessBasicInformation,
&basic_info, sizeof(basic_info), nullptr);
if (result != 0) {
LOG(ERROR) << ProcessType::query_information_process_name << " Failed "
<< std::hex << result;
return GdiTableEntryVector();
}
static auto read_virtual_mem_func =
reinterpret_cast<typename ProcessType::ReadVirtualMemoryFunc*>(
GetProcAddress(ntdll, ProcessType::read_virtual_memory_func_name));
if (!read_virtual_mem_func) {
LOG(ERROR) << ProcessType::read_virtual_memory_func_name << " Missing";
return GdiTableEntryVector();
}
PartialWinPeb<typename ProcessType::NativePointerType> peb;
result = read_virtual_mem_func(process.Handle(), basic_info.PebBaseAddress,
&peb, sizeof(peb), nullptr);
if (result != 0) {
LOG(ERROR) << ProcessType::read_virtual_memory_func_name << " PEB Failed "
<< std::hex << result;
return GdiTableEntryVector();
}
// Estimated size derived from address space allocation of the table:
// Windows 10
// 32-bit Size: 1052672 bytes
// 64-bit Size: 1576960 bytes
// sizeof(GdiTableEntry)
// 32-bit: 16 bytes
// 64-bit: 24 bytes
// Entry Count
// 32-bit: 65792
// 64-bit: 65706ish
// So we'll take a look at 65536 entries since that's the maximum handle count.
constexpr int kGdiTableEntryCount = 65536;
GdiTableEntryVector entries;
entries.resize(kGdiTableEntryCount);
result = read_virtual_mem_func(
process.Handle(), peb.GdiSharedHandleTable, &entries[0],
sizeof(typename GdiTableEntryVector::value_type) * entries.size(),
nullptr);
if (result != 0) {
LOG(ERROR) << ProcessType::read_virtual_memory_func_name
<< " GDI Handle Table Failed " << std::hex << result;
return GdiTableEntryVector();
}
return entries;
}
// Iterates through |gdi_table| and finds handles that belong to |pid|,
// incrementing the appropriate fields in |base::debug::GdiHandleCounts|.
template <typename PointerType>
base::debug::GdiHandleCounts CountHandleTypesFromTable(
DWORD pid,
const std::vector<GdiTableEntry<PointerType>>& gdi_table) {
base::debug::GdiHandleCounts counts{};
for (const auto& entry : gdi_table) {
if (entry.wProcessId != pid)
continue;
switch (entry.wType & 0x7F) {
case GdiHandleType::kDC:
++counts.dcs;
break;
case GdiHandleType::kRegion:
++counts.regions;
break;
case GdiHandleType::kBitmap:
++counts.bitmaps;
break;
case GdiHandleType::kPalette:
++counts.palettes;
break;
case GdiHandleType::kFont:
++counts.fonts;
break;
case GdiHandleType::kBrush:
++counts.brushes;
break;
case GdiHandleType::kPen:
++counts.pens;
break;
default:
++counts.unknown;
break;
}
}
counts.total_tracked = counts.dcs + counts.regions + counts.bitmaps +
counts.palettes + counts.fonts + counts.brushes +
counts.pens + counts.unknown;
return counts;
}
template <typename ProcessType>
base::Optional<base::debug::GdiHandleCounts> CollectGdiHandleCountsImpl(
DWORD pid) {
base::Process process = base::Process::OpenWithExtraPrivileges(pid);
if (!process.IsValid())
return base::nullopt;
std::vector<GdiTableEntry<typename ProcessType::NativePointerType>>
gdi_entries = GetGdiTableEntries<ProcessType>(process);
return CountHandleTypesFromTable(pid, gdi_entries);
}
// Returns the GDI Handle counts from the GDI Shared handle table. Empty on
// failure.
base::Optional<base::debug::GdiHandleCounts> CollectGdiHandleCounts(DWORD pid) {
if (base::win::OSInfo::GetInstance()->wow64_status() ==
base::win::OSInfo::WOW64_ENABLED) {
return CollectGdiHandleCountsImpl<WowProcessTypes>(pid);
}
return CollectGdiHandleCountsImpl<RegularProcessTypes>(pid);
}
constexpr size_t kLotsOfMemory = 1500 * 1024 * 1024; // 1.5GB
HANDLE NOINLINE GetToolhelpSnapshot() {
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
CHECK_NE(INVALID_HANDLE_VALUE, snapshot);
return snapshot;
}
void NOINLINE GetFirstProcess(HANDLE snapshot, PROCESSENTRY32* proc_entry) {
proc_entry->dwSize = sizeof(PROCESSENTRY32);
CHECK(Process32First(snapshot, proc_entry));
}
void NOINLINE CrashIfExcessiveHandles(DWORD num_gdi_handles) {
// By default, Windows 10 allows a max of 10,000 GDI handles per process.
// Number found by inspecting
//
// HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\
// CurrentVersion\Windows\GDIProcessHandleQuota
//
// on a Windows 10 laptop.
static constexpr DWORD kLotsOfHandles = 9990;
CHECK_LE(num_gdi_handles, kLotsOfHandles);
}
void NOINLINE
CrashIfPagefileUsageTooLarge(const PROCESS_MEMORY_COUNTERS_EX& pmc) {
CHECK_LE(pmc.PagefileUsage, kLotsOfMemory);
}
void NOINLINE
CrashIfPrivateUsageTooLarge(const PROCESS_MEMORY_COUNTERS_EX& pmc) {
CHECK_LE(pmc.PrivateUsage, kLotsOfMemory);
}
void NOINLINE CrashIfCannotAllocateSmallBitmap(BITMAPINFOHEADER* header,
HANDLE shared_section) {
void* small_data = nullptr;
base::debug::Alias(&small_data);
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);
}
void NOINLINE GetProcessMemoryInfo(PROCESS_MEMORY_COUNTERS_EX* pmc) {
pmc->cb = sizeof(*pmc);
CHECK(GetProcessMemoryInfo(GetCurrentProcess(),
reinterpret_cast<PROCESS_MEMORY_COUNTERS*>(pmc),
sizeof(*pmc)));
}
DWORD NOINLINE GetNumGdiHandles() {
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);
}
return num_gdi_handles;
}
void CollectChildGDIUsageAndDie(DWORD parent_pid) {
HANDLE snapshot = GetToolhelpSnapshot();
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 = {};
GetFirstProcess(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 = GetNumGdiHandles();
base::debug::Alias(&num_gdi_handles);
base::debug::Alias(&num_user_handles);
base::Optional<GdiHandleCounts> optional_handle_counts =
CollectGdiHandleCounts(GetCurrentProcessId());
bool handle_counts_set = optional_handle_counts.has_value();
GdiHandleCounts handle_counts =
optional_handle_counts.value_or(GdiHandleCounts());
int tracked_dcs = handle_counts.dcs;
int tracked_regions = handle_counts.regions;
int tracked_bitmaps = handle_counts.bitmaps;
int tracked_palettes = handle_counts.palettes;
int tracked_fonts = handle_counts.fonts;
int tracked_brushes = handle_counts.brushes;
int tracked_pens = handle_counts.pens;
int tracked_unknown_handles = handle_counts.unknown;
int tracked_total = handle_counts.total_tracked;
base::debug::Alias(&handle_counts_set);
base::debug::Alias(&tracked_dcs);
base::debug::Alias(&tracked_regions);
base::debug::Alias(&tracked_bitmaps);
base::debug::Alias(&tracked_palettes);
base::debug::Alias(&tracked_fonts);
base::debug::Alias(&tracked_brushes);
base::debug::Alias(&tracked_pens);
base::debug::Alias(&tracked_unknown_handles);
base::debug::Alias(&tracked_total);
CrashIfExcessiveHandles(num_gdi_handles);
PROCESS_MEMORY_COUNTERS_EX pmc;
GetProcessMemoryInfo(&pmc);
CrashIfPagefileUsageTooLarge(pmc);
CrashIfPrivateUsageTooLarge(pmc);
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.
CrashIfCannotAllocateSmallBitmap(header, shared_section);
}
// Maybe the child processes are the ones leaking GDI or USER resouces.
CollectChildGDIUsageAndDie(GetCurrentProcessId());
}
GdiHandleCounts GetGDIHandleCountsInCurrentProcessForTesting() {
base::Optional<GdiHandleCounts> handle_counts =
CollectGdiHandleCounts(GetCurrentProcessId());
DCHECK(handle_counts.has_value());
return handle_counts.value_or(GdiHandleCounts());
}
} // namespace debug
} // namespace base

View file

@ -0,0 +1,39 @@
// 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 {
struct BASE_EXPORT GdiHandleCounts {
int dcs = 0;
int regions = 0;
int bitmaps = 0;
int palettes = 0;
int fonts = 0;
int brushes = 0;
int pens = 0;
int unknown = 0;
int total_tracked = 0;
};
// 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.
BASE_EXPORT void CollectGDIUsageAndDie(BITMAPINFOHEADER* header = nullptr,
HANDLE shared_section = nullptr);
BASE_EXPORT GdiHandleCounts GetGDIHandleCountsInCurrentProcessForTesting();
} // namespace debug
} // namespace base
#endif // BASE_DEBUG_GDI_DEBUG_UTIL_WIN_H_

View file

@ -0,0 +1,56 @@
// Copyright 2018 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/invalid_access_win.h"
#include <stdlib.h>
#include <windows.h>
#include "base/logging.h"
#include "base/win/windows_version.h"
namespace base {
namespace debug {
namespace win {
namespace {
void CreateSyntheticHeapCorruption() {
EXCEPTION_RECORD record = {};
record.ExceptionCode = STATUS_HEAP_CORRUPTION;
RaiseFailFastException(&record, nullptr,
FAIL_FAST_GENERATE_EXCEPTION_ADDRESS);
}
} // namespace
void TerminateWithHeapCorruption() {
__try {
// Pre-Windows 10, it's hard to trigger a heap corruption fast fail, so
// artificially create one instead.
if (base::win::GetVersion() < base::win::Version::WIN10)
CreateSyntheticHeapCorruption();
HANDLE heap = ::HeapCreate(0, 0, 0);
CHECK(heap);
CHECK(HeapSetInformation(heap, HeapEnableTerminationOnCorruption, nullptr,
0));
void* addr = ::HeapAlloc(heap, 0, 0x1000);
CHECK(addr);
// Corrupt heap header.
char* addr_mutable = reinterpret_cast<char*>(addr);
memset(addr_mutable - sizeof(addr), 0xCC, sizeof(addr));
HeapFree(heap, 0, addr);
HeapDestroy(heap);
} __except (EXCEPTION_EXECUTE_HANDLER) {
// Heap corruption exception should never be caught.
CHECK(false);
}
// Should never reach here.
abort();
}
} // namespace win
} // namespace debug
} // namespace base

View file

@ -0,0 +1,22 @@
// Copyright 2018 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_INVALID_ACCESS_WIN_H_
#define BASE_DEBUG_INVALID_ACCESS_WIN_H_
#include "base/base_export.h"
namespace base {
namespace debug {
namespace win {
// Creates a synthetic heap corruption that causes the current process to
// terminate immediately with a fast fail exception.
[[noreturn]] BASE_EXPORT void TerminateWithHeapCorruption();
} // namespace win
} // namespace debug
} // namespace base
#endif // BASE_DEBUG_INVALID_ACCESS_WIN_H_

View 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_

View file

@ -0,0 +1,142 @@
// 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:
// This destructor suppresses warnings about instances of this class not being
// used.
~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_

View 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",
&region.start, &region.end, permissions, &region.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

View 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_

View file

@ -0,0 +1,180 @@
// 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/allocator/buildflags.h"
#include "base/debug/debugging_buildflags.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) && BUILDFLAG(USE_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) && BUILDFLAG(USE_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 = NumberToString(GetCurrentProcId());
std::string count = NumberToString(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)
ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc() {
return nullptr;
}
AddDynamicSymbol GetProfilerAddDynamicSymbolFunc() {
return nullptr;
}
MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc() {
return nullptr;
}
#else // defined(OS_WIN)
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) {
base::win::PEImage image(CURRENT_MODULE());
FunctionSearchContext ctx = { function_name, NULL };
image.EnumImportChunks(FindResolutionFunctionInImports, &ctx, nullptr);
return reinterpret_cast<FunctionType>(ctx.function);
}
} // namespace
ReturnAddressLocationResolver GetProfilerReturnAddrResolutionFunc() {
return FindFunctionInImports<ReturnAddressLocationResolver>(
"ResolveReturnAddressLocation");
}
AddDynamicSymbol GetProfilerAddDynamicSymbolFunc() {
return FindFunctionInImports<AddDynamicSymbol>(
"AddDynamicSymbol");
}
MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc() {
return FindFunctionInImports<MoveDynamicSymbol>(
"MoveDynamicSymbol");
}
#endif // defined(OS_WIN)
} // namespace debug
} // namespace base

View file

@ -0,0 +1,76 @@
// 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 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);
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 AddDynamicSymbol GetProfilerAddDynamicSymbolFunc();
BASE_EXPORT MoveDynamicSymbol GetProfilerMoveDynamicSymbolFunc();
} // namespace debug
} // namespace base
#endif // BASE_DEBUG_PROFILER_H_

View file

@ -0,0 +1,305 @@
// 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/stl_util.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) {
const uintptr_t* fp_addr = reinterpret_cast<const uintptr_t*>(fp);
MSAN_UNPOISON(fp_addr, sizeof(uintptr_t));
return fp_addr[0] - kStackFrameAdjustment;
}
uintptr_t GetStackFramePC(uintptr_t fp) {
const uintptr_t* fp_addr = reinterpret_cast<const uintptr_t*>(fp);
MSAN_UNPOISON(&fp_addr[1], sizeof(uintptr_t));
return fp_addr[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(base::size(trace_)) {}
StackTrace::StackTrace(size_t count) {
count_ = CollectStackTrace(trace_, std::min(count, base::size(trace_)));
}
StackTrace::StackTrace(const void* const* trace, size_t count) {
count = std::min(count, base::size(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;
}
void StackTrace::Print() const {
PrintWithPrefix(nullptr);
}
void StackTrace::OutputToStream(std::ostream* os) const {
OutputToStreamWithPrefix(os, nullptr);
}
std::string StackTrace::ToString() const {
return ToStringWithPrefix(nullptr);
}
std::string StackTrace::ToStringWithPrefix(const char* prefix_string) const {
std::stringstream stream;
#if !defined(__UCLIBC__) && !defined(_AIX)
OutputToStreamWithPrefix(&stream, prefix_string);
#endif
return stream.str();
}
std::ostream& operator<<(std::ostream& os, const StackTrace& s) {
#if !defined(__UCLIBC__) & !defined(_AIX)
s.OutputToStream(&os);
#else
os << "StackTrace::OutputToStream not implemented.";
#endif
return os;
}
#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

View file

@ -0,0 +1,232 @@
// 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_buildflags.h"
#include "base/macros.h"
#include "build/build_config.h"
#if defined(OS_POSIX)
#if !defined(OS_NACL)
#include <signal.h>
#endif
#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) && !defined(OS_NACL)
// Sets a first-chance callback for the stack dump signal handler. This callback
// is called at the beginning of the signal handler to handle special kinds of
// signals, like out-of-bounds memory accesses in WebAssembly (WebAssembly Trap
// Handler).
// {SetStackDumpFirstChanceCallback} returns {true} if the callback
// has been set correctly. It returns {false} if the stack dump signal handler
// has not been registered with the OS, e.g. because of ASAN.
BASE_EXPORT bool SetStackDumpFirstChanceCallback(bool (*handler)(int,
siginfo_t*,
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. Addresses()[0] will contain an
// address from the leaf function, and Addresses()[count-1] will contain an
// address from the root function (i.e.; the thread's entry point).
const void* const* Addresses(size_t* count) const;
// Prints the stack trace to stderr.
void Print() const;
// Prints the stack trace to stderr, prepending the given string before
// each output line.
void PrintWithPrefix(const char* prefix_string) const;
#if !defined(__UCLIBC__) & !defined(_AIX)
// Resolves backtrace to symbols and write to stream.
void OutputToStream(std::ostream* os) const;
// Resolves backtrace to symbols and write to stream, with the provided
// prefix string prepended to each line.
void OutputToStreamWithPrefix(std::ostream* os,
const char* prefix_string) const;
#endif
// Resolves backtrace to symbols and returns as string.
std::string ToString() const;
// Resolves backtrace to symbols and returns as string, prepending the
// provided prefix string to each line.
std::string ToStringWithPrefix(const char* prefix_string) const;
private:
#if defined(OS_WIN)
void InitTrace(const _CONTEXT* context_record);
#endif
#if defined(OS_ANDROID)
// TODO(https://crbug.com/925525): Testing indicates that Android has issues
// with a larger value here, so leave Android at 62.
static constexpr int kMaxTraces = 62;
#else
// For other platforms, use 250. This seems reasonable without
// being huge.
static constexpr int kMaxTraces = 250;
#endif
void* trace_[kMaxTraces];
// The number of valid frames in |trace_|.
size_t count_;
};
// Forwards to StackTrace::OutputToStream().
BASE_EXPORT std::ostream& operator<<(std::ostream& os, const StackTrace& s);
// Record a stack trace with up to |count| frames into |trace|. Returns the
// number of frames read.
BASE_EXPORT size_t CollectStackTrace(void** 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_

View file

@ -0,0 +1,151 @@
// 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/stl_util.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;
}
bool EndsWith(const std::string& s, const std::string& suffix) {
return s.size() >= suffix.size() &&
s.substr(s.size() - suffix.size(), suffix.size()) == suffix;
}
} // 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);
}
size_t CollectStackTrace(void** trace, size_t count) {
StackCrawlState state(reinterpret_cast<uintptr_t*>(trace), count);
_Unwind_Backtrace(&TraceStackFrame, &state);
return state.frame_count;
}
void StackTrace::PrintWithPrefix(const char* prefix_string) const {
std::string backtrace = ToStringWithPrefix(prefix_string);
__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::OutputToStreamWithPrefix(std::ostream* os,
const char* prefix_string) 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, &regions)) {
__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;
}
if (prefix_string)
*os << prefix_string;
// Adjust absolute address to be an offset within the mapped region, to
// match the format dumped by Android's crash output.
if (iter != regions.end()) {
address -= iter->start;
}
// The format below intentionally matches that of Android's debuggerd
// output. This simplifies decoding by scripts such as stack.py.
*os << base::StringPrintf("#%02zd pc " FMT_ADDR " ", i, address);
if (iter != regions.end()) {
*os << base::StringPrintf("%s", iter->path.c_str());
if (EndsWith(iter->path, ".apk")) {
*os << base::StringPrintf(" (offset 0x%llx)", iter->offset);
}
} else {
*os << "<unknown>";
}
*os << "\n";
}
}
} // namespace debug
} // namespace base

View file

@ -0,0 +1,283 @@
// 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 <elf.h>
#include <link.h>
#include <stddef.h>
#include <threads.h>
#include <unwind.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/port.h>
#include <zircon/types.h>
#include <algorithm>
#include <array>
#include <iomanip>
#include <iostream>
#include <type_traits>
#include "base/atomic_sequence_num.h"
#include "base/debug/elf_reader.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
namespace base {
namespace debug {
namespace {
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;
}
// Build a "rwx" C string-based representation of the permission bits.
// The output buffer is reused across calls, and should not be retained across
// consecutive invocations of this function.
const char* PermissionFlagsToString(int flags, char permission_buf[4]) {
char* permission = permission_buf;
if (flags & PF_R)
(*permission++) = 'r';
if (flags & PF_W)
(*permission++) = 'w';
if (flags & PF_X)
(*permission++) = 'x';
*permission = '\0';
return permission_buf;
}
// Stores and queries debugging symbol map info for the current process.
class SymbolMap {
public:
struct Segment {
const void* addr = nullptr;
size_t relative_addr = 0;
int permission_flags = 0;
size_t size = 0;
};
struct Module {
// Maximum number of PT_LOAD segments to process per ELF binary. Most
// binaries have only 2-3 such segments.
static constexpr size_t kMaxSegmentCount = 8;
const void* addr = nullptr;
std::array<Segment, kMaxSegmentCount> segments;
size_t segment_count = 0;
char name[ZX_MAX_NAME_LEN + 1] = {0};
char build_id[kMaxBuildIdStringLength + 1] = {0};
};
SymbolMap();
~SymbolMap() = default;
// Gets all entries for the symbol map.
span<Module> GetModules() { return {modules_.data(), count_}; }
private:
// Component builds of Chrome pull about 250 shared libraries (on Linux), so
// 512 entries should be enough in most cases.
static const size_t kMaxMapEntries = 512;
void Populate();
// Sorted in descending order by address, for lookup purposes.
std::array<Module, kMaxMapEntries> modules_;
size_t count_ = 0;
bool valid_ = false;
DISALLOW_COPY_AND_ASSIGN(SymbolMap);
};
SymbolMap::SymbolMap() {
Populate();
}
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[std::extent<decltype(SymbolMap::Module::name)>()];
zx_status_t status =
zx_object_get_property(process, ZX_PROP_NAME, app_name, sizeof(app_name));
if (status == ZX_OK) {
// The process name may have a process type suffix at the end (e.g.
// "context", "renderer", gpu"), which doesn't belong in the module list.
// Trim the suffix from the name.
for (size_t i = 0; i < base::size(app_name) && app_name[i] != '\0'; ++i) {
if (app_name[i] == ':') {
app_name[i] = 0;
break;
}
}
} else {
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.
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;
}
// Populate ELF binary metadata into |modules_|.
while (lmap != nullptr) {
if (count_ >= kMaxMapEntries)
break;
SymbolMap::Module& next_entry = modules_[count_];
++count_;
next_entry.addr = reinterpret_cast<void*>(lmap->l_addr);
// Create Segment sub-entries for all PT_LOAD headers.
// Each Segment corresponds to a "mmap" line in the output.
next_entry.segment_count = 0;
for (const Elf64_Phdr& phdr : GetElfProgramHeaders(next_entry.addr)) {
if (phdr.p_type != PT_LOAD)
continue;
if (next_entry.segment_count > Module::kMaxSegmentCount) {
LOG(WARNING) << "Exceeded the maximum number of segments.";
break;
}
Segment segment;
segment.addr =
reinterpret_cast<const char*>(next_entry.addr) + phdr.p_vaddr;
segment.relative_addr = phdr.p_vaddr;
segment.size = phdr.p_memsz;
segment.permission_flags = phdr.p_flags;
next_entry.segments[next_entry.segment_count] = std::move(segment);
++next_entry.segment_count;
}
// Get the human-readable library name from the ELF header, falling back on
// using names from the link map for binaries that aren't shared libraries.
Optional<StringPiece> elf_library_name =
ReadElfLibraryName(next_entry.addr);
if (elf_library_name) {
strlcpy(next_entry.name, elf_library_name->data(),
elf_library_name->size() + 1);
} else {
StringPiece link_map_name(lmap->l_name[0] ? lmap->l_name : app_name);
// The "module" stack trace annotation doesn't allow for strings which
// resemble paths, so extract the filename portion from |link_map_name|.
size_t directory_prefix_idx = link_map_name.find_last_of("/");
if (directory_prefix_idx != StringPiece::npos) {
link_map_name = link_map_name.substr(
directory_prefix_idx + 1,
link_map_name.size() - directory_prefix_idx - 1);
}
strlcpy(next_entry.name, link_map_name.data(), link_map_name.size() + 1);
}
if (!ReadElfBuildId(next_entry.addr, false, next_entry.build_id)) {
LOG(WARNING) << "Couldn't read build ID.";
continue;
}
lmap = lmap->l_next;
}
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;
}
size_t CollectStackTrace(void** trace, size_t count) {
size_t frame_count = 0;
BacktraceData data = {trace, &frame_count, count};
_Unwind_Backtrace(&UnwindStore, &data);
return frame_count;
}
void StackTrace::PrintWithPrefix(const char* prefix_string) const {
OutputToStreamWithPrefix(&std::cerr, prefix_string);
}
// Emits stack trace data using the symbolizer markup format specified at:
// https://fuchsia.googlesource.com/zircon/+/master/docs/symbolizer_markup.md
void StackTrace::OutputToStreamWithPrefix(std::ostream* os,
const char* prefix_string) const {
SymbolMap map;
int module_id = 0;
*os << "{{{reset}}}\n";
for (const SymbolMap::Module& entry : map.GetModules()) {
*os << "{{{module:" << module_id << ":" << entry.name
<< ":elf:" << entry.build_id << "}}}\n";
for (size_t i = 0; i < entry.segment_count; ++i) {
const SymbolMap::Segment& segment = entry.segments[i];
char permission_string[4] = {};
*os << "{{{mmap:" << segment.addr << ":0x" << std::hex << segment.size
<< std::dec << ":load:" << module_id << ":"
<< PermissionFlagsToString(segment.permission_flags,
permission_string)
<< ":"
<< "0x" << std::hex << segment.relative_addr << std::dec << "}}}\n";
}
++module_id;
}
for (size_t i = 0; i < count_; ++i)
*os << "{{{bt:" << i << ":" << trace_[i] << "}}}\n";
}
} // namespace debug
} // namespace base

View file

@ -0,0 +1,363 @@
// 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/memory/singleton.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "build/build_config.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) {
// Force a reinitialization. Will ensure any modules loaded after process
// startup also get symbolized.
SymCleanup(GetCurrentProcess());
g_initialized_symbols = false;
}
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.
static constexpr size_t kSymbolsArraySize = 1024;
wchar_t symbols_path[kSymbolsArraySize];
// Note: The below function takes buffer size as number of characters,
// not number of bytes!
if (!SymGetSearchPathW(GetCurrentProcess(), symbols_path,
kSymbolsArraySize)) {
g_init_error = GetLastError();
DLOG(WARNING) << "SymGetSearchPath failed: " << g_init_error;
return false;
}
std::wstring new_path = StringPrintf(L"%ls;%ls", symbols_path,
GetExePath().DirName().value().c_str());
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,
const char* prefix_string) {
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.
if (prefix_string)
(*os) << prefix_string;
(*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();
}
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();
}
NOINLINE size_t CollectStackTrace(void** trace, size_t count) {
// When walking our own stack, use CaptureStackBackTrace().
return CaptureStackBackTrace(0, count, trace, NULL);
}
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(ARCH_CPU_X86_64)
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;
#elif defined(ARCH_CPU_ARM64)
int machine_type = IMAGE_FILE_MACHINE_ARM64;
stack_frame.AddrPC.Offset = context_record->Pc;
stack_frame.AddrFrame.Offset = context_record->Fp;
stack_frame.AddrStack.Offset = context_record->Sp;
#elif defined(ARCH_CPU_X86)
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;
#else
#error Unsupported Windows Arch
#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_ < size(trace_)) {
trace_[count_++] = reinterpret_cast<void*>(stack_frame.AddrPC.Offset);
}
for (size_t i = count_; i < size(trace_); ++i)
trace_[i] = NULL;
}
void StackTrace::PrintWithPrefix(const char* prefix_string) const {
OutputToStreamWithPrefix(&std::cerr, prefix_string);
}
void StackTrace::OutputToStreamWithPrefix(std::ostream* os,
const char* prefix_string) 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) {
if (prefix_string)
(*os) << prefix_string;
(*os) << "\t" << trace_[i] << "\n";
}
} else {
(*os) << "Backtrace:\n";
context->OutputTraceToStream(trace_, count_, os, prefix_string);
}
}
} // namespace debug
} // namespace base

View file

@ -0,0 +1,111 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/debug/task_trace.h"
#include "build/build_config.h"
#if defined(OS_ANDROID)
#include <android/log.h>
#endif // OS_ANDROID
#include <algorithm>
#include <iostream>
#include <sstream>
#if defined(OS_ANDROID)
#include "base/no_destructor.h"
#endif
#include "base/pending_task.h"
#include "base/task/common/task_annotator.h"
namespace base {
namespace debug {
namespace {
#if defined(OS_ANDROID)
// Android sends stdout and stderr to /dev/null; logging should be done through
// the __android_log_write() function. Here we create an override of
// std::stringbuf that writes to the Android log.
class AndroidErrBuffer : public std::stringbuf {
protected:
int sync() override {
__android_log_write(ANDROID_LOG_ERROR, "chromium", str().c_str());
return 0;
}
};
std::ostream& DefaultOutputStream() {
static NoDestructor<AndroidErrBuffer> buf;
static NoDestructor<std::ostream> out(buf.get());
return *out;
}
#else
// Use stderr by default.
std::ostream& DefaultOutputStream() {
return std::cerr;
}
#endif // OS_ANDROID
} // namespace
TaskTrace::TaskTrace() {
const PendingTask* current_task = TaskAnnotator::CurrentTaskForThread();
if (!current_task)
return;
std::array<const void*, PendingTask::kTaskBacktraceLength + 1> task_trace;
task_trace[0] = current_task->posted_from.program_counter();
std::copy(current_task->task_backtrace.begin(),
current_task->task_backtrace.end(), task_trace.begin() + 1);
size_t length = 0;
while (length < task_trace.size() && task_trace[length])
++length;
if (length == 0)
return;
stack_trace_.emplace(task_trace.data(), length);
trace_overflow_ = current_task->task_backtrace_overflow;
}
bool TaskTrace::empty() const {
return !stack_trace_.has_value();
}
void TaskTrace::Print() const {
OutputToStream(&DefaultOutputStream());
}
void TaskTrace::OutputToStream(std::ostream* os) const {
*os << "Task trace:" << std::endl;
if (!stack_trace_) {
*os << "No active task.";
return;
}
*os << *stack_trace_;
if (trace_overflow_) {
*os << "Task trace buffer limit hit, update "
"PendingTask::kTaskBacktraceLength to increase."
<< std::endl;
}
}
std::string TaskTrace::ToString() const {
std::stringstream stream;
OutputToStream(&stream);
return stream.str();
}
base::span<const void* const> TaskTrace::AddressesForTesting() const {
if (empty())
return {};
size_t count = 0;
const void* const* addresses = stack_trace_->Addresses(&count);
return {addresses, count};
}
std::ostream& operator<<(std::ostream& os, const TaskTrace& task_trace) {
task_trace.OutputToStream(&os);
return os;
}
} // namespace debug
} // namespace base

View file

@ -0,0 +1,67 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_DEBUG_TASK_TRACE_H_
#define BASE_DEBUG_TASK_TRACE_H_
#include <iosfwd>
#include <string>
#include "base/base_export.h"
#include "base/containers/span.h"
#include "base/debug/stack_trace.h"
#include "base/optional.h"
namespace base {
namespace debug {
// Provides a snapshot of which places in the code called
// base::TaskRunner::PostTask() that led to the TaskTrace() constructor call.
// Analogous to base::StackTrace, but for posted tasks rather than function
// calls.
//
// Example usage:
// TaskTrace().Print();
//
// Example output:
// Task trace:
// #0 content::ServiceWorkerContextWrapper::DidCheckHasServiceWorker()
// #1 content::ServiceWorkerStorage::FindForDocumentInDB()
// #2 content::ServiceWorkerStorage::FindRegistrationForDocument()
// #3 content::ServiceWorkerContextWrapper::CheckHasServiceWorker()
// #4 content::ManifestIconDownloader::ScaleIcon()
// Task trace buffer limit hit, update PendingTask::kTaskBacktraceLength to
// increase.
class BASE_EXPORT TaskTrace {
public:
TaskTrace();
// Whether there is any trace data.
bool empty() const;
// Outputs to stderr via OutputToStream.
void Print() const;
// Outputs trace to |os|, may be called when empty() is true.
void OutputToStream(std::ostream* os) const;
// Resolves trace to symbols and returns as string.
std::string ToString() const;
// Returns the list of addresses in the task trace for testing.
base::span<const void* const> AddressesForTesting() const;
private:
base::Optional<StackTrace> stack_trace_;
bool trace_overflow_ = false;
};
// Forwards to TaskTrace::OutputToStream.
BASE_EXPORT std::ostream& operator<<(std::ostream& os,
const TaskTrace& task_trace);
} // namespace debug
} // namespace base
#endif // BASE_DEBUG_TASK_TRACE_H_