Last active
January 4, 2016 06:56
-
-
Save awreece/5bcb361ff8344350f25e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import gdb | |
from datetime import datetime | |
import sys | |
uintptr_t = gdb.lookup_type("uintptr_t") | |
if (sys.version_info > (3, 0)): | |
raw_input = input | |
def read_command_lines(): | |
# | |
# It is really unfortunate that we there does not appear to be a way to | |
# execute the gdb function `read_command_lines` from python. | |
# | |
ret = [] | |
gdb.write('End with a line saying just "end".\n') | |
line = raw_input(">") | |
while line != "end": | |
ret.append(line) | |
line = raw_input(">") | |
return ret | |
def FunctionForPc(pc): | |
orig_pc = pc | |
try: | |
block = gdb.block_for_pc(int(pc)) | |
while block: | |
if block.function: | |
return str(block.function) | |
block = block.superblock | |
except: | |
pass | |
# | |
# If we couldn't figure it out by looking at the block, it is still | |
# possible that gdb can figure out something for it (e.g. if it is | |
# in JIT-ed code, etc. If all else fails, return the original address | |
# as hex. | |
# | |
if isinstance(orig_pc, gdb.Value): | |
return str(orig_pc) | |
else: | |
return hex(int(orig_pc)) | |
def TrackedClassWhatis(tc): | |
assert isinstance(tc, gdb.Value) | |
timeval = tc['ti_metadata']['tim_timestamp'] | |
seconds = int(timeval['tv_sec'].cast(uintptr_t)) | |
micros = int(timeval['tv_usec'].cast(uintptr_t)) | |
alloctime = datetime.utcfromtimestamp(seconds + (1000000.0 / micros)) | |
gdb.write(str(tc) + " is a " + str(tc.type) + | |
" allocated by thread " + | |
str(tc['ti_metadata']['tim_thread']) + | |
" at " + str(alloctime) + | |
" from:\n") | |
depth = int(tc['ti_metadata']['tim_stack_depth']) | |
raw_stack = (tc['ti_metadata']['tim_stack'][i] for i in range(depth)) | |
for pc in raw_stack: | |
gdb.write("\t" + FunctionForPc(pc) + "\n") | |
gdb.write("\n") | |
def TrackedClassWalk(class_name, filter=lambda _: True): | |
instance = gdb.parse_and_eval( | |
'TrackedClass<%s>::s_tracked_instances' % class_name) | |
while instance: | |
gdb.execute("set $_ = ('%s' *)%s" % (class_name, str(instance))) | |
gdb.execute("set $__ = *$_") | |
if filter(instance): | |
yield instance | |
instance = instance['ti_next_instance'] | |
class TrackedClassCommand(gdb.Command): | |
"""Operations on tracked clases.""" | |
def __init__(self): | |
super(TrackedClassCommand, self).__init__( | |
"tracked-class", gdb.COMMAND_DATA, gdb.COMPLETE_COMMAND, True) | |
TrackedClassCommand() | |
class TrackedClassWhatisCommand(gdb.Command): | |
"""Print allocation metadata for a TrackedClass.""" | |
def __init__(self): | |
super(TrackedClassWhatisCommand, self).__init__( | |
"tracked-class whatis", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) | |
def invoke(self, argument, from_tty): | |
tc = gdb.parse_and_eval(argument) | |
TrackedClassWhatis(tc) | |
self.dont_repeat() | |
TrackedClassWhatisCommand() | |
class TrackedClassWalkCommand(gdb.Command): | |
"""Print all instances of a TrackedClass. | |
If an optional filter is used, $_ and $__ can be used in the filter | |
expression to reference the instance. | |
Usage: | |
tracked-class walk <type> [if <filter>] | |
Example: | |
(gdb) tracked-class walk LLVMEngine::CompiledUnit if $_->m_loaded | |
""" | |
def __init__(self): | |
super(TrackedClassWalkCommand, self).__init__( | |
"tracked-class walk", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) | |
def invoke(self, argument, from_tty): | |
args = gdb.string_to_argv(argument) | |
if (len(args) == 0 or len(args) == 2 or | |
(len(args) > 1 and args[1] != "if")): | |
raise gdb.GdbError("Usage: <type> [if <filter expression>]") | |
_filter = lambda _: True | |
if len(args) > 1: | |
_filter = lambda _: bool(gdb.parse_and_eval(" ".join(args[2:]))) | |
for tc in TrackedClassWalk(args[0], _filter): | |
gdb.write(str(tc) + "\n") | |
self.dont_repeat() | |
TrackedClassWalkCommand() | |
class TrackedClassWalkCommandsCommand(gdb.Command): | |
"""Execute the given commands on all instances of a TrackedClass. | |
The commands can reference the instance via $_ and $__. | |
If an optional filter is used, $_ and $__ can be used in the filter | |
expression to reference the instance. | |
Usage: | |
tracked-class walk-commands <type> [if <filter>] | |
Example: | |
(gdb) tracked-class walk-commands Mbc::Module if $_->m_loaded | |
Type commands for each instance. | |
End with a line saying just "end". | |
>tracked-class whatis $_ | |
>end | |
""" | |
def __init__(self): | |
super(TrackedClassWalkCommandsCommand, self).__init__( | |
"tracked-class walk-commands", gdb.COMMAND_DATA, | |
gdb.COMPLETE_EXPRESSION) | |
def invoke(self, argument, from_tty): | |
args = gdb.string_to_argv(argument) | |
if (len(args) == 0 or len(args) == 2 or | |
(len(args) > 1 and args[1] != "if")): | |
raise gdb.GdbError("Usage: <type> [if <filter expression>]") | |
_filter = lambda _: True | |
if len(args) > 1: | |
_filter = lambda _: bool(gdb.parse_and_eval(" ".join(args[2:]))) | |
gdb.write("Type commands for each instance.\n") | |
commands = read_command_lines() | |
for _ in TrackedClassWalk(args[0], _filter): | |
for command in commands: | |
gdb.execute(command) | |
self.dont_repeat() | |
TrackedClassWalkCommandsCommand() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
// A parent class using CRTP to track all instances of the derived class. | |
// | |
// This supports two python functions for use in gdb: | |
// | |
// - TrackedClassWalk(derived_class_name, filter=lambda _: True): | |
// | |
// A generator that iterates over all instances of the derived class | |
// in existance. This currently relies on embedding additional data in | |
// the Derived class to track all instances in a doubly linked list | |
// and consequently is only enabled in debug builds. Future versions | |
// of TrackedClass will instead use memory allocator metadata to enable | |
// them to support TrackedClassWalk with no overhead in release builds | |
// as well as debug builds. | |
// | |
// - TrackedClassWhatis(gdb_value, pretty=True): | |
// | |
// Prints to the gdb console metadata about the value, including | |
// the stacktrace where it was allocated. This currently requires | |
// embedding the stack trace in the Derived class and consequently | |
// is only enabled in debug builds. Future versions of TrackedClass | |
// will instead use memory allocator metadata to avoid the need to | |
// embed data in the Derived class, but will still only support | |
// TrackedClassWhatis in debug builds. | |
// | |
// == Usage == | |
// | |
// (gdb) source tracked_class.py | |
// (gdb) python print(len(list(TrackedClassWalk('LLVMEngine::CompiledUnit')))) | |
// 23 | |
// (gdb) python le_cu = next(TrackedClassWalk('LLVMEngine::CompiledUnit')) | |
// (gdb) python print le_cu | |
// 0x5f4db00 | |
// (gdb) python m_u = next(TrackedClassWalk('Mbc::Unit', lambda u: u['m_cu']['_M_t']['_M_head_impl'] == le_cu)) | |
// (gdb) python print m_u | |
// 0x5c776e0 | |
// (gdb) python TrackedClassWhatis(le_cu) | |
// 0x64d77f0 is a LLVMEngine::CompiledUnit * allocated by thread {_M_thread = 140572196010240} | |
// TrackedClass<LLVMEngine::CompiledUnit>::TrackedClass() | |
// LLVMEngine::CompiledUnit::CompiledUnit(bool) | |
// LLVMEngine::CompiledUnitBuilder::Create(bool, bool) | |
// LLVMEngine::Compile(Mbc::Unit*, char const*, bool, bool) | |
// Mbc::Unit::getFunction<void, void*>(std::string const&, std::function<void (void*)>&, InterpreterMode) | |
// GetCreateIndexesFn(AllMetadataTable const&, Mbc::Unit*, unsigned long, InterpreterMode, AlterTableInfo*) | |
// CreateTableEntry(DynamicObject&, char const*, int, std::unique_ptr<AllMetadataTable, std::default_delete<AllMetadataTable> >&, unsigned long, DatabaseMetadataManager*, char const*, AlterTableInfo*, OperatorSelect*, OperatorSelect*, bool) | |
// Metametadata::CreateInternalTable(int, INTERNAL_TABLE_TYPE, DatabasesListEntry*) | |
// DatabaseMetadataManager::DatabaseMetadataManager(int, MetadataDatabase*, bool, bool, bool, size_t, DatabasesListEntry*) | |
// DatabasesListEntry::DatabasesListEntry(int, MetadataDatabase*, bool, bool, bool, size_t) | |
// MetadataManager::CreateDatabase(int, char const*, bool, bool, bool, unsigned long, unsigned int, bool, bool, MetadataReplica*) | |
// InitInternalDatabase(int, char const*, bool) | |
// MemSqlInitializeMetadataMgr() | |
// main(int, char**) | |
// __libc_start_main | |
// [unknown] | |
// | |
// | |
#ifdef NDEBUG | |
template <typename T> class TrackedClass {}; | |
#else | |
#include <assert.h> | |
#include <execinfo.h> | |
#include <sys/time.h> | |
#include <mutex> | |
#include <thread> | |
template <typename T> | |
class TrackedClass | |
{ | |
protected: | |
__attribute__((noinline)) | |
~TrackedClass() | |
{ | |
assert(ti_magic == TC_ALLOCED_MAGIC); | |
ti_magic = TC_FREED_MAGIC; | |
std::lock_guard<std::mutex> autolock(T::s_tracked_instances_lock); | |
if (ti_prev_instance == nullptr) | |
{ | |
assert(T::s_tracked_instances == static_cast<T *>(this)); | |
T::s_tracked_instances = ti_next_instance; | |
} | |
else | |
{ | |
assert(ti_prev_instance->ti_next_instance == | |
static_cast<T *>(this)); | |
ti_prev_instance->ti_next_instance = ti_next_instance; | |
} | |
if (ti_next_instance != nullptr) | |
{ | |
assert(ti_next_instance->ti_prev_instance == | |
static_cast<T *>(this)); | |
ti_next_instance->ti_prev_instance = ti_prev_instance; | |
} | |
T::s_tracked_instances_count--; | |
} | |
TrackedClass(const TrackedClass&) = delete; | |
TrackedClass(TrackedClass&&) = delete; | |
__attribute__((noinline)) | |
TrackedClass() : ti_magic(TC_ALLOCED_MAGIC), | |
ti_next_instance(nullptr), ti_prev_instance(nullptr) | |
{ | |
trackInstance(); | |
} | |
private: | |
void updateMetadata() | |
{ | |
gettimeofday(&ti_metadata.tim_timestamp, NULL); | |
ti_metadata.tim_thread = std::this_thread::get_id(); | |
void *stack[MAX_STACK_DEPTH + SKIP_STACK_FRAMES]; | |
ti_metadata.tim_stack_depth = | |
backtrace(stack, MAX_STACK_DEPTH + SKIP_STACK_FRAMES); | |
assert(ti_metadata.tim_stack_depth > SKIP_STACK_FRAMES); | |
ti_metadata.tim_stack_depth -= SKIP_STACK_FRAMES; | |
memcpy(ti_metadata.tim_stack, &stack[SKIP_STACK_FRAMES], | |
ti_metadata.tim_stack_depth * sizeof(stack[0])); | |
} | |
void trackInstance() | |
{ | |
assert(ti_prev_instance == nullptr); | |
assert(ti_next_instance == nullptr); | |
assert(ti_magic == TC_ALLOCED_MAGIC); | |
updateMetadata(); | |
ti_prev_instance = nullptr; | |
std::lock_guard<std::mutex> autolock(T::s_tracked_instances_lock); | |
ti_next_instance = T::s_tracked_instances; | |
T::s_tracked_instances = static_cast<T *>(this); | |
if (ti_next_instance != nullptr) | |
{ | |
assert(ti_next_instance->ti_prev_instance == nullptr); | |
ti_next_instance->ti_prev_instance = static_cast<T *>(this); | |
} | |
T::s_tracked_instances_count++; | |
} | |
// Capture up to 24 frames of the stack trace but exclude the first 3 | |
// frames: <updateMetadata, trackInstance, TrackInstance::TrackInstance>. | |
// | |
static constexpr size_t MAX_STACK_DEPTH = 24; | |
static constexpr size_t SKIP_STACK_FRAMES = 3; | |
static constexpr uint64_t TC_ALLOCED_MAGIC = 0x4141414141414141; | |
static constexpr uint64_t TC_FREED_MAGIC = 0x4646464646464646; | |
// We annotate all of these as having "default" visibility so that there | |
// we guarantee there is always ever a single copy of them, even in the | |
// case where it is used in a shared object compiled with | |
// -fvisibility=hidden. | |
// | |
// WARNING A dlcose of a library using a TrackedClass should invoke | |
// std::mutex::~mutex on the s_tracked_instances_lock if the library | |
// had been compiled with -fuse-cxa-atexit. In theory, this should make | |
// s_tracked_instances_lock unuseable by other threads; in practice, this | |
// appears to work. | |
// | |
static T *s_tracked_instances | |
__attribute__((visibility("default"))); | |
static std::mutex s_tracked_instances_lock | |
__attribute__((visibility("default"))); | |
static uint64_t s_tracked_instances_count | |
__attribute__((visibility("default"))); | |
uint64_t ti_magic; | |
T *ti_next_instance; | |
T *ti_prev_instance; | |
struct { | |
std::thread::id tim_thread; | |
struct timeval tim_timestamp; | |
size_t tim_stack_depth; | |
void *tim_stack[MAX_STACK_DEPTH]; | |
} ti_metadata; | |
}; | |
template <typename T> std::mutex TrackedClass<T>::s_tracked_instances_lock; | |
template <typename T> T *TrackedClass<T>::s_tracked_instances(nullptr); | |
template <typename T> uint64_t TrackedClass<T>::s_tracked_instances_count(0); | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment