1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
|
#!/usr/bin/env python3
"""
Run gdb to disassemble a function, feed the bytes to 'llvm-mc -disassemble' command,
and display the disassembly result.
"""
from __future__ import print_function
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 io 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('%s # %s' % (memory_dump, disasm_str), file=mc_input)
# 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()
|