| #!/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.") |