Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
| 3 | """ |
| 4 | Run gdb to disassemble a function, feed the bytes to 'llvm-mc -disassemble' command, |
| 5 | and display the disassembly result. |
| 6 | |
| 7 | """ |
| 8 | |
| 9 | import os |
| 10 | import sys |
| 11 | from optparse import OptionParser |
| 12 | |
| 13 | def is_exe(fpath): |
| 14 | """Check whether fpath is an executable.""" |
| 15 | return os.path.isfile(fpath) and os.access(fpath, os.X_OK) |
| 16 | |
| 17 | def which(program): |
| 18 | """Find the full path to a program, or return None.""" |
| 19 | fpath, fname = os.path.split(program) |
| 20 | if fpath: |
| 21 | if is_exe(program): |
| 22 | return program |
| 23 | else: |
| 24 | for path in os.environ["PATH"].split(os.pathsep): |
| 25 | exe_file = os.path.join(path, program) |
| 26 | if is_exe(exe_file): |
| 27 | return exe_file |
| 28 | return None |
| 29 | |
Johnny Chen | c4a2344 | 2011-03-21 23:44:44 +0000 | [diff] [blame] | 30 | def do_llvm_mc_disassembly(gdb_commands, gdb_options, exe, func, mc, mc_options): |
Johnny Chen | 0251bc7 | 2011-03-17 19:05:34 +0000 | [diff] [blame] | 31 | from cStringIO import StringIO |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 32 | import pexpect |
| 33 | |
| 34 | gdb_prompt = "\r\n\(gdb\) " |
Johnny Chen | c4a2344 | 2011-03-21 23:44:44 +0000 | [diff] [blame] | 35 | gdb = pexpect.spawn(('gdb %s' % gdb_options) if gdb_options else 'gdb') |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 36 | # Turn on logging for what gdb sends back. |
| 37 | gdb.logfile_read = sys.stdout |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 38 | gdb.expect(gdb_prompt) |
Johnny Chen | c4a2344 | 2011-03-21 23:44:44 +0000 | [diff] [blame] | 39 | |
| 40 | # See if there any extra command(s) to execute before we issue the file command. |
| 41 | for cmd in gdb_commands: |
| 42 | gdb.sendline(cmd) |
| 43 | gdb.expect(gdb_prompt) |
| 44 | |
| 45 | # Now issue the file command. |
| 46 | gdb.sendline('file %s' % exe) |
| 47 | gdb.expect(gdb_prompt) |
| 48 | |
| 49 | # Send the disassemble command. |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 50 | gdb.sendline('disassemble %s' % func) |
| 51 | gdb.expect(gdb_prompt) |
| 52 | |
| 53 | # Get the output from gdb. |
| 54 | gdb_output = gdb.before |
| 55 | |
Johnny Chen | 0251bc7 | 2011-03-17 19:05:34 +0000 | [diff] [blame] | 56 | # Use StringIO to record the memory dump as well as the gdb assembler code. |
| 57 | mc_input = StringIO() |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 58 | |
| 59 | # These keep track of the states of our simple gdb_output parser. |
| 60 | prev_line = None |
| 61 | prev_addr = None |
| 62 | curr_addr = None |
| 63 | addr_diff = 0 |
| 64 | looking = False |
| 65 | for line in gdb_output.split(os.linesep): |
| 66 | if line.startswith('Dump of assembler code'): |
| 67 | looking = True |
| 68 | continue |
| 69 | |
| 70 | if line.startswith('End of assembler dump.'): |
| 71 | looking = False |
| 72 | prev_addr = curr_addr |
| 73 | if mc_options and mc_options.find('arm') != -1: |
| 74 | addr_diff = 4 |
| 75 | if mc_options and mc_options.find('thumb') != -1: |
| 76 | # It is obviously wrong to assume the last instruction of the |
| 77 | # function has two bytes. |
| 78 | # FIXME |
| 79 | addr_diff = 2 |
| 80 | |
| 81 | if looking and line.startswith('0x'): |
| 82 | # It's an assembler code dump. |
| 83 | prev_addr = curr_addr |
| 84 | curr_addr = line.split(None, 1)[0] |
| 85 | if prev_addr and curr_addr: |
| 86 | addr_diff = int(curr_addr, 16) - int(prev_addr, 16) |
| 87 | |
Johnny Chen | 0251bc7 | 2011-03-17 19:05:34 +0000 | [diff] [blame] | 88 | if prev_addr and addr_diff > 0: |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 89 | # Feed the examining memory command to gdb. |
| 90 | gdb.sendline('x /%db %s' % (addr_diff, prev_addr)) |
| 91 | gdb.expect(gdb_prompt) |
| 92 | x_output = gdb.before |
Johnny Chen | ce1eefd | 2011-03-19 01:24:25 +0000 | [diff] [blame] | 93 | # Get the last output line from the gdb examine memory command, |
| 94 | # split the string into a 3-tuple with separator '>:' to handle |
| 95 | # objc method names. |
| 96 | memory_dump = x_output.split(os.linesep)[-1].partition('>:')[2].strip() |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 97 | #print "\nbytes:", memory_dump |
Johnny Chen | ce1eefd | 2011-03-19 01:24:25 +0000 | [diff] [blame] | 98 | disasm_str = prev_line.partition('>:')[2] |
Johnny Chen | 0251bc7 | 2011-03-17 19:05:34 +0000 | [diff] [blame] | 99 | print >> mc_input, '%s # %s' % (memory_dump, disasm_str) |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 100 | |
| 101 | # We're done with the processing. Assign the current line to be prev_line. |
| 102 | prev_line = line |
| 103 | |
| 104 | # Close the gdb session now that we are done with it. |
| 105 | gdb.sendline('quit') |
| 106 | gdb.expect(pexpect.EOF) |
| 107 | gdb.close() |
| 108 | |
Johnny Chen | 0251bc7 | 2011-03-17 19:05:34 +0000 | [diff] [blame] | 109 | # Write the memory dump into a file. |
| 110 | with open('disasm-input.txt', 'w') as f: |
| 111 | f.write(mc_input.getvalue()) |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 112 | |
| 113 | mc_cmd = '%s -disassemble %s disasm-input.txt' % (mc, mc_options) |
| 114 | print "\nExecuting command:", mc_cmd |
| 115 | os.system(mc_cmd) |
| 116 | |
| 117 | # And invoke llvm-mc with the just recorded file. |
| 118 | #mc = pexpect.spawn('%s -disassemble %s disasm-input.txt' % (mc, mc_options)) |
| 119 | #mc.logfile_read = sys.stdout |
| 120 | #print "mc:", mc |
| 121 | #mc.close() |
| 122 | |
| 123 | |
| 124 | def main(): |
| 125 | # This is to set up the Python path to include the pexpect-2.4 dir. |
| 126 | # Remember to update this when/if things change. |
| 127 | scriptPath = sys.path[0] |
| 128 | sys.path.append(os.path.join(scriptPath, os.pardir, os.pardir, 'test', 'pexpect-2.4')) |
| 129 | |
| 130 | parser = OptionParser(usage="""\ |
| 131 | Run gdb to disassemble a function, feed the bytes to 'llvm-mc -disassemble' command, |
| 132 | and display the disassembly result. |
| 133 | |
| 134 | Usage: %prog [options] |
| 135 | """) |
Johnny Chen | c4a2344 | 2011-03-21 23:44:44 +0000 | [diff] [blame] | 136 | parser.add_option('-C', '--gdb-command', |
| 137 | type='string', action='append', metavar='COMMAND', |
| 138 | default=[], dest='gdb_commands', |
| 139 | help='Command(s) gdb executes after starting up (can be empty)') |
| 140 | parser.add_option('-O', '--gdb-options', |
| 141 | type='string', action='store', |
| 142 | dest='gdb_options', |
| 143 | help="""The options passed to 'gdb' command if specified.""") |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 144 | parser.add_option('-e', '--executable', |
| 145 | type='string', action='store', |
| 146 | dest='executable', |
| 147 | help="""The executable to do disassembly on.""") |
| 148 | parser.add_option('-f', '--function', |
| 149 | type='string', action='store', |
| 150 | dest='function', |
| 151 | help="""The function name (could be an address to gdb) for disassembly.""") |
| 152 | parser.add_option('-m', '--llvm-mc', |
| 153 | type='string', action='store', |
| 154 | dest='llvm_mc', |
| 155 | help="""The llvm-mc executable full path, if specified. |
| 156 | Otherwise, it must be present in your PATH environment.""") |
| 157 | |
| 158 | parser.add_option('-o', '--options', |
| 159 | type='string', action='store', |
| 160 | dest='llvm_mc_options', |
Johnny Chen | c4a2344 | 2011-03-21 23:44:44 +0000 | [diff] [blame] | 161 | help="""The options passed to 'llvm-mc -disassemble' command if specified.""") |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 162 | |
| 163 | opts, args = parser.parse_args() |
| 164 | |
Johnny Chen | c4a2344 | 2011-03-21 23:44:44 +0000 | [diff] [blame] | 165 | gdb_commands = opts.gdb_commands |
| 166 | gdb_options = opts.gdb_options |
| 167 | |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 168 | if not opts.executable: |
| 169 | parser.print_help() |
| 170 | sys.exit(1) |
| 171 | executable = opts.executable |
| 172 | |
| 173 | if not opts.function: |
| 174 | parser.print_help() |
| 175 | sys.exit(1) |
| 176 | function = opts.function |
| 177 | |
| 178 | llvm_mc = opts.llvm_mc if opts.llvm_mc else which('llvm-mc') |
| 179 | if not llvm_mc: |
| 180 | parser.print_help() |
| 181 | sys.exit(1) |
| 182 | |
| 183 | # This is optional. For example: |
| 184 | # --options='-triple=arm-apple-darwin -debug-only=arm-disassembler' |
| 185 | llvm_mc_options = opts.llvm_mc_options |
| 186 | |
| 187 | # We have parsed the options. |
Johnny Chen | c4a2344 | 2011-03-21 23:44:44 +0000 | [diff] [blame] | 188 | print "gdb commands:", gdb_commands |
| 189 | print "gdb options:", gdb_options |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 190 | print "executable:", executable |
| 191 | print "function:", function |
| 192 | print "llvm-mc:", llvm_mc |
| 193 | print "llvm-mc options:", llvm_mc_options |
| 194 | |
Johnny Chen | c4a2344 | 2011-03-21 23:44:44 +0000 | [diff] [blame] | 195 | do_llvm_mc_disassembly(gdb_commands, gdb_options, executable, function, llvm_mc, llvm_mc_options) |
Johnny Chen | 9c407f5 | 2011-03-17 00:59:57 +0000 | [diff] [blame] | 196 | |
| 197 | if __name__ == '__main__': |
| 198 | main() |