Skip to content

Instantly share code, notes, and snippets.

@adrianherrera
Created March 9, 2019 06:50
Show Gist options
  • Save adrianherrera/3e1108bc902a34422a6cba7f6ea0529f to your computer and use it in GitHub Desktop.
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
#!/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