chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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.
|
||||
|
||||
#ifndef BUTIL_DEBUG_ADDRESS_ANNOTATIONS_H_
|
||||
#define BUTIL_DEBUG_ADDRESS_ANNOTATIONS_H_
|
||||
|
||||
#include "butil/macros.h"
|
||||
|
||||
// It provides AddressSanitizer annotations for bthread and memory management.
|
||||
// See <sanitizer/asan_interface.h> for detail of these annotations.
|
||||
|
||||
#ifdef BUTIL_USE_ASAN
|
||||
|
||||
#include <sanitizer/asan_interface.h>
|
||||
|
||||
#define BUTIL_ASAN_POISON_MEMORY_REGION(addr, size) \
|
||||
__asan_poison_memory_region(addr, size)
|
||||
|
||||
#define BUTIL_ASAN_UNPOISON_MEMORY_REGION(addr, size) \
|
||||
__asan_unpoison_memory_region(addr, size)
|
||||
|
||||
#define BUTIL_ASAN_ADDRESS_IS_POISONED(addr) \
|
||||
__asan_address_is_poisoned(addr)
|
||||
|
||||
#define BUTIL_ASAN_START_SWITCH_FIBER(fake_stack_save, bottom, size) \
|
||||
__sanitizer_start_switch_fiber(fake_stack_save, bottom, size)
|
||||
|
||||
#define BUTIL_ASAN_FINISH_SWITCH_FIBER(fake_stack_save, bottom_old, size_old) \
|
||||
__sanitizer_finish_switch_fiber(fake_stack_save, bottom_old, size_old)
|
||||
|
||||
#else
|
||||
// If ASan is not used, these annotations are no-ops.
|
||||
#define BUTIL_ASAN_POISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
|
||||
#define BUTIL_ASAN_UNPOISON_MEMORY_REGION(addr, size) ((void)(addr), (void)(size))
|
||||
#define BUTIL_ASAN_START_SWITCH_FIBER(fake_stack_save, bottom, size) \
|
||||
((void)(fake_stack_save), (void)(bottom), (void)(size))
|
||||
#define BUTIL_ASAN_FINISH_SWITCH_FIBER(fake_stack_save, bottom_old, size_old) \
|
||||
((void)(fake_stack_save), (void)(bottom_old), (void)(size_old))
|
||||
#endif // BUTIL_USE_ASAN
|
||||
|
||||
#endif // BUTIL_DEBUG_ADDRESS_ANNOTATIONS_H_
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "butil/debug/alias.h"
|
||||
#include "butil/build_config.h"
|
||||
|
||||
namespace butil {
|
||||
namespace debug {
|
||||
|
||||
#if defined(COMPILER_MSVC)
|
||||
#pragma optimize("", off)
|
||||
#endif
|
||||
|
||||
void Alias(const void* var) {
|
||||
}
|
||||
|
||||
#if defined(COMPILER_MSVC)
|
||||
#pragma optimize("", on)
|
||||
#endif
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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 BUTIL_DEBUG_ALIAS_H_
|
||||
#define BUTIL_DEBUG_ALIAS_H_
|
||||
|
||||
#include "butil/base_export.h"
|
||||
|
||||
namespace butil {
|
||||
namespace debug {
|
||||
|
||||
// Make the optimizer think that var is aliased. This is to prevent it from
|
||||
// optimizing out variables that that would not otherwise be live at the point
|
||||
// of a potential crash.
|
||||
void BUTIL_EXPORT Alias(const void* var);
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
|
||||
#endif // BUTIL_DEBUG_ALIAS_H_
|
||||
@@ -0,0 +1,94 @@
|
||||
// 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.
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include "butil/debug/alias.h"
|
||||
#include "butil/debug/asan_invalid_access.h"
|
||||
#include "butil/logging.h"
|
||||
#include "butil/memory/scoped_ptr.h"
|
||||
|
||||
namespace butil {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(SYZYASAN)
|
||||
// Corrupt a memory block and make sure that the corruption gets detected either
|
||||
// when we free it or when another crash happens (if |induce_crash| is set to
|
||||
// true).
|
||||
NOINLINE void CorruptMemoryBlock(bool induce_crash) {
|
||||
// NOTE(sebmarchand): We intentionally corrupt a memory block here in order to
|
||||
// trigger an Address Sanitizer (ASAN) error report.
|
||||
static const int kArraySize = 5;
|
||||
int* array = new int[kArraySize];
|
||||
// Encapsulate the invalid memory access into a try-catch statement to prevent
|
||||
// this function from being instrumented. This way the underflow won't be
|
||||
// detected but the corruption will (as the allocator will still be hooked).
|
||||
try {
|
||||
// Declares the dummy value as volatile to make sure it doesn't get
|
||||
// optimized away.
|
||||
int volatile dummy = array[-1]--;
|
||||
butil::debug::Alias(const_cast<int*>(&dummy));
|
||||
} catch (...) {
|
||||
}
|
||||
if (induce_crash)
|
||||
CHECK(false);
|
||||
delete[] array;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined(ADDRESS_SANITIZER) || defined(SYZYASAN)
|
||||
// NOTE(sebmarchand): We intentionally perform some invalid heap access here in
|
||||
// order to trigger an AddressSanitizer (ASan) error report.
|
||||
|
||||
static const int kArraySize = 5;
|
||||
|
||||
void AsanHeapOverflow() {
|
||||
scoped_ptr<int[]> array(new int[kArraySize]);
|
||||
// Declares the dummy value as volatile to make sure it doesn't get optimized
|
||||
// away.
|
||||
int volatile dummy = 0;
|
||||
dummy = array[kArraySize];
|
||||
butil::debug::Alias(const_cast<int*>(&dummy));
|
||||
}
|
||||
|
||||
void AsanHeapUnderflow() {
|
||||
scoped_ptr<int[]> array(new int[kArraySize]);
|
||||
// Declares the dummy value as volatile to make sure it doesn't get optimized
|
||||
// away.
|
||||
int volatile dummy = 0;
|
||||
dummy = array[-1];
|
||||
butil::debug::Alias(const_cast<int*>(&dummy));
|
||||
}
|
||||
|
||||
void AsanHeapUseAfterFree() {
|
||||
scoped_ptr<int[]> array(new int[kArraySize]);
|
||||
// Declares the dummy value as volatile to make sure it doesn't get optimized
|
||||
// away.
|
||||
int volatile dummy = 0;
|
||||
int* dangling = array.get();
|
||||
array.reset();
|
||||
dummy = dangling[kArraySize / 2];
|
||||
butil::debug::Alias(const_cast<int*>(&dummy));
|
||||
}
|
||||
|
||||
#endif // ADDRESS_SANITIZER || SYZYASAN
|
||||
|
||||
#if defined(SYZYASAN)
|
||||
void AsanCorruptHeapBlock() {
|
||||
CorruptMemoryBlock(false);
|
||||
}
|
||||
|
||||
void AsanCorruptHeap() {
|
||||
CorruptMemoryBlock(true);
|
||||
}
|
||||
#endif // SYZYASAN
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
@@ -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 BUTIL_DEBUG_ASAN_INVALID_ACCESS_H_
|
||||
#define BUTIL_DEBUG_ASAN_INVALID_ACCESS_H_
|
||||
|
||||
#include "butil/base_export.h"
|
||||
#include "butil/compiler_specific.h"
|
||||
|
||||
namespace butil {
|
||||
namespace debug {
|
||||
|
||||
#if defined(ADDRESS_SANITIZER) || defined(SYZYASAN)
|
||||
|
||||
// Generates an heap buffer overflow.
|
||||
BUTIL_EXPORT NOINLINE void AsanHeapOverflow();
|
||||
|
||||
// Generates an heap buffer underflow.
|
||||
BUTIL_EXPORT NOINLINE void AsanHeapUnderflow();
|
||||
|
||||
// Generates an use after free.
|
||||
BUTIL_EXPORT NOINLINE void AsanHeapUseAfterFree();
|
||||
|
||||
#endif // ADDRESS_SANITIZER || SYZYASAN
|
||||
|
||||
// The "corrupt-block" and "corrupt-heap" classes of bugs is specific to
|
||||
// SyzyASan.
|
||||
#if defined(SYZYASAN)
|
||||
|
||||
// Corrupts a memory block and makes sure that the corruption gets detected when
|
||||
// we try to free this block.
|
||||
BUTIL_EXPORT NOINLINE void AsanCorruptHeapBlock();
|
||||
|
||||
// Corrupts the heap and makes sure that the corruption gets detected when a
|
||||
// crash occur.
|
||||
BUTIL_EXPORT NOINLINE void AsanCorruptHeap();
|
||||
|
||||
#endif // SYZYASAN
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
|
||||
#endif // BUTIL_DEBUG_ASAN_INVALID_ACCESS_H_
|
||||
@@ -0,0 +1,202 @@
|
||||
// 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 "butil/debug/crash_logging.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
|
||||
#include "butil/debug/stack_trace.h"
|
||||
#include "butil/format_macros.h"
|
||||
#include "butil/logging.h"
|
||||
#include "butil/strings/string_util.h"
|
||||
#include "butil/strings/stringprintf.h"
|
||||
|
||||
namespace butil {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
// Global map of crash key names to registration entries.
|
||||
typedef std::map<butil::StringPiece, CrashKey> CrashKeyMap;
|
||||
CrashKeyMap* g_crash_keys_ = NULL;
|
||||
|
||||
// The maximum length of a single chunk.
|
||||
size_t g_chunk_max_length_ = 0;
|
||||
|
||||
// String used to format chunked key names.
|
||||
const char kChunkFormatString[] = "%s-%" PRIuS;
|
||||
|
||||
// The functions that are called to actually set the key-value pairs in the
|
||||
// crash reportng system.
|
||||
SetCrashKeyValueFuncT g_set_key_func_ = NULL;
|
||||
ClearCrashKeyValueFuncT g_clear_key_func_ = NULL;
|
||||
|
||||
// For a given |length|, computes the number of chunks a value of that size
|
||||
// will occupy.
|
||||
size_t NumChunksForLength(size_t length) {
|
||||
return (size_t)std::ceil(length / static_cast<double>(g_chunk_max_length_));
|
||||
}
|
||||
|
||||
// The longest max_length allowed by the system.
|
||||
const size_t kLargestValueAllowed = 1024;
|
||||
|
||||
} // namespace
|
||||
|
||||
void SetCrashKeyValue(const butil::StringPiece& key,
|
||||
const butil::StringPiece& value) {
|
||||
if (!g_set_key_func_ || !g_crash_keys_)
|
||||
return;
|
||||
|
||||
const CrashKey* crash_key = LookupCrashKey(key);
|
||||
|
||||
DCHECK(crash_key) << "All crash keys must be registered before use "
|
||||
<< "(key = " << key << ")";
|
||||
|
||||
// Handle the un-chunked case.
|
||||
if (!crash_key || crash_key->max_length <= g_chunk_max_length_) {
|
||||
g_set_key_func_(key, value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unset the unused chunks.
|
||||
std::vector<std::string> chunks =
|
||||
ChunkCrashKeyValue(*crash_key, value, g_chunk_max_length_);
|
||||
for (size_t i = chunks.size();
|
||||
i < NumChunksForLength(crash_key->max_length);
|
||||
++i) {
|
||||
g_clear_key_func_(butil::StringPrintf(kChunkFormatString, key.data(), i+1));
|
||||
}
|
||||
|
||||
// Set the chunked keys.
|
||||
for (size_t i = 0; i < chunks.size(); ++i) {
|
||||
g_set_key_func_(butil::StringPrintf(kChunkFormatString, key.data(), i+1),
|
||||
chunks[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void ClearCrashKey(const butil::StringPiece& key) {
|
||||
if (!g_clear_key_func_ || !g_crash_keys_)
|
||||
return;
|
||||
|
||||
const CrashKey* crash_key = LookupCrashKey(key);
|
||||
|
||||
// Handle the un-chunked case.
|
||||
if (!crash_key || crash_key->max_length <= g_chunk_max_length_) {
|
||||
g_clear_key_func_(key);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < NumChunksForLength(crash_key->max_length); ++i) {
|
||||
g_clear_key_func_(butil::StringPrintf(kChunkFormatString, key.data(), i+1));
|
||||
}
|
||||
}
|
||||
|
||||
void SetCrashKeyToStackTrace(const butil::StringPiece& key,
|
||||
const StackTrace& trace) {
|
||||
size_t count = 0;
|
||||
const void* const* addresses = trace.Addresses(&count);
|
||||
SetCrashKeyFromAddresses(key, addresses, count);
|
||||
}
|
||||
|
||||
void SetCrashKeyFromAddresses(const butil::StringPiece& key,
|
||||
const void* const* addresses,
|
||||
size_t count) {
|
||||
std::string value = "<null>";
|
||||
if (addresses && count) {
|
||||
const size_t kBreakpadValueMax = 255;
|
||||
|
||||
std::vector<std::string> hex_backtrace;
|
||||
size_t length = 0;
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
std::string s = butil::StringPrintf("%p", addresses[i]);
|
||||
length += s.length() + 1;
|
||||
if (length > kBreakpadValueMax)
|
||||
break;
|
||||
hex_backtrace.push_back(s);
|
||||
}
|
||||
|
||||
value = JoinString(hex_backtrace, ' ');
|
||||
|
||||
// Warn if this exceeds the breakpad limits.
|
||||
DCHECK_LE(value.length(), kBreakpadValueMax);
|
||||
}
|
||||
|
||||
SetCrashKeyValue(key, value);
|
||||
}
|
||||
|
||||
ScopedCrashKey::ScopedCrashKey(const butil::StringPiece& key,
|
||||
const butil::StringPiece& value)
|
||||
: key_(key.as_string()) {
|
||||
SetCrashKeyValue(key, value);
|
||||
}
|
||||
|
||||
ScopedCrashKey::~ScopedCrashKey() {
|
||||
ClearCrashKey(key_);
|
||||
}
|
||||
|
||||
size_t InitCrashKeys(const CrashKey* const keys, size_t count,
|
||||
size_t chunk_max_length) {
|
||||
DCHECK(!g_crash_keys_) << "Crash logging may only be initialized once";
|
||||
if (!keys) {
|
||||
delete g_crash_keys_;
|
||||
g_crash_keys_ = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_crash_keys_ = new CrashKeyMap;
|
||||
g_chunk_max_length_ = chunk_max_length;
|
||||
|
||||
size_t total_keys = 0;
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
g_crash_keys_->emplace(keys[i].key_name, keys[i]);
|
||||
total_keys += NumChunksForLength(keys[i].max_length);
|
||||
DCHECK_LT(keys[i].max_length, kLargestValueAllowed);
|
||||
}
|
||||
DCHECK_EQ(count, g_crash_keys_->size())
|
||||
<< "Duplicate crash keys were registered";
|
||||
|
||||
return total_keys;
|
||||
}
|
||||
|
||||
const CrashKey* LookupCrashKey(const butil::StringPiece& key) {
|
||||
if (!g_crash_keys_)
|
||||
return NULL;
|
||||
CrashKeyMap::const_iterator it = g_crash_keys_->find(key.as_string());
|
||||
if (it == g_crash_keys_->end())
|
||||
return NULL;
|
||||
return &(it->second);
|
||||
}
|
||||
|
||||
void SetCrashKeyReportingFunctions(
|
||||
SetCrashKeyValueFuncT set_key_func,
|
||||
ClearCrashKeyValueFuncT clear_key_func) {
|
||||
g_set_key_func_ = set_key_func;
|
||||
g_clear_key_func_ = clear_key_func;
|
||||
}
|
||||
|
||||
std::vector<std::string> ChunkCrashKeyValue(const CrashKey& crash_key,
|
||||
const butil::StringPiece& value,
|
||||
size_t chunk_max_length) {
|
||||
std::string value_string = value.substr(0, crash_key.max_length).as_string();
|
||||
std::vector<std::string> chunks;
|
||||
for (size_t offset = 0; offset < value_string.length(); ) {
|
||||
std::string chunk = value_string.substr(offset, chunk_max_length);
|
||||
chunks.push_back(chunk);
|
||||
offset += chunk.length();
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
void ResetCrashLoggingForTesting() {
|
||||
delete g_crash_keys_;
|
||||
g_crash_keys_ = NULL;
|
||||
g_chunk_max_length_ = 0;
|
||||
g_set_key_func_ = NULL;
|
||||
g_clear_key_func_ = NULL;
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
@@ -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 BUTIL_DEBUG_CRASH_LOGGING_H_
|
||||
#define BUTIL_DEBUG_CRASH_LOGGING_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "butil/base_export.h"
|
||||
#include "butil/basictypes.h"
|
||||
#include "butil/strings/string_piece.h"
|
||||
|
||||
// These functions add metadata to the upload payload when sending crash reports
|
||||
// to the crash server.
|
||||
//
|
||||
// IMPORTANT: On OS X and Linux, the key/value pairs are only sent as part of
|
||||
// the upload and are not included in the minidump!
|
||||
|
||||
namespace butil {
|
||||
namespace debug {
|
||||
|
||||
class StackTrace;
|
||||
|
||||
// Set or clear a specific key-value pair from the crash metadata. Keys and
|
||||
// values are terminated at the null byte.
|
||||
BUTIL_EXPORT void SetCrashKeyValue(const butil::StringPiece& key,
|
||||
const butil::StringPiece& value);
|
||||
BUTIL_EXPORT void ClearCrashKey(const butil::StringPiece& key);
|
||||
|
||||
// Records the given StackTrace into a crash key.
|
||||
BUTIL_EXPORT void SetCrashKeyToStackTrace(const butil::StringPiece& key,
|
||||
const StackTrace& trace);
|
||||
|
||||
// Formats |count| instruction pointers from |addresses| using %p and
|
||||
// sets the resulting string as a value for crash key |key|. A maximum of 23
|
||||
// items will be encoded, since breakpad limits values to 255 bytes.
|
||||
BUTIL_EXPORT void SetCrashKeyFromAddresses(const butil::StringPiece& key,
|
||||
const void* const* addresses,
|
||||
size_t count);
|
||||
|
||||
// A scoper that sets the specified key to value for the lifetime of the
|
||||
// object, and clears it on destruction.
|
||||
class BUTIL_EXPORT ScopedCrashKey {
|
||||
public:
|
||||
ScopedCrashKey(const butil::StringPiece& key, const butil::StringPiece& value);
|
||||
~ScopedCrashKey();
|
||||
|
||||
private:
|
||||
std::string key_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ScopedCrashKey);
|
||||
};
|
||||
|
||||
// Before setting values for a key, all the keys must be registered.
|
||||
struct BUTIL_EXPORT CrashKey {
|
||||
// The name of the crash key, used in the above functions.
|
||||
const char* key_name;
|
||||
|
||||
// The maximum length for a value. If the value is longer than this, it will
|
||||
// be truncated. If the value is larger than the |chunk_max_length| passed to
|
||||
// InitCrashKeys() but less than this value, it will be split into multiple
|
||||
// numbered chunks.
|
||||
size_t max_length;
|
||||
};
|
||||
|
||||
// Before the crash key logging mechanism can be used, all crash keys must be
|
||||
// registered with this function. The function returns the amount of space
|
||||
// the crash reporting implementation should allocate space for the registered
|
||||
// crash keys. |chunk_max_length| is the maximum size that a value in a single
|
||||
// chunk can be.
|
||||
BUTIL_EXPORT size_t InitCrashKeys(const CrashKey* const keys, size_t count,
|
||||
size_t chunk_max_length);
|
||||
|
||||
// Returns the correspnding crash key object or NULL for a given key.
|
||||
BUTIL_EXPORT const CrashKey* LookupCrashKey(const butil::StringPiece& key);
|
||||
|
||||
// In the platform crash reporting implementation, these functions set and
|
||||
// clear the NUL-termianted key-value pairs.
|
||||
typedef void (*SetCrashKeyValueFuncT)(const butil::StringPiece&,
|
||||
const butil::StringPiece&);
|
||||
typedef void (*ClearCrashKeyValueFuncT)(const butil::StringPiece&);
|
||||
|
||||
// Sets the function pointers that are used to integrate with the platform-
|
||||
// specific crash reporting libraries.
|
||||
BUTIL_EXPORT void SetCrashKeyReportingFunctions(
|
||||
SetCrashKeyValueFuncT set_key_func,
|
||||
ClearCrashKeyValueFuncT clear_key_func);
|
||||
|
||||
// Helper function that breaks up a value according to the parameters
|
||||
// specified by the crash key object.
|
||||
BUTIL_EXPORT std::vector<std::string> ChunkCrashKeyValue(
|
||||
const CrashKey& crash_key,
|
||||
const butil::StringPiece& value,
|
||||
size_t chunk_max_length);
|
||||
|
||||
// Resets the crash key system so it can be reinitialized. For testing only.
|
||||
BUTIL_EXPORT void ResetCrashLoggingForTesting();
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
|
||||
#endif // BUTIL_DEBUG_CRASH_LOGGING_H_
|
||||
@@ -0,0 +1,41 @@
|
||||
// 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 "butil/debug/debugger.h"
|
||||
#include "butil/logging.h"
|
||||
#include "butil/threading/platform_thread.h"
|
||||
|
||||
namespace butil {
|
||||
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 butil
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This is a cross platform interface for helper functions related to
|
||||
// debuggers. You should use this to test if you're running under a debugger,
|
||||
// and if you would like to yield (breakpoint) into the debugger.
|
||||
|
||||
#ifndef BUTIL_DEBUG_DEBUGGER_H
|
||||
#define BUTIL_DEBUG_DEBUGGER_H
|
||||
|
||||
#include "butil/base_export.h"
|
||||
|
||||
namespace butil {
|
||||
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.
|
||||
BUTIL_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.
|
||||
BUTIL_EXPORT bool BeingDebugged();
|
||||
|
||||
// Break into the debugger, assumes a debugger is present.
|
||||
BUTIL_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.
|
||||
BUTIL_EXPORT void SetSuppressDebugUI(bool suppress);
|
||||
BUTIL_EXPORT bool IsDebugUISuppressed();
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
|
||||
#endif // BUTIL_DEBUG_DEBUGGER_H
|
||||
@@ -0,0 +1,257 @@
|
||||
// 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 "butil/debug/debugger.h"
|
||||
#include "butil/build_config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#if defined(__GLIBCXX__)
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_MACOSX) || defined(OS_BSD)
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_FREEBSD)
|
||||
#include <sys/user.h>
|
||||
#endif
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "butil/basictypes.h"
|
||||
#include "butil/logging.h"
|
||||
#include "butil/memory/scoped_ptr.h"
|
||||
#include "butil/posix/eintr_wrapper.h"
|
||||
#include "butil/safe_strerror_posix.h"
|
||||
#include "butil/strings/string_piece.h"
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
#include "butil/third_party/symbolize/symbolize.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
#include "butil/threading/platform_thread.h"
|
||||
#endif
|
||||
|
||||
namespace butil {
|
||||
namespace debug {
|
||||
|
||||
#if defined(OS_MACOSX) || defined(OS_BSD)
|
||||
|
||||
// Based on Apple's recommended method as described in
|
||||
// http://developer.apple.com/qa/qa2004/qa1361.html
|
||||
bool BeingDebugged() {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
//
|
||||
// While some code used below may be async-signal unsafe, note how
|
||||
// the result is cached (see |is_set| and |being_debugged| static variables
|
||||
// right below). If this code is properly warmed-up early
|
||||
// in the start-up process, it should be safe to use later.
|
||||
|
||||
// If the process is sandboxed then we can't use the sysctl, so cache the
|
||||
// value.
|
||||
static bool is_set = false;
|
||||
static bool being_debugged = false;
|
||||
|
||||
if (is_set)
|
||||
return being_debugged;
|
||||
|
||||
// Initialize mib, which tells sysctl what info we want. In this case,
|
||||
// we're looking for information about a specific process ID.
|
||||
int mib[] = {
|
||||
CTL_KERN,
|
||||
KERN_PROC,
|
||||
KERN_PROC_PID,
|
||||
getpid()
|
||||
#if defined(OS_OPENBSD)
|
||||
, sizeof(struct kinfo_proc),
|
||||
0
|
||||
#endif
|
||||
};
|
||||
|
||||
// Caution: struct kinfo_proc is marked __APPLE_API_UNSTABLE. The source and
|
||||
// binary interfaces may change.
|
||||
struct kinfo_proc info;
|
||||
size_t info_size = sizeof(info);
|
||||
|
||||
#if defined(OS_OPENBSD)
|
||||
if (sysctl(mib, arraysize(mib), NULL, &info_size, NULL, 0) < 0)
|
||||
return -1;
|
||||
|
||||
mib[5] = (info_size / sizeof(struct kinfo_proc));
|
||||
#endif
|
||||
|
||||
int sysctl_result = sysctl(mib, arraysize(mib), &info, &info_size, NULL, 0);
|
||||
DCHECK_EQ(sysctl_result, 0);
|
||||
if (sysctl_result != 0) {
|
||||
is_set = true;
|
||||
being_debugged = false;
|
||||
return being_debugged;
|
||||
}
|
||||
|
||||
// This process is being debugged if the P_TRACED flag is set.
|
||||
is_set = true;
|
||||
#if defined(OS_FREEBSD)
|
||||
being_debugged = (info.ki_flag & P_TRACED) != 0;
|
||||
#elif defined(OS_BSD)
|
||||
being_debugged = (info.p_flag & P_TRACED) != 0;
|
||||
#else
|
||||
being_debugged = (info.kp_proc.p_flag & P_TRACED) != 0;
|
||||
#endif
|
||||
return being_debugged;
|
||||
}
|
||||
|
||||
#elif defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
|
||||
// We can look in /proc/self/status for TracerPid. We are likely used in crash
|
||||
// handling, so we are careful not to use the heap or have side effects.
|
||||
// Another option that is common is to try to ptrace yourself, but then we
|
||||
// can't detach without forking(), and that's not so great.
|
||||
// static
|
||||
bool BeingDebugged() {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
int status_fd = open("/proc/self/status", O_RDONLY);
|
||||
if (status_fd == -1)
|
||||
return false;
|
||||
|
||||
// We assume our line will be in the first 1024 characters and that we can
|
||||
// read this much all at once. In practice this will generally be true.
|
||||
// This simplifies and speeds up things considerably.
|
||||
char buf[1024];
|
||||
|
||||
ssize_t num_read = HANDLE_EINTR(read(status_fd, buf, sizeof(buf)));
|
||||
if (IGNORE_EINTR(close(status_fd)) < 0)
|
||||
return false;
|
||||
|
||||
if (num_read <= 0)
|
||||
return false;
|
||||
|
||||
StringPiece status(buf, num_read);
|
||||
StringPiece tracer("TracerPid:\t");
|
||||
|
||||
StringPiece::size_type pid_index = status.find(tracer);
|
||||
if (pid_index == StringPiece::npos)
|
||||
return false;
|
||||
|
||||
// Our pid is 0 without a debugger, assume this for any pid starting with 0.
|
||||
pid_index += tracer.size();
|
||||
return pid_index < status.size() && status[pid_index] != '0';
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool BeingDebugged() {
|
||||
NOTIMPLEMENTED();
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// We want to break into the debugger in Debug mode, and cause a crash dump in
|
||||
// Release mode. Breakpad behaves as follows:
|
||||
//
|
||||
// +-------+-----------------+-----------------+
|
||||
// | OS | Dump on SIGTRAP | Dump on SIGABRT |
|
||||
// +-------+-----------------+-----------------+
|
||||
// | Linux | N | Y |
|
||||
// | Mac | Y | N |
|
||||
// +-------+-----------------+-----------------+
|
||||
//
|
||||
// Thus we do the following:
|
||||
// Linux: Debug mode if a debugger is attached, send SIGTRAP; otherwise send
|
||||
// SIGABRT
|
||||
// Mac: Always send SIGTRAP.
|
||||
|
||||
#if defined(ARCH_CPU_ARMEL)
|
||||
#define DEBUG_BREAK_ASM() asm("bkpt 0")
|
||||
#elif defined(ARCH_CPU_ARM64)
|
||||
#define DEBUG_BREAK_ASM() asm("brk 0")
|
||||
#elif defined(ARCH_CPU_LOONGARCH64_FAMILY)
|
||||
#define DEBUG_BREAK_ASM() asm("break 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) {
|
||||
butil::PlatformThread::Sleep(butil::TimeDelta::FromMilliseconds(100));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
#define DEBUG_BREAK() DebugBreak()
|
||||
#elif defined(DEBUG_BREAK_ASM)
|
||||
#define DEBUG_BREAK() DEBUG_BREAK_ASM()
|
||||
#else
|
||||
#error "Don't know how to debug break on this architecture/OS"
|
||||
#endif
|
||||
|
||||
void BreakDebugger() {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
DEBUG_BREAK();
|
||||
#if defined(OS_ANDROID) && !defined(OFFICIAL_BUILD)
|
||||
// For Android development we always build release (debug builds are
|
||||
// unmanageably large), so the unofficial build is used for debugging. It is
|
||||
// helpful to be able to insert BreakDebugger() statements in the source,
|
||||
// attach the debugger, inspect the state of the program and then resume it by
|
||||
// setting the 'go' variable above.
|
||||
#elif defined(NDEBUG)
|
||||
// Terminate the program after signaling the debug break.
|
||||
_exit(1);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
@@ -0,0 +1,32 @@
|
||||
// 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 "butil/debug/dump_without_crashing.h"
|
||||
|
||||
#include "butil/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Pointer to the function that's called by DumpWithoutCrashing() to dump the
|
||||
// process's memory.
|
||||
void (CDECL *dump_without_crashing_function_)() = NULL;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace butil {
|
||||
|
||||
namespace debug {
|
||||
|
||||
void DumpWithoutCrashing() {
|
||||
if (dump_without_crashing_function_)
|
||||
(*dump_without_crashing_function_)();
|
||||
}
|
||||
|
||||
void SetDumpWithoutCrashingFunction(void (CDECL *function)()) {
|
||||
dump_without_crashing_function_ = function;
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
|
||||
} // namespace butil
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 BUTIL_DEBUG_DUMP_WITHOUT_CRASHING_H_
|
||||
#define BUTIL_DEBUG_DUMP_WITHOUT_CRASHING_H_
|
||||
|
||||
#include "butil/base_export.h"
|
||||
#include "butil/compiler_specific.h"
|
||||
#include "butil/build_config.h"
|
||||
|
||||
namespace butil {
|
||||
|
||||
namespace debug {
|
||||
|
||||
// Handler to silently dump the current process without crashing.
|
||||
BUTIL_EXPORT void DumpWithoutCrashing();
|
||||
|
||||
// Sets a function that'll be invoked to dump the current process when
|
||||
// DumpWithoutCrashing() is called.
|
||||
BUTIL_EXPORT void SetDumpWithoutCrashingFunction(void (CDECL *function)());
|
||||
|
||||
} // namespace debug
|
||||
|
||||
} // namespace butil
|
||||
|
||||
#endif // BUTIL_DEBUG_DUMP_WITHOUT_CRASHING_H_
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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 BUTIL_DEBUG_LEAK_ANNOTATIONS_H_
|
||||
#define BUTIL_DEBUG_LEAK_ANNOTATIONS_H_
|
||||
|
||||
#include "butil/basictypes.h"
|
||||
#include "butil/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)) || defined(BUTIL_USE_ASAN)
|
||||
|
||||
// Public LSan API from <sanitizer/lsan_interface.h>.
|
||||
extern "C" {
|
||||
void __lsan_disable();
|
||||
void __lsan_enable();
|
||||
void __lsan_ignore_object(const void *p);
|
||||
} // extern "C"
|
||||
|
||||
class ScopedLeakSanitizerDisabler {
|
||||
public:
|
||||
ScopedLeakSanitizerDisabler() { __lsan_disable(); }
|
||||
~ScopedLeakSanitizerDisabler() { __lsan_enable(); }
|
||||
|
||||
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)
|
||||
|
||||
// Manually pair these to mark allocations made in between as intentional non-leaks.
|
||||
#define ANNOTATE_MEMORY_LEAK_DISABLE() __lsan_disable()
|
||||
#define ANNOTATE_MEMORY_LEAK_ENABLE() __lsan_enable()
|
||||
|
||||
#else
|
||||
|
||||
// If neither HeapChecker nor LSan are used, the annotations should be no-ops.
|
||||
#define ANNOTATE_SCOPED_MEMORY_LEAK ((void)0)
|
||||
#define ANNOTATE_LEAKING_OBJECT_PTR(X) ((void)(X))
|
||||
#define ANNOTATE_MEMORY_LEAK_DISABLE() ((void)0)
|
||||
#define ANNOTATE_MEMORY_LEAK_ENABLE() ((void)0)
|
||||
|
||||
#endif
|
||||
|
||||
#endif // BUTIL_DEBUG_LEAK_ANNOTATIONS_H_
|
||||
@@ -0,0 +1,138 @@
|
||||
// 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 BUTIL_DEBUG_LEAK_TRACKER_H_
|
||||
#define BUTIL_DEBUG_LEAK_TRACKER_H_
|
||||
|
||||
#include "butil/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 "butil/containers/linked_list.h"
|
||||
#include "butil/debug/stack_trace.h"
|
||||
#include "butil/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:
|
||||
// butil::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 butil {
|
||||
namespace debug {
|
||||
|
||||
#ifndef ENABLE_LEAK_TRACKER
|
||||
|
||||
// If leak tracking is disabled, do nothing.
|
||||
template<typename T>
|
||||
class LeakTracker {
|
||||
public:
|
||||
~LeakTracker() {}
|
||||
static void CheckForLeaks() {}
|
||||
static int NumLiveInstances() { return -1; }
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
// If leak tracking is enabled we track where the object was allocated from.
|
||||
|
||||
template<typename T>
|
||||
class LeakTracker : public LinkNode<LeakTracker<T> > {
|
||||
public:
|
||||
LeakTracker() {
|
||||
instances()->Append(this);
|
||||
}
|
||||
|
||||
~LeakTracker() {
|
||||
this->RemoveFromList();
|
||||
}
|
||||
|
||||
static void CheckForLeaks() {
|
||||
// Walk the allocation list and print each entry it contains.
|
||||
size_t count = 0;
|
||||
|
||||
// Copy the first 3 leak allocation callstacks onto the stack.
|
||||
// This way if we hit the CHECK() in a release build, the leak
|
||||
// information will be available in mini-dump.
|
||||
const size_t kMaxStackTracesToCopyOntoStack = 3;
|
||||
StackTrace stacktraces[kMaxStackTracesToCopyOntoStack];
|
||||
|
||||
for (LinkNode<LeakTracker<T> >* node = instances()->head();
|
||||
node != instances()->end();
|
||||
node = node->next()) {
|
||||
StackTrace& allocation_stack = node->value()->allocation_stack_;
|
||||
|
||||
if (count < kMaxStackTracesToCopyOntoStack)
|
||||
stacktraces[count] = allocation_stack;
|
||||
|
||||
++count;
|
||||
std::ostringstream err;
|
||||
err << "Leaked " << node << " which was allocated by:";
|
||||
allocation_stack.OutputToStream(&err);
|
||||
LOG(ERROR) << err.str();
|
||||
}
|
||||
|
||||
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 butil
|
||||
|
||||
#endif // BUTIL_DEBUG_LEAK_TRACKER_H_
|
||||
@@ -0,0 +1,167 @@
|
||||
// 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 "butil/debug/proc_maps_linux.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
|
||||
#if defined(OS_LINUX) || defined(OS_ANDROID)
|
||||
#include <inttypes.h>
|
||||
#endif
|
||||
|
||||
#include "butil/file_util.h"
|
||||
#include "butil/files/scoped_file.h"
|
||||
#include "butil/strings/string_split.h"
|
||||
|
||||
#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 butil {
|
||||
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);
|
||||
|
||||
butil::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', &lines);
|
||||
|
||||
for (size_t i = 0; i < lines.size(); ++i) {
|
||||
// Due to splitting on '\n' the last line should be empty.
|
||||
if (i == lines.size() - 1) {
|
||||
if (!lines[i].empty()) {
|
||||
DLOG(WARNING) << "Last line not empty";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
MappedMemoryRegion region;
|
||||
const char* line = lines[i].c_str();
|
||||
char permissions[5] = {'\0'}; // Ensure NUL-terminated string.
|
||||
uint8_t dev_major = 0;
|
||||
uint8_t dev_minor = 0;
|
||||
long inode = 0;
|
||||
int path_index = 0;
|
||||
|
||||
// Sample format from man 5 proc:
|
||||
//
|
||||
// address perms offset dev inode pathname
|
||||
// 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm
|
||||
//
|
||||
// The final %n term captures the offset in the input string, which is used
|
||||
// to determine the path name. It *does not* increment the return value.
|
||||
// Refer to man 3 sscanf for details.
|
||||
if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4c %llx %hhx:%hhx %ld %n",
|
||||
®ion.start, ®ion.end, permissions, ®ion.offset,
|
||||
&dev_major, &dev_minor, &inode, &path_index) < 7) {
|
||||
DPLOG(WARNING) << "sscanf failed for line: " << line;
|
||||
return false;
|
||||
}
|
||||
|
||||
region.permissions = 0;
|
||||
|
||||
if (permissions[0] == 'r')
|
||||
region.permissions |= MappedMemoryRegion::READ;
|
||||
else if (permissions[0] != '-')
|
||||
return false;
|
||||
|
||||
if (permissions[1] == 'w')
|
||||
region.permissions |= MappedMemoryRegion::WRITE;
|
||||
else if (permissions[1] != '-')
|
||||
return false;
|
||||
|
||||
if (permissions[2] == 'x')
|
||||
region.permissions |= MappedMemoryRegion::EXECUTE;
|
||||
else if (permissions[2] != '-')
|
||||
return false;
|
||||
|
||||
if (permissions[3] == 'p')
|
||||
region.permissions |= MappedMemoryRegion::PRIVATE;
|
||||
else if (permissions[3] != 's' && permissions[3] != 'S') // Shared memory.
|
||||
return false;
|
||||
|
||||
// Pushing then assigning saves us a string copy.
|
||||
regions.push_back(region);
|
||||
regions.back().path.assign(line + path_index);
|
||||
}
|
||||
|
||||
regions_out->swap(regions);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
@@ -0,0 +1,90 @@
|
||||
// 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 BUTIL_DEBUG_PROC_MAPS_LINUX_H_
|
||||
#define BUTIL_DEBUG_PROC_MAPS_LINUX_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "butil/base_export.h"
|
||||
#include "butil/basictypes.h"
|
||||
|
||||
namespace butil {
|
||||
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;
|
||||
|
||||
// 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
|
||||
BUTIL_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.
|
||||
BUTIL_EXPORT bool ParseProcMaps(const std::string& input,
|
||||
std::vector<MappedMemoryRegion>* regions);
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
|
||||
#endif // BUTIL_DEBUG_PROC_MAPS_LINUX_H_
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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 "butil/debug/stack_trace.h"
|
||||
|
||||
#include "butil/basictypes.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
namespace butil {
|
||||
namespace debug {
|
||||
|
||||
StackTrace::StackTrace(const void* const* trace, size_t count) {
|
||||
count = std::min(count, arraysize(trace_));
|
||||
if (count)
|
||||
memcpy(trace_, trace, count * sizeof(trace_[0]));
|
||||
count_ = count;
|
||||
}
|
||||
|
||||
const void *const *StackTrace::Addresses(size_t* count) const {
|
||||
*count = count_;
|
||||
if (count_)
|
||||
return trace_;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t StackTrace::CopyAddressTo(void** buffer, size_t max_nframes) const {
|
||||
size_t nframes = std::min(count_, max_nframes);
|
||||
memcpy(buffer, trace_, nframes * sizeof(void*));
|
||||
return nframes;
|
||||
}
|
||||
|
||||
std::string StackTrace::ToString() const {
|
||||
std::string str;
|
||||
str.reserve(1024);
|
||||
#if !defined(__UCLIBC__)
|
||||
OutputToString(str);
|
||||
#endif
|
||||
return str;
|
||||
}
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
@@ -0,0 +1,122 @@
|
||||
// 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 BUTIL_DEBUG_STACK_TRACE_H_
|
||||
#define BUTIL_DEBUG_STACK_TRACE_H_
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#include "butil/base_export.h"
|
||||
#include "butil/build_config.h"
|
||||
|
||||
#if defined(OS_POSIX)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
struct _EXCEPTION_POINTERS;
|
||||
#endif
|
||||
|
||||
namespace butil {
|
||||
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.
|
||||
BUTIL_EXPORT bool EnableInProcessStackDumping();
|
||||
|
||||
// A different version of EnableInProcessStackDumping that also works for
|
||||
// sandboxed processes. For more details take a look at the description
|
||||
// of EnableInProcessStackDumping.
|
||||
// Calling this function on Linux opens /proc/self/maps and caches its
|
||||
// contents. In DEBUG 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).
|
||||
BUTIL_EXPORT bool EnableInProcessStackDumpingForSandbox();
|
||||
|
||||
// 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 BUTIL_EXPORT StackTrace {
|
||||
public:
|
||||
// Creates a stacktrace from the current location.
|
||||
// Exclude constructor frame of StackTrace if |exclude_self| is true.
|
||||
explicit StackTrace(bool exclude_self = false);
|
||||
|
||||
// Creates a stacktrace from an existing array of instruction
|
||||
// pointers (such as returned by Addresses()). |count| will be
|
||||
// trimmed to |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(const _EXCEPTION_POINTERS* exception_pointers);
|
||||
#endif
|
||||
|
||||
// Copying and assignment are allowed with the default functions.
|
||||
|
||||
// Gets an array of instruction pointer values. |*count| will be set to the
|
||||
// number of elements in the returned array.
|
||||
const void* const* Addresses(size_t* count) const;
|
||||
|
||||
// Gets the number of frames in the stack trace.
|
||||
size_t FrameCount() const { return count_; }
|
||||
|
||||
// Copies the stack trace to |buffer|,
|
||||
// where the size is min(max_nframes, frame_count()).
|
||||
size_t CopyAddressTo(void** dest, size_t max_nframes) const;
|
||||
|
||||
// Whether if the given symbol is found in the stack trace.
|
||||
bool FindSymbol(void* symbol) const;
|
||||
|
||||
// Prints the stack trace to stderr.
|
||||
void Print() const;
|
||||
|
||||
#if !defined(__UCLIBC__)
|
||||
// Resolves backtrace to symbols and write to stream.
|
||||
void OutputToStream(std::ostream* os) const;
|
||||
void OutputToString(std::string& str) const;
|
||||
#endif
|
||||
|
||||
// Resolves backtrace to symbols and returns as string.
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
// From http://msdn.microsoft.com/en-us/library/bb204633.aspx,
|
||||
// the sum of FramesToSkip and FramesToCapture must be less than 63,
|
||||
// so set it to 62. Even if on POSIX it could be a larger value, it usually
|
||||
// doesn't give much more information.
|
||||
static const int kMaxTraces = 62;
|
||||
|
||||
void* trace_[kMaxTraces]{};
|
||||
|
||||
// The number of valid frames in |trace_|.
|
||||
size_t count_;
|
||||
};
|
||||
|
||||
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.
|
||||
BUTIL_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 butil
|
||||
|
||||
#endif // BUTIL_DEBUG_STACK_TRACE_H_
|
||||
@@ -0,0 +1,894 @@
|
||||
// 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 "butil/debug/stack_trace.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined(__GLIBCXX__)
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
#if !defined(__UCLIBC__)
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
#include <AvailabilityMacros.h>
|
||||
#endif
|
||||
|
||||
#include "butil/basictypes.h"
|
||||
#include "butil/debug/debugger.h"
|
||||
#include "butil/debug/proc_maps_linux.h"
|
||||
#include "butil/logging.h"
|
||||
#include "butil/memory/scoped_ptr.h"
|
||||
#include "butil/memory/singleton.h"
|
||||
#include "butil/numerics/safe_conversions.h"
|
||||
#include "butil/posix/eintr_wrapper.h"
|
||||
#include "butil/strings/string_number_conversions.h"
|
||||
#include "butil/build_config.h"
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
#include "butil/third_party/symbolize/symbolize.h"
|
||||
#endif
|
||||
|
||||
extern int BAIDU_WEAK GetStackTrace(void** result, int max_depth, int skip_count);
|
||||
|
||||
namespace butil {
|
||||
namespace debug {
|
||||
|
||||
namespace {
|
||||
|
||||
volatile sig_atomic_t in_signal_handler = 0;
|
||||
|
||||
#if !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__)
|
||||
// The prefix used for mangled symbols, per the Itanium C++ ABI:
|
||||
// http://www.codesourcery.com/cxx-abi/abi.html#mangling
|
||||
const char kMangledSymbolPrefix[] = "_Z";
|
||||
|
||||
// Characters that can be used for symbols, generated by Ruby:
|
||||
// (('a'..'z').to_a+('A'..'Z').to_a+('0'..'9').to_a + ['_']).join
|
||||
const char kSymbolCharacters[] =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||
#endif // !defined(USE_SYMBOLIZE) && defined(__GLIBCXX__)
|
||||
|
||||
#if !defined(USE_SYMBOLIZE)
|
||||
// Demangles C++ symbols in the given text. Example:
|
||||
//
|
||||
// "out/Debug/base_unittests(_ZN10StackTraceC1Ev+0x20) [0x817778c]"
|
||||
// =>
|
||||
// "out/Debug/base_unittests(StackTrace::StackTrace()+0x20) [0x817778c]"
|
||||
void DemangleSymbols(std::string* text) {
|
||||
// Note: code in this function is NOT async-signal safe (std::string uses
|
||||
// malloc internally).
|
||||
|
||||
#if defined(__GLIBCXX__) && !defined(__UCLIBC__)
|
||||
|
||||
std::string::size_type search_from = 0;
|
||||
while (search_from < text->size()) {
|
||||
// Look for the start of a mangled symbol, from search_from.
|
||||
std::string::size_type mangled_start =
|
||||
text->find(kMangledSymbolPrefix, search_from);
|
||||
if (mangled_start == std::string::npos) {
|
||||
break; // Mangled symbol not found.
|
||||
}
|
||||
|
||||
// Look for the end of the mangled symbol.
|
||||
std::string::size_type mangled_end =
|
||||
text->find_first_not_of(kSymbolCharacters, mangled_start);
|
||||
if (mangled_end == std::string::npos) {
|
||||
mangled_end = text->size();
|
||||
}
|
||||
std::string mangled_symbol =
|
||||
text->substr(mangled_start, mangled_end - mangled_start);
|
||||
|
||||
// Try to demangle the mangled symbol candidate.
|
||||
int status = 0;
|
||||
scoped_ptr<char, butil::FreeDeleter> demangled_symbol(
|
||||
abi::__cxa_demangle(mangled_symbol.c_str(), NULL, 0, &status));
|
||||
if (status == 0) { // Demangling is successful.
|
||||
// Remove the mangled symbol.
|
||||
text->erase(mangled_start, mangled_end - mangled_start);
|
||||
// Insert the demangled symbol.
|
||||
text->insert(mangled_start, demangled_symbol.get());
|
||||
// Next time, we'll start right after the demangled symbol we inserted.
|
||||
search_from = mangled_start + strlen(demangled_symbol.get());
|
||||
} else {
|
||||
// Failed to demangle. Retry after the "_Z" we just found.
|
||||
search_from = mangled_start + 2;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // defined(__GLIBCXX__) && !defined(__UCLIBC__)
|
||||
}
|
||||
#endif // !defined(USE_SYMBOLIZE)
|
||||
|
||||
class BacktraceOutputHandler {
|
||||
public:
|
||||
virtual void HandleOutput(const char* output) = 0;
|
||||
|
||||
protected:
|
||||
virtual ~BacktraceOutputHandler() {}
|
||||
};
|
||||
|
||||
void OutputPointer(void* pointer, BacktraceOutputHandler* handler) {
|
||||
// This should be more than enough to store a 64-bit number in hex:
|
||||
// 16 hex digits + 1 for null-terminator.
|
||||
char buf[17] = { '\0' };
|
||||
handler->HandleOutput("0x");
|
||||
internal::itoa_r(reinterpret_cast<intptr_t>(pointer),
|
||||
buf, sizeof(buf), 16, 12);
|
||||
handler->HandleOutput(buf);
|
||||
}
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
void OutputFrameId(intptr_t frame_id, BacktraceOutputHandler* handler) {
|
||||
// Max unsigned 64-bit number in decimal has 20 digits (18446744073709551615).
|
||||
// Hence, 30 digits should be more than enough to represent it in decimal
|
||||
// (including the null-terminator).
|
||||
char buf[30] = { '\0' };
|
||||
handler->HandleOutput("#");
|
||||
internal::itoa_r(frame_id, buf, sizeof(buf), 10, 1);
|
||||
handler->HandleOutput(buf);
|
||||
}
|
||||
#endif // defined(USE_SYMBOLIZE)
|
||||
|
||||
void ProcessBacktrace(void *const *trace,
|
||||
size_t size,
|
||||
BacktraceOutputHandler* handler) {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
OutputFrameId(i, handler);
|
||||
handler->HandleOutput(" ");
|
||||
OutputPointer(trace[i], handler);
|
||||
handler->HandleOutput(" ");
|
||||
|
||||
char buf[1024] = { '\0' };
|
||||
|
||||
// Subtract by one as return address of function may be in the next
|
||||
// function when a function is annotated as noreturn.
|
||||
void* address = static_cast<char*>(trace[i]) - 1;
|
||||
if (google::Symbolize(address, buf, sizeof(buf)))
|
||||
handler->HandleOutput(buf);
|
||||
else
|
||||
handler->HandleOutput("<unknown>");
|
||||
|
||||
handler->HandleOutput("\n");
|
||||
}
|
||||
#elif !defined(__UCLIBC__)
|
||||
bool printed = false;
|
||||
|
||||
// Below part is async-signal unsafe (uses malloc), so execute it only
|
||||
// when we are not executing the signal handler.
|
||||
if (in_signal_handler == 0) {
|
||||
scoped_ptr<char*, FreeDeleter>
|
||||
trace_symbols(backtrace_symbols(trace, size));
|
||||
if (trace_symbols.get()) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
std::string trace_symbol = trace_symbols.get()[i];
|
||||
DemangleSymbols(&trace_symbol);
|
||||
handler->HandleOutput(trace_symbol.c_str());
|
||||
handler->HandleOutput("\n");
|
||||
}
|
||||
|
||||
printed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!printed) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
handler->HandleOutput(" [");
|
||||
OutputPointer(trace[i], handler);
|
||||
handler->HandleOutput("]\n");
|
||||
}
|
||||
}
|
||||
#endif // defined(USE_SYMBOLIZE)
|
||||
}
|
||||
|
||||
void PrintToStderr(const char* output) {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
ignore_result(HANDLE_EINTR(write(STDERR_FILENO, output, strlen(output))));
|
||||
}
|
||||
|
||||
void StackDumpSignalHandler(int signal, siginfo_t* info, void* void_context) {
|
||||
// NOTE: This code MUST be async-signal safe.
|
||||
// NO malloc or stdio is allowed here.
|
||||
|
||||
// Record the fact that we are in the signal handler now, so that the rest
|
||||
// of StackTrace can behave in an async-signal-safe manner.
|
||||
in_signal_handler = 1;
|
||||
|
||||
if (BeingDebugged())
|
||||
BreakDebugger();
|
||||
|
||||
PrintToStderr("Received signal ");
|
||||
char buf[1024] = { 0 };
|
||||
internal::itoa_r(signal, buf, sizeof(buf), 10, 0);
|
||||
PrintToStderr(buf);
|
||||
if (signal == SIGBUS) {
|
||||
if (info->si_code == BUS_ADRALN)
|
||||
PrintToStderr(" BUS_ADRALN ");
|
||||
else if (info->si_code == BUS_ADRERR)
|
||||
PrintToStderr(" BUS_ADRERR ");
|
||||
else if (info->si_code == BUS_OBJERR)
|
||||
PrintToStderr(" BUS_OBJERR ");
|
||||
else
|
||||
PrintToStderr(" <unknown> ");
|
||||
} else if (signal == SIGFPE) {
|
||||
if (info->si_code == FPE_FLTDIV)
|
||||
PrintToStderr(" FPE_FLTDIV ");
|
||||
else if (info->si_code == FPE_FLTINV)
|
||||
PrintToStderr(" FPE_FLTINV ");
|
||||
else if (info->si_code == FPE_FLTOVF)
|
||||
PrintToStderr(" FPE_FLTOVF ");
|
||||
else if (info->si_code == FPE_FLTRES)
|
||||
PrintToStderr(" FPE_FLTRES ");
|
||||
else if (info->si_code == FPE_FLTSUB)
|
||||
PrintToStderr(" FPE_FLTSUB ");
|
||||
else if (info->si_code == FPE_FLTUND)
|
||||
PrintToStderr(" FPE_FLTUND ");
|
||||
else if (info->si_code == FPE_INTDIV)
|
||||
PrintToStderr(" FPE_INTDIV ");
|
||||
else if (info->si_code == FPE_INTOVF)
|
||||
PrintToStderr(" FPE_INTOVF ");
|
||||
else
|
||||
PrintToStderr(" <unknown> ");
|
||||
} else if (signal == SIGILL) {
|
||||
if (info->si_code == ILL_BADSTK)
|
||||
PrintToStderr(" ILL_BADSTK ");
|
||||
else if (info->si_code == ILL_COPROC)
|
||||
PrintToStderr(" ILL_COPROC ");
|
||||
else if (info->si_code == ILL_ILLOPN)
|
||||
PrintToStderr(" ILL_ILLOPN ");
|
||||
else if (info->si_code == ILL_ILLADR)
|
||||
PrintToStderr(" ILL_ILLADR ");
|
||||
else if (info->si_code == ILL_ILLTRP)
|
||||
PrintToStderr(" ILL_ILLTRP ");
|
||||
else if (info->si_code == ILL_PRVOPC)
|
||||
PrintToStderr(" ILL_PRVOPC ");
|
||||
else if (info->si_code == ILL_PRVREG)
|
||||
PrintToStderr(" ILL_PRVREG ");
|
||||
else
|
||||
PrintToStderr(" <unknown> ");
|
||||
} else if (signal == SIGSEGV) {
|
||||
if (info->si_code == SEGV_MAPERR)
|
||||
PrintToStderr(" SEGV_MAPERR ");
|
||||
else if (info->si_code == SEGV_ACCERR)
|
||||
PrintToStderr(" SEGV_ACCERR ");
|
||||
else
|
||||
PrintToStderr(" <unknown> ");
|
||||
}
|
||||
if (signal == SIGBUS || signal == SIGFPE ||
|
||||
signal == SIGILL || signal == SIGSEGV) {
|
||||
internal::itoa_r(reinterpret_cast<intptr_t>(info->si_addr),
|
||||
buf, sizeof(buf), 16, 12);
|
||||
PrintToStderr(buf);
|
||||
}
|
||||
PrintToStderr("\n");
|
||||
|
||||
debug::StackTrace().Print();
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
#if ARCH_CPU_X86_FAMILY
|
||||
ucontext_t* context = reinterpret_cast<ucontext_t*>(void_context);
|
||||
const struct {
|
||||
const char* label;
|
||||
greg_t value;
|
||||
} registers[] = {
|
||||
#if ARCH_CPU_32_BITS
|
||||
{ " gs: ", context->uc_mcontext.gregs[REG_GS] },
|
||||
{ " fs: ", context->uc_mcontext.gregs[REG_FS] },
|
||||
{ " es: ", context->uc_mcontext.gregs[REG_ES] },
|
||||
{ " ds: ", context->uc_mcontext.gregs[REG_DS] },
|
||||
{ " edi: ", context->uc_mcontext.gregs[REG_EDI] },
|
||||
{ " esi: ", context->uc_mcontext.gregs[REG_ESI] },
|
||||
{ " ebp: ", context->uc_mcontext.gregs[REG_EBP] },
|
||||
{ " esp: ", context->uc_mcontext.gregs[REG_ESP] },
|
||||
{ " ebx: ", context->uc_mcontext.gregs[REG_EBX] },
|
||||
{ " edx: ", context->uc_mcontext.gregs[REG_EDX] },
|
||||
{ " ecx: ", context->uc_mcontext.gregs[REG_ECX] },
|
||||
{ " eax: ", context->uc_mcontext.gregs[REG_EAX] },
|
||||
{ " trp: ", context->uc_mcontext.gregs[REG_TRAPNO] },
|
||||
{ " err: ", context->uc_mcontext.gregs[REG_ERR] },
|
||||
{ " ip: ", context->uc_mcontext.gregs[REG_EIP] },
|
||||
{ " cs: ", context->uc_mcontext.gregs[REG_CS] },
|
||||
{ " efl: ", context->uc_mcontext.gregs[REG_EFL] },
|
||||
{ " usp: ", context->uc_mcontext.gregs[REG_UESP] },
|
||||
{ " ss: ", context->uc_mcontext.gregs[REG_SS] },
|
||||
#elif ARCH_CPU_64_BITS
|
||||
{ " r8: ", context->uc_mcontext.gregs[REG_R8] },
|
||||
{ " r9: ", context->uc_mcontext.gregs[REG_R9] },
|
||||
{ " r10: ", context->uc_mcontext.gregs[REG_R10] },
|
||||
{ " r11: ", context->uc_mcontext.gregs[REG_R11] },
|
||||
{ " r12: ", context->uc_mcontext.gregs[REG_R12] },
|
||||
{ " r13: ", context->uc_mcontext.gregs[REG_R13] },
|
||||
{ " r14: ", context->uc_mcontext.gregs[REG_R14] },
|
||||
{ " r15: ", context->uc_mcontext.gregs[REG_R15] },
|
||||
{ " di: ", context->uc_mcontext.gregs[REG_RDI] },
|
||||
{ " si: ", context->uc_mcontext.gregs[REG_RSI] },
|
||||
{ " bp: ", context->uc_mcontext.gregs[REG_RBP] },
|
||||
{ " bx: ", context->uc_mcontext.gregs[REG_RBX] },
|
||||
{ " dx: ", context->uc_mcontext.gregs[REG_RDX] },
|
||||
{ " ax: ", context->uc_mcontext.gregs[REG_RAX] },
|
||||
{ " cx: ", context->uc_mcontext.gregs[REG_RCX] },
|
||||
{ " sp: ", context->uc_mcontext.gregs[REG_RSP] },
|
||||
{ " ip: ", context->uc_mcontext.gregs[REG_RIP] },
|
||||
{ " efl: ", context->uc_mcontext.gregs[REG_EFL] },
|
||||
{ " cgf: ", context->uc_mcontext.gregs[REG_CSGSFS] },
|
||||
{ " erf: ", context->uc_mcontext.gregs[REG_ERR] },
|
||||
{ " trp: ", context->uc_mcontext.gregs[REG_TRAPNO] },
|
||||
{ " msk: ", context->uc_mcontext.gregs[REG_OLDMASK] },
|
||||
{ " cr2: ", context->uc_mcontext.gregs[REG_CR2] },
|
||||
#endif
|
||||
};
|
||||
|
||||
#if ARCH_CPU_32_BITS
|
||||
const int kRegisterPadding = 8;
|
||||
#elif ARCH_CPU_64_BITS
|
||||
const int kRegisterPadding = 16;
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(registers); i++) {
|
||||
PrintToStderr(registers[i].label);
|
||||
internal::itoa_r(registers[i].value, buf, sizeof(buf),
|
||||
16, kRegisterPadding);
|
||||
PrintToStderr(buf);
|
||||
|
||||
if ((i + 1) % 4 == 0)
|
||||
PrintToStderr("\n");
|
||||
}
|
||||
PrintToStderr("\n");
|
||||
#endif
|
||||
#elif defined(OS_MACOSX)
|
||||
// TODO(shess): Port to 64-bit, and ARM architecture (32 and 64-bit).
|
||||
#if ARCH_CPU_X86_FAMILY && ARCH_CPU_32_BITS
|
||||
ucontext_t* context = reinterpret_cast<ucontext_t*>(void_context);
|
||||
size_t len;
|
||||
|
||||
// NOTE: Even |snprintf()| is not on the approved list for signal
|
||||
// handlers, but buffered I/O is definitely not on the list due to
|
||||
// potential for |malloc()|.
|
||||
len = static_cast<size_t>(
|
||||
snprintf(buf, sizeof(buf),
|
||||
"ax: %x, bx: %x, cx: %x, dx: %x\n",
|
||||
context->uc_mcontext->__ss.__eax,
|
||||
context->uc_mcontext->__ss.__ebx,
|
||||
context->uc_mcontext->__ss.__ecx,
|
||||
context->uc_mcontext->__ss.__edx));
|
||||
write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1));
|
||||
|
||||
len = static_cast<size_t>(
|
||||
snprintf(buf, sizeof(buf),
|
||||
"di: %x, si: %x, bp: %x, sp: %x, ss: %x, flags: %x\n",
|
||||
context->uc_mcontext->__ss.__edi,
|
||||
context->uc_mcontext->__ss.__esi,
|
||||
context->uc_mcontext->__ss.__ebp,
|
||||
context->uc_mcontext->__ss.__esp,
|
||||
context->uc_mcontext->__ss.__ss,
|
||||
context->uc_mcontext->__ss.__eflags));
|
||||
write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1));
|
||||
|
||||
len = static_cast<size_t>(
|
||||
snprintf(buf, sizeof(buf),
|
||||
"ip: %x, cs: %x, ds: %x, es: %x, fs: %x, gs: %x\n",
|
||||
context->uc_mcontext->__ss.__eip,
|
||||
context->uc_mcontext->__ss.__cs,
|
||||
context->uc_mcontext->__ss.__ds,
|
||||
context->uc_mcontext->__ss.__es,
|
||||
context->uc_mcontext->__ss.__fs,
|
||||
context->uc_mcontext->__ss.__gs));
|
||||
write(STDERR_FILENO, buf, std::min(len, sizeof(buf) - 1));
|
||||
#endif // ARCH_CPU_32_BITS
|
||||
#endif // defined(OS_MACOSX)
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
class PrintBacktraceOutputHandler : public BacktraceOutputHandler {
|
||||
public:
|
||||
PrintBacktraceOutputHandler() {}
|
||||
|
||||
virtual void HandleOutput(const char* output) OVERRIDE {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
PrintToStderr(output);
|
||||
}
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(PrintBacktraceOutputHandler);
|
||||
};
|
||||
|
||||
class StreamBacktraceOutputHandler : public BacktraceOutputHandler {
|
||||
public:
|
||||
explicit StreamBacktraceOutputHandler(std::ostream* os) : os_(os) {
|
||||
}
|
||||
|
||||
virtual void HandleOutput(const char* output) OVERRIDE {
|
||||
(*os_) << output;
|
||||
}
|
||||
|
||||
private:
|
||||
std::ostream* os_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StreamBacktraceOutputHandler);
|
||||
};
|
||||
|
||||
class StringBacktraceOutputHandler : public BacktraceOutputHandler {
|
||||
public:
|
||||
explicit StringBacktraceOutputHandler(std::string& str) : _str(str) {}
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StringBacktraceOutputHandler);
|
||||
|
||||
void HandleOutput(const char* output) OVERRIDE {
|
||||
if (NULL == output) {
|
||||
return;
|
||||
}
|
||||
_str.append(output);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string& _str;
|
||||
};
|
||||
|
||||
void WarmUpBacktrace() {
|
||||
// Warm up stack trace infrastructure. It turns out that on the first
|
||||
// call glibc initializes some internal data structures using pthread_once,
|
||||
// and even backtrace() can call malloc(), leading to hangs.
|
||||
//
|
||||
// Example stack trace snippet (with tcmalloc):
|
||||
//
|
||||
// #8 0x0000000000a173b5 in tc_malloc
|
||||
// at ./third_party/tcmalloc/chromium/src/debugallocation.cc:1161
|
||||
// #9 0x00007ffff7de7900 in _dl_map_object_deps at dl-deps.c:517
|
||||
// #10 0x00007ffff7ded8a9 in dl_open_worker at dl-open.c:262
|
||||
// #11 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178
|
||||
// #12 0x00007ffff7ded31a in _dl_open (file=0x7ffff625e298 "libgcc_s.so.1")
|
||||
// at dl-open.c:639
|
||||
// #13 0x00007ffff6215602 in do_dlopen at dl-libc.c:89
|
||||
// #14 0x00007ffff7de9176 in _dl_catch_error at dl-error.c:178
|
||||
// #15 0x00007ffff62156c4 in dlerror_run at dl-libc.c:48
|
||||
// #16 __GI___libc_dlopen_mode at dl-libc.c:165
|
||||
// #17 0x00007ffff61ef8f5 in init
|
||||
// at ../sysdeps/x86_64/../ia64/backtrace.c:53
|
||||
// #18 0x00007ffff6aad400 in pthread_once
|
||||
// at ../nptl/sysdeps/unix/sysv/linux/x86_64/pthread_once.S:104
|
||||
// #19 0x00007ffff61efa14 in __GI___backtrace
|
||||
// at ../sysdeps/x86_64/../ia64/backtrace.c:104
|
||||
// #20 0x0000000000752a54 in butil::debug::StackTrace::StackTrace
|
||||
// at butil/debug/stack_trace_posix.cc:175
|
||||
// #21 0x00000000007a4ae5 in
|
||||
// butil::(anonymous namespace)::StackDumpSignalHandler
|
||||
// at butil/process_util_posix.cc:172
|
||||
// #22 <signal handler called>
|
||||
StackTrace stack_trace;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
|
||||
// class SandboxSymbolizeHelper.
|
||||
//
|
||||
// The purpose of this class is to prepare and install a "file open" callback
|
||||
// needed by the stack trace symbolization code
|
||||
// (butil/third_party/symbolize/symbolize.h) so that it can function properly
|
||||
// in a sandboxed process. The caveat is that this class must be instantiated
|
||||
// before the sandboxing is enabled so that it can get the chance to open all
|
||||
// the object files that are loaded in the virtual address space of the current
|
||||
// process.
|
||||
class SandboxSymbolizeHelper {
|
||||
public:
|
||||
// Returns the singleton instance.
|
||||
static SandboxSymbolizeHelper* GetInstance() {
|
||||
return Singleton<SandboxSymbolizeHelper>::get();
|
||||
}
|
||||
|
||||
private:
|
||||
friend struct DefaultSingletonTraits<SandboxSymbolizeHelper>;
|
||||
|
||||
SandboxSymbolizeHelper()
|
||||
: is_initialized_(false) {
|
||||
Init();
|
||||
}
|
||||
|
||||
~SandboxSymbolizeHelper() {
|
||||
UnregisterCallback();
|
||||
CloseObjectFiles();
|
||||
}
|
||||
|
||||
// Returns a O_RDONLY file descriptor for |file_path| if it was opened
|
||||
// sucessfully during the initialization. The file is repositioned at
|
||||
// offset 0.
|
||||
// IMPORTANT: This function must be async-signal-safe because it can be
|
||||
// called from a signal handler (symbolizing stack frames for a crash).
|
||||
int GetFileDescriptor(const char* file_path) {
|
||||
int fd = -1;
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
if (file_path) {
|
||||
// The assumption here is that iterating over std::map<std::string, int>
|
||||
// using a const_iterator does not allocate dynamic memory, hense it is
|
||||
// async-signal-safe.
|
||||
std::map<std::string, int>::const_iterator it;
|
||||
for (it = modules_.begin(); it != modules_.end(); ++it) {
|
||||
if (strcmp((it->first).c_str(), file_path) == 0) {
|
||||
// POSIX.1-2004 requires an implementation to guarantee that dup()
|
||||
// is async-signal-safe.
|
||||
fd = dup(it->second);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// POSIX.1-2004 requires an implementation to guarantee that lseek()
|
||||
// is async-signal-safe.
|
||||
if (fd >= 0 && lseek(fd, 0, SEEK_SET) < 0) {
|
||||
// Failed to seek.
|
||||
fd = -1;
|
||||
}
|
||||
}
|
||||
#endif // !defined(NDEBUG)
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
// Searches for the object file (from /proc/self/maps) that contains
|
||||
// the specified pc. If found, sets |start_address| to the start address
|
||||
// of where this object file is mapped in memory, sets the module base
|
||||
// address into |base_address|, copies the object file name into
|
||||
// |out_file_name|, and attempts to open the object file. If the object
|
||||
// file is opened successfully, returns the file descriptor. Otherwise,
|
||||
// returns -1. |out_file_name_size| is the size of the file name buffer
|
||||
// (including the null terminator).
|
||||
// IMPORTANT: This function must be async-signal-safe because it can be
|
||||
// called from a signal handler (symbolizing stack frames for a crash).
|
||||
static int OpenObjectFileContainingPc(uint64_t pc, uint64_t& start_address,
|
||||
uint64_t& base_address, char* file_path,
|
||||
int file_path_size) {
|
||||
// This method can only be called after the singleton is instantiated.
|
||||
// This is ensured by the following facts:
|
||||
// * This is the only static method in this class, it is private, and
|
||||
// the class has no friends (except for the DefaultSingletonTraits).
|
||||
// The compiler guarantees that it can only be called after the
|
||||
// singleton is instantiated.
|
||||
// * This method is used as a callback for the stack tracing code and
|
||||
// the callback registration is done in the constructor, so logically
|
||||
// it cannot be called before the singleton is created.
|
||||
SandboxSymbolizeHelper* instance = GetInstance();
|
||||
|
||||
// The assumption here is that iterating over
|
||||
// std::vector<MappedMemoryRegion> using a const_iterator does not allocate
|
||||
// dynamic memory, hence it is async-signal-safe.
|
||||
std::vector<MappedMemoryRegion>::const_iterator it;
|
||||
bool is_first = true;
|
||||
for (it = instance->regions_.begin(); it != instance->regions_.end();
|
||||
++it, is_first = false) {
|
||||
const MappedMemoryRegion& region = *it;
|
||||
if (region.start <= pc && pc < region.end) {
|
||||
start_address = region.start;
|
||||
// Don't subtract 'start_address' from the first entry:
|
||||
// * If a binary is compiled w/o -pie, then the first entry in
|
||||
// process maps is likely the binary itself (all dynamic libs
|
||||
// are mapped higher in address space). For such a binary,
|
||||
// instruction offset in binary coincides with the actual
|
||||
// instruction address in virtual memory (as code section
|
||||
// is mapped to a fixed memory range).
|
||||
// * If a binary is compiled with -pie, all the modules are
|
||||
// mapped high at address space (in particular, higher than
|
||||
// shadow memory of the tool), so the module can't be the
|
||||
// first entry.
|
||||
base_address = (is_first ? 0U : start_address) - region.offset;
|
||||
if (file_path && file_path_size > 0) {
|
||||
strncpy(file_path, region.path.c_str(), file_path_size);
|
||||
// Ensure null termination.
|
||||
file_path[file_path_size - 1] = '\0';
|
||||
}
|
||||
return instance->GetFileDescriptor(region.path.c_str());
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Parses /proc/self/maps in order to compile a list of all object file names
|
||||
// for the modules that are loaded in the current process.
|
||||
// Returns true on success.
|
||||
bool CacheMemoryRegions() {
|
||||
// Reads /proc/self/maps.
|
||||
std::string contents;
|
||||
if (!ReadProcMaps(&contents)) {
|
||||
LOG(ERROR) << "Failed to read /proc/self/maps";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Parses /proc/self/maps.
|
||||
if (!ParseProcMaps(contents, ®ions_)) {
|
||||
LOG(ERROR) << "Failed to parse the contents of /proc/self/maps";
|
||||
return false;
|
||||
}
|
||||
|
||||
is_initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// FIXME(gejun): Missing O_CLOEXEC from our linux headers. The flag should
|
||||
// work on majority machines which installed 2.6.23 or newer kernels. But
|
||||
// if the kernel is older, I'm not sure that open() will fail or ignore
|
||||
// the flag.
|
||||
#ifndef O_CLOEXEC
|
||||
#define O_CLOEXEC 02000000
|
||||
#endif
|
||||
|
||||
// Opens all object files and caches their file descriptors.
|
||||
void OpenSymbolFiles() {
|
||||
// Pre-opening and caching the file descriptors of all loaded modules is
|
||||
// not considered safe for retail builds. Hence it is only done in debug
|
||||
// builds. For more details, take a look at: http://crbug.com/341966
|
||||
// Enabling this to release mode would require approval from the security
|
||||
// team.
|
||||
#if !defined(NDEBUG)
|
||||
// Open the object files for all read-only executable regions and cache
|
||||
// their file descriptors.
|
||||
std::vector<MappedMemoryRegion>::const_iterator it;
|
||||
for (it = regions_.begin(); it != regions_.end(); ++it) {
|
||||
const MappedMemoryRegion& region = *it;
|
||||
// Only interesed in read-only executable regions.
|
||||
if ((region.permissions & MappedMemoryRegion::READ) ==
|
||||
MappedMemoryRegion::READ &&
|
||||
(region.permissions & MappedMemoryRegion::WRITE) == 0 &&
|
||||
(region.permissions & MappedMemoryRegion::EXECUTE) ==
|
||||
MappedMemoryRegion::EXECUTE) {
|
||||
if (region.path.empty()) {
|
||||
// Skip regions with empty file names.
|
||||
continue;
|
||||
}
|
||||
if (region.path[0] == '[') {
|
||||
// Skip pseudo-paths, like [stack], [vdso], [heap], etc ...
|
||||
continue;
|
||||
}
|
||||
// Avoid duplicates.
|
||||
if (modules_.find(region.path) == modules_.end()) {
|
||||
int fd = open(region.path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
if (fd >= 0) {
|
||||
modules_.emplace(region.path, fd);
|
||||
} else {
|
||||
LOG(WARNING) << "Failed to open file: " << region.path
|
||||
<< "\n Error: " << strerror(errno);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !defined(NDEBUG)
|
||||
}
|
||||
|
||||
// Initializes and installs the symbolization callback.
|
||||
void Init() {
|
||||
if (CacheMemoryRegions()) {
|
||||
OpenSymbolFiles();
|
||||
google::InstallSymbolizeOpenObjectFileCallback(
|
||||
&OpenObjectFileContainingPc);
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister symbolization callback.
|
||||
void UnregisterCallback() {
|
||||
if (is_initialized_) {
|
||||
google::InstallSymbolizeOpenObjectFileCallback(NULL);
|
||||
is_initialized_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Closes all file descriptors owned by this instance.
|
||||
void CloseObjectFiles() {
|
||||
#if !defined(NDEBUG)
|
||||
std::map<std::string, int>::iterator it;
|
||||
for (it = modules_.begin(); it != modules_.end(); ++it) {
|
||||
int ret = IGNORE_EINTR(close(it->second));
|
||||
DCHECK(!ret);
|
||||
it->second = -1;
|
||||
}
|
||||
modules_.clear();
|
||||
#endif // !defined(NDEBUG)
|
||||
}
|
||||
|
||||
// Set to true upon successful initialization.
|
||||
bool is_initialized_;
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
// Mapping from file name to file descriptor. Includes file descriptors
|
||||
// for all successfully opened object files and the file descriptor for
|
||||
// /proc/self/maps. This code is not safe for release builds so
|
||||
// this is only done for DEBUG builds.
|
||||
std::map<std::string, int> modules_;
|
||||
#endif // !defined(NDEBUG)
|
||||
|
||||
// Cache for the process memory regions. Produced by parsing the contents
|
||||
// of /proc/self/maps cache.
|
||||
std::vector<MappedMemoryRegion> regions_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(SandboxSymbolizeHelper);
|
||||
};
|
||||
#endif // USE_SYMBOLIZE
|
||||
|
||||
bool EnableInProcessStackDumpingForSandbox() {
|
||||
#if defined(USE_SYMBOLIZE)
|
||||
SandboxSymbolizeHelper::GetInstance();
|
||||
#endif // USE_SYMBOLIZE
|
||||
|
||||
return EnableInProcessStackDumping();
|
||||
}
|
||||
|
||||
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.
|
||||
struct sigaction sigpipe_action;
|
||||
memset(&sigpipe_action, 0, sizeof(sigpipe_action));
|
||||
sigpipe_action.sa_handler = SIG_IGN;
|
||||
sigemptyset(&sigpipe_action.sa_mask);
|
||||
bool success = (sigaction(SIGPIPE, &sigpipe_action, NULL) == 0);
|
||||
|
||||
// Avoid hangs during backtrace initialization, see above.
|
||||
WarmUpBacktrace();
|
||||
|
||||
struct sigaction action;
|
||||
memset(&action, 0, sizeof(action));
|
||||
action.sa_flags = SA_RESETHAND | SA_SIGINFO;
|
||||
action.sa_sigaction = &StackDumpSignalHandler;
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
success &= (sigaction(SIGILL, &action, NULL) == 0);
|
||||
success &= (sigaction(SIGABRT, &action, NULL) == 0);
|
||||
success &= (sigaction(SIGFPE, &action, NULL) == 0);
|
||||
success &= (sigaction(SIGBUS, &action, NULL) == 0);
|
||||
success &= (sigaction(SIGSEGV, &action, NULL) == 0);
|
||||
// On Linux, SIGSYS is reserved by the kernel for seccomp-bpf sandboxing.
|
||||
#if !defined(OS_LINUX)
|
||||
success &= (sigaction(SIGSYS, &action, NULL) == 0);
|
||||
#endif // !defined(OS_LINUX)
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
StackTrace::StackTrace(bool exclude_self) {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
if (GetStackTrace) {
|
||||
count_ = GetStackTrace(trace_, arraysize(trace_), exclude_self ? 1 : 0);
|
||||
} else {
|
||||
#if !defined(__UCLIBC__)
|
||||
// Though the backtrace API man page does not list any possible negative
|
||||
// return values, we take no chance.
|
||||
count_ = butil::saturated_cast<size_t>(backtrace(trace_, arraysize(trace_)));
|
||||
if (exclude_self && count_ > 1) {
|
||||
// Skip the top frame.
|
||||
memmove(trace_, trace_ + 1, (count_ - 1) * sizeof(void*));
|
||||
count_--;
|
||||
}
|
||||
#else
|
||||
count_ = 0;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
bool StackTrace::FindSymbol(void* symbol) const {
|
||||
#if !defined(__UCLIBC__)
|
||||
for (size_t i = 0; i < count_; ++i) {
|
||||
uint64_t saddr;
|
||||
// Subtract by one as return address of function may be in the next
|
||||
// function when a function is annotated as noreturn.
|
||||
void* address = static_cast<char*>(trace_[i]) - 1;
|
||||
if (!google::SymbolizeAddress(address, &saddr)) {
|
||||
continue;
|
||||
}
|
||||
if ((void*)saddr == symbol) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
void StackTrace::Print() const {
|
||||
// NOTE: This code MUST be async-signal safe (it's used by in-process
|
||||
// stack dumping signal handler). NO malloc or stdio is allowed here.
|
||||
|
||||
#if !defined(__UCLIBC__)
|
||||
PrintBacktraceOutputHandler handler;
|
||||
ProcessBacktrace(trace_, count_, &handler);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !defined(__UCLIBC__)
|
||||
void StackTrace::OutputToStream(std::ostream* os) const {
|
||||
StreamBacktraceOutputHandler handler(os);
|
||||
ProcessBacktrace(trace_, count_, &handler);
|
||||
}
|
||||
|
||||
void StackTrace::OutputToString(std::string& str) const {
|
||||
StringBacktraceOutputHandler handler(str);
|
||||
ProcessBacktrace(trace_, count_, &handler);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace internal {
|
||||
|
||||
// NOTE: code from sandbox/linux/seccomp-bpf/demo.cc.
|
||||
char *itoa_r(intptr_t i, char *buf, size_t sz, int base, size_t padding) {
|
||||
// Make sure we can write at least one NUL byte.
|
||||
size_t n = 1;
|
||||
if (n > sz)
|
||||
return NULL;
|
||||
|
||||
if (base < 2 || base > 16) {
|
||||
buf[0] = '\000';
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *start = buf;
|
||||
|
||||
uintptr_t j = i;
|
||||
|
||||
// Handle negative numbers (only for base 10).
|
||||
if (i < 0 && base == 10) {
|
||||
j = -i;
|
||||
|
||||
// Make sure we can write the '-' character.
|
||||
if (++n > sz) {
|
||||
buf[0] = '\000';
|
||||
return NULL;
|
||||
}
|
||||
*start++ = '-';
|
||||
}
|
||||
|
||||
// Loop until we have converted the entire number. Output at least one
|
||||
// character (i.e. '0').
|
||||
char *ptr = start;
|
||||
do {
|
||||
// Make sure there is still enough space left in our output buffer.
|
||||
if (++n > sz) {
|
||||
buf[0] = '\000';
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Output the next digit.
|
||||
*ptr++ = "0123456789abcdef"[j % base];
|
||||
j /= base;
|
||||
|
||||
if (padding > 0)
|
||||
padding--;
|
||||
} while (j > 0 || padding > 0);
|
||||
|
||||
// Terminate the output with a NUL character.
|
||||
*ptr = '\000';
|
||||
|
||||
// Conversion to ASCII actually resulted in the digits being in reverse
|
||||
// order. We can't easily generate them in forward order, as we can't tell
|
||||
// the number of characters needed until we are done converting.
|
||||
// So, now, we reverse the string (except for the possible "-" sign).
|
||||
while (--ptr > start) {
|
||||
char ch = *ptr;
|
||||
*ptr = *start;
|
||||
*start++ = ch;
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace debug
|
||||
} // namespace butil
|
||||
Reference in New Issue
Block a user