Created
March 9, 2019 06:50
-
-
Save adrianherrera/3e1108bc902a34422a6cba7f6ea0529f to your computer and use it in GitHub Desktop.
GDB script to serialize a stack trace from a crashed program to JSON
This file contains hidden or 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
#!/usr/bin/env python | |
# | |
# Serializes a stack trace from a crashed program into JSON. | |
# | |
# To run: | |
# BACKTRACE_OUT=out.json gdb --command gdb_stack_trace.py PROG [GDB_ARGS]... | |
# | |
import json | |
import os | |
import sys | |
try: | |
import gdb | |
except ImportError as e: | |
raise ImportError('This script must be run inside GDB: %s' % e) | |
# Environment variable that specifies where the backtrace will be dumped to | |
BACKTRACE_OUTPUT_ENV_VAR = 'BACKTRACE_OUT' | |
def error_quit(*args, **kwargs): | |
"""Quit gdb with an error.""" | |
print(*args, file=sys.stderr, **kwargs) | |
sys.exit(1) | |
class Frame(object): | |
"""Represents a stack frame.""" | |
def __init__(self, gdb_frame, position): | |
self._file = os.path.normpath(gdb_frame.find_sal().symtab.fullname()) | |
self._name = gdb_frame.name() | |
self._position = position | |
self._pc = gdb_frame.pc() | |
@property | |
def file_path(self): | |
return self._file | |
@property | |
def name(self): | |
return self._name | |
@property | |
def position(self): | |
return self._position | |
@property | |
def address(self): | |
return self._pc | |
def to_dict(self): | |
return dict(file=self.file_path, name=self.name, address=self.address) | |
class Backtrace(list): | |
"""Represents a backtrace (i.e., a list of frames) for a thread.""" | |
def __init__(self, thread=None, limit=None): | |
super(Backtrace, self).__init__() | |
# If a thread was specified, save the current thread so that we can | |
# restore it later | |
if thread: | |
orig_thread = gdb.selected_thread() | |
thread.switch() | |
self._abnormal_termination = False | |
i = 0 | |
try: | |
gdb.newest_frame() | |
frame = gdb.selected_frame() | |
while frame: | |
self.append(Frame(frame, i)) | |
try: | |
frame = frame.older() | |
except RuntimeError: | |
self._abnormal_termination = True | |
break | |
i += 1 | |
if limit and i >= limit: | |
break | |
finally: | |
# No matter what happens, restore the original thread | |
orig_thread.switch() | |
@property | |
def abnormal_termination(self): | |
return self._abnormal_termination | |
if __name__ == '__main__': | |
# Some sanity checks | |
inferiors = gdb.inferiors() | |
if len(inferiors) != 1: | |
error_quit('Invalid number of inferiors: %d' % len(inferiors)) | |
inferior = inferiors[0] | |
# If the inferior exits, just quit | |
gdb.events.exited.connect(lambda _: gdb.execute('quit')) | |
try: | |
# Run the program until it either crashes or exits | |
gdb.execute('run') | |
# Maps threads to backtraces | |
backtraces = dict() | |
# Generate the backtrace for each thread | |
for thread in inferior.threads(): | |
thread_id = 'thread-%02d' % thread.num | |
backtraces[thread_id] = Backtrace(thread=thread) | |
# Produce a JSON dump of the backtrace information | |
json_dict = { | |
'abnormal_termination': any(bt.abnormal_termination for bt in | |
backtraces.values()), | |
'backtraces': {tid: [f.to_dict() for f in bt] for tid, bt in | |
backtraces.items()}, | |
} | |
# Either dump the JSON to stdout or to a file (if specified) | |
if BACKTRACE_OUTPUT_ENV_VAR in os.environ: | |
with open(os.environ[BACKTRACE_OUTPUT_ENV_VAR], 'w') as out_f: | |
json.dump(json_dict, out_f, sort_keys=True, indent=4) | |
else: | |
print(json.dumps(json_dict, sort_keys=True, indent=4)) | |
except Exception as e: | |
# If anything goes wrong, just quit | |
error_quit(e) | |
sys.exit(0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment