blob: cc56bbdd1ce85b66669007088d253b0b4dd76c89 [file] [log] [blame]
#!/usr/bin/env python
# XXX #1767: Python3 introduces a print function instead of the print
# statement in python2. This import allows us to access the print
# function in python2+.
from __future__ import print_function
import gdb
import os
import traceback
import re
# XXX #1767: Python3 unified the int and long types. To maintain
# compatibility with python2, we will use long and alias it to int
# for python3.
if sys.version_info > (3,):
long = int
print('Loading gdb scripts for debugging DynamoRIO...')
# FIXME i#531: Support loading symbols after attaching.
# If we were sourced directly instead of auto-loaded, try to guess where DR is
# so we can make RunDR work. We don't need this for anything else yet.
try:
DR_LIBDIR = os.path.dirname(os.path.abspath(__file__))
except:
DR_LIBDIR = None
class DROption(gdb.Parameter):
def __init__(self, dr_option, param_class):
super(DROption, self).__init__("dr-" + dr_option, gdb.COMMAND_OBSCURE,
param_class)
self.dr_option = dr_option
set_doc = ("DynamoRIO option of the same name.")
show_doc = set_doc
def get_set_string(self):
return str(self.value)
def get_show_string(self, svalue):
return svalue
# The client and client-args options are special in that they do not go in
# DYNAMORIO_OPTIONS without being massaged first. We also specify
# documentation strings for them.
class DRClient(DROption):
def __init__(self):
super(DRClient, self).__init__("client", gdb.PARAM_OPTIONAL_FILENAME)
set_doc = ("Path to DynamoRIO client to run when invoking DR. "
"Leave blank to run without a client.")
show_doc = set_doc
class DRClientArgs(DROption):
def __init__(self):
super(DRClientArgs, self).__init__("client-args", gdb.PARAM_STRING)
set_doc = ("DynamoRIO client arguments.")
show_doc = set_doc
# Client specification options.
dr_client = DRClient()
dr_client_args = DRClientArgs()
# Other useful flags to pass to DynamoRIO.
dr_options = [
DROption('msgbox_mask', gdb.PARAM_ZINTEGER),
DROption('loglevel', gdb.PARAM_ZINTEGER),
DROption('logmask', gdb.PARAM_ZINTEGER),
]
class RunDR(gdb.Command):
"""Run the application under DynamoRIO with the current options.
Goes through the drrun script to avoid depending on the config file format.
"""
def __init__(self):
super(RunDR, self).__init__("rundr", gdb.COMMAND_OBSCURE)
def invoke(self, arg, from_tty):
# Find drrun.
parts = DR_LIBDIR.split(os.sep)
build_mode = parts[-1]
arch = parts[-2][-2:]
if build_mode not in ('debug', 'release'):
print("Unrecognized build_mode {0}.".format(build_mode))
return
if arch not in ('32', '64'):
print("Unable to find drrun using libdir {0}, unrecognized arch {1}.".format(
DR_LIBDIR, arch))
return
drrun_path = os.sep.join(parts[:-2])
drrun_path = os.path.join(drrun_path, 'bin{0}/drrun'.format(arch))
gdb.execute("set exec-wrapper {0} -{1}".format(drrun_path, build_mode))
# Build options string.
# FIXME: The escaping is most likely wrong here. It's tricky because
# the command is parsed by gdb and DynamoRIO.
env_opts = os.environ.get('DYNAMORIO_OPTIONS', '')
param_opts = ' '.join("-{0} {1}".format(p.dr_option, p.value)
for p in dr_options)
client_opts = ''
if dr_client.value:
client_opts = ('-code_api -client_lib {0};0;{1}'.format(
dr_client.value, dr_client_args.value))
dr_opts = ' '.join([env_opts, param_opts, client_opts])
gdb.execute("set env DYNAMORIO_OPTIONS " + dr_opts)
gdb.execute("run " + arg)
RunDR()
def gdb_has_breakpoints():
match = re.match(r'^(\d+)\.(\d+)', gdb.VERSION)
if not match:
# Some version strings are like this: "Fedora 7.7.1-21.fc20"
match = re.match(r'.*\s+(\d+)\.(\d+)', gdb.VERSION)
if not match:
print("Error parsing gdb version ({0})".format(gdb.VERSION))
return False
major = int(match.group(1))
minor = int(match.group(2))
if major > 7:
return True
elif major == 7 and minor >= 3:
return True
return False
# gdb 7.2 doesn't really support breakpoints in the Python API. We do a version
# check so we can print an informative message rather than dying with an
# exception on "class PrivloadBP(gdb.Breakpoint)".
if gdb_has_breakpoints():
class PrivloadBP(gdb.Breakpoint):
# Enable to debug this breakpoint.
DEBUG = False
DYNAMORIO_BP = True
def __init__(self):
super(PrivloadBP, self).__init__("dr_gdb_add_symbol_file",
internal=not self.DEBUG)
def stop(self):
try:
frame = gdb.newest_frame()
filename = frame.read_var("filename").string()
textaddr = long(frame.read_var("textaddr"))
cmd = "add-symbol-file '{0}' {1}".format(filename, hex(textaddr))
print("Executing gdb command:", cmd)
# We suppress output to the screen with to_string unless we're
# debugging.
gdb.execute(cmd, to_string=not self.DEBUG)
return self.DEBUG # Controls whether the user stops here or not.
except:
# gdb won't print a Python stack trace if we raise an exception,
# so we do it ourselves.
traceback.print_exc()
return True
# Delete all breakpoints set from previous runs and initializations and
# replace them with new ones.
def remove_old_bps():
bps = gdb.breakpoints()
if not bps:
return
for bp in bps:
if getattr(bp, 'DYNAMORIO_BP', False):
bp.delete()
remove_old_bps()
# We need pending breakpoints in order to wait for LD_PRELOAD to bring in
# the library with the symbol.
gdb.execute("set breakpoint pending on")
PrivloadBP()
else:
print("This version of gdb does not support breakpoints from Python. "
"Libraries loaded by DynamoRIO will not be automatically "
"registered with gdb.")