199 lines
6.8 KiB
Python
199 lines
6.8 KiB
Python
|
#!/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()
|