#!/usr/bin/env python """ Run gdb to disassemble a function, feed the bytes to 'llvm-mc -disassemble' command, and display the disassembly result. """ import os import sys from optparse import OptionParser def is_exe(fpath): """Check whether fpath is an executable.""" return os.path.isfile(fpath) and os.access(fpath, os.X_OK) def which(program): """Find the full path to a program, or return None.""" fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None def do_llvm_mc_disassembly(gdb_commands, gdb_options, exe, func, mc, mc_options): from cStringIO import StringIO import pexpect gdb_prompt = "\r\n\(gdb\) " gdb = pexpect.spawn(('gdb %s' % gdb_options) if gdb_options else 'gdb') # Turn on logging for what gdb sends back. gdb.logfile_read = sys.stdout gdb.expect(gdb_prompt) # See if there any extra command(s) to execute before we issue the file command. for cmd in gdb_commands: gdb.sendline(cmd) gdb.expect(gdb_prompt) # Now issue the file command. gdb.sendline('file %s' % exe) gdb.expect(gdb_prompt) # Send the disassemble command. gdb.sendline('disassemble %s' % func) gdb.expect(gdb_prompt) # Get the output from gdb. gdb_output = gdb.before # Use StringIO to record the memory dump as well as the gdb assembler code. mc_input = StringIO() # These keep track of the states of our simple gdb_output parser. prev_line = None prev_addr = None curr_addr = None addr_diff = 0 looking = False for line in gdb_output.split(os.linesep): if line.startswith('Dump of assembler code'): looking = True continue if line.startswith('End of assembler dump.'): looking = False prev_addr = curr_addr if mc_options and mc_options.find('arm') != -1: addr_diff = 4 if mc_options and mc_options.find('thumb') != -1: # It is obviously wrong to assume the last instruction of the # function has two bytes. # FIXME addr_diff = 2 if looking and line.startswith('0x'): # It's an assembler code dump. prev_addr = curr_addr curr_addr = line.split(None, 1)[0] if prev_addr and curr_addr: addr_diff = int(curr_addr, 16) - int(prev_addr, 16) if prev_addr and addr_diff > 0: # Feed the examining memory command to gdb. gdb.sendline('x /%db %s' % (addr_diff, prev_addr)) gdb.expect(gdb_prompt) x_output = gdb.before # Get the last output line from the gdb examine memory command, # split the string into a 3-tuple with separator '>:' to handle # objc method names. memory_dump = x_output.split(os.linesep)[-1].partition('>:')[2].strip() #print "\nbytes:", memory_dump disasm_str = prev_line.partition('>:')[2] print >> mc_input, '%s # %s' % (memory_dump, disasm_str) # We're done with the processing. Assign the current line to be prev_line. prev_line = line # Close the gdb session now that we are done with it. gdb.sendline('quit') gdb.expect(pexpect.EOF) gdb.close() # Write the memory dump into a file. with open('disasm-input.txt', 'w') as f: f.write(mc_input.getvalue()) mc_cmd = '%s -disassemble %s disasm-input.txt' % (mc, mc_options) print "\nExecuting command:", mc_cmd os.system(mc_cmd) # And invoke llvm-mc with the just recorded file. #mc = pexpect.spawn('%s -disassemble %s disasm-input.txt' % (mc, mc_options)) #mc.logfile_read = sys.stdout #print "mc:", mc #mc.close() def main(): # This is to set up the Python path to include the pexpect-2.4 dir. # Remember to update this when/if things change. scriptPath = sys.path[0] sys.path.append(os.path.join(scriptPath, os.pardir, os.pardir, 'test', 'pexpect-2.4')) parser = OptionParser(usage="""\ Run gdb to disassemble a function, feed the bytes to 'llvm-mc -disassemble' command, and display the disassembly result. Usage: %prog [options] """) parser.add_option('-C', '--gdb-command', type='string', action='append', metavar='COMMAND', default=[], dest='gdb_commands', help='Command(s) gdb executes after starting up (can be empty)') parser.add_option('-O', '--gdb-options', type='string', action='store', dest='gdb_options', help="""The options passed to 'gdb' command if specified.""") parser.add_option('-e', '--executable', type='string', action='store', dest='executable', help="""The executable to do disassembly on.""") parser.add_option('-f', '--function', type='string', action='store', dest='function', help="""The function name (could be an address to gdb) for disassembly.""") parser.add_option('-m', '--llvm-mc', type='string', action='store', dest='llvm_mc', help="""The llvm-mc executable full path, if specified. Otherwise, it must be present in your PATH environment.""") parser.add_option('-o', '--options', type='string', action='store', dest='llvm_mc_options', help="""The options passed to 'llvm-mc -disassemble' command if specified.""") opts, args = parser.parse_args() gdb_commands = opts.gdb_commands gdb_options = opts.gdb_options if not opts.executable: parser.print_help() sys.exit(1) executable = opts.executable if not opts.function: parser.print_help() sys.exit(1) function = opts.function llvm_mc = opts.llvm_mc if opts.llvm_mc else which('llvm-mc') if not llvm_mc: parser.print_help() sys.exit(1) # This is optional. For example: # --options='-triple=arm-apple-darwin -debug-only=arm-disassembler' llvm_mc_options = opts.llvm_mc_options # We have parsed the options. print "gdb commands:", gdb_commands print "gdb options:", gdb_options print "executable:", executable print "function:", function print "llvm-mc:", llvm_mc print "llvm-mc options:", llvm_mc_options do_llvm_mc_disassembly(gdb_commands, gdb_options, executable, function, llvm_mc, llvm_mc_options) if __name__ == '__main__': main()