| #!/usr/bin/env python |
| |
| from __future__ import absolute_import, division, print_function |
| |
| import argparse |
| import difflib |
| import filecmp |
| import os |
| import subprocess |
| import sys |
| |
| disassembler = 'objdump' |
| |
| def keep_line(line): |
| """Returns true for lines that should be compared in the disassembly |
| output.""" |
| return "file format" not in line |
| |
| def disassemble(objfile): |
| """Disassemble object to a file.""" |
| p = subprocess.Popen([disassembler, '-d', objfile], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE) |
| (out, err) = p.communicate() |
| if p.returncode or err: |
| print("Disassemble failed: {}".format(objfile)) |
| sys.exit(1) |
| return [line for line in out.split(os.linesep) if keep_line(line)] |
| |
| def dump_debug(objfile): |
| """Dump all of the debug info from a file.""" |
| p = subprocess.Popen([disassembler, '-WliaprmfsoRt', objfile], stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| (out, err) = p.communicate() |
| if p.returncode or err: |
| print("Dump debug failed: {}".format(objfile)) |
| sys.exit(1) |
| return [line for line in out.split(os.linesep) if keep_line(line)] |
| |
| def first_diff(a, b, fromfile, tofile): |
| """Returns the first few lines of a difference, if there is one. Python |
| diff can be very slow with large objects and the most interesting changes |
| are the first ones. Truncate data before sending to difflib. Returns None |
| is there is no difference.""" |
| |
| # Find first diff |
| first_diff_idx = None |
| for idx, val in enumerate(a): |
| if val != b[idx]: |
| first_diff_idx = idx |
| break |
| |
| if first_diff_idx == None: |
| # No difference |
| return None |
| |
| # Diff to first line of diff plus some lines |
| context = 3 |
| diff = difflib.unified_diff(a[:first_diff_idx+context], |
| b[:first_diff_idx+context], |
| fromfile, |
| tofile) |
| difference = "\n".join(diff) |
| if first_diff_idx + context < len(a): |
| difference += "\n*** Diff truncated ***" |
| return difference |
| |
| def compare_object_files(objfilea, objfileb): |
| """Compare disassembly of two different files. |
| Allowing unavoidable differences, such as filenames. |
| Return the first difference if the disassembly differs, or None. |
| """ |
| disa = disassemble(objfilea) |
| disb = disassemble(objfileb) |
| return first_diff(disa, disb, objfilea, objfileb) |
| |
| def compare_debug_info(objfilea, objfileb): |
| """Compare debug info of two different files. |
| Allowing unavoidable differences, such as filenames. |
| Return the first difference if the debug info differs, or None. |
| If there are differences in the code, there will almost certainly be differences in the debug info too. |
| """ |
| dbga = dump_debug(objfilea) |
| dbgb = dump_debug(objfileb) |
| return first_diff(dbga, dbgb, objfilea, objfileb) |
| |
| def compare_exact(objfilea, objfileb): |
| """Byte for byte comparison between object files. |
| Returns True if equal, False otherwise. |
| """ |
| return filecmp.cmp(objfilea, objfileb) |
| |
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser() |
| parser.add_argument('objfilea', nargs=1) |
| parser.add_argument('objfileb', nargs=1) |
| parser.add_argument('-v', '--verbose', action='store_true') |
| args = parser.parse_args() |
| diff = compare_object_files(args.objfilea[0], args.objfileb[0]) |
| if diff: |
| print("Difference detected") |
| if args.verbose: |
| print(diff) |
| sys.exit(1) |
| else: |
| print("The same") |