1 | #!/usr/bin/env python |
2 | |
3 | from __future__ import absolute_import, division, print_function |
4 | |
5 | import argparse |
6 | import difflib |
7 | import filecmp |
8 | import os |
9 | import subprocess |
10 | import sys |
11 | |
12 | disassembler = 'objdump' |
13 | |
14 | def keep_line(line): |
15 | """Returns true for lines that should be compared in the disassembly |
16 | output.""" |
17 | return "file format" not in line |
18 | |
19 | def disassemble(objfile): |
20 | """Disassemble object to a file.""" |
21 | p = subprocess.Popen([disassembler, '-d', objfile], |
22 | stdout=subprocess.PIPE, |
23 | stderr=subprocess.PIPE) |
24 | (out, err) = p.communicate() |
25 | if p.returncode or err: |
26 | print("Disassemble failed: {}".format(objfile)) |
27 | sys.exit(1) |
28 | return [line for line in out.split(os.linesep) if keep_line(line)] |
29 | |
30 | def dump_debug(objfile): |
31 | """Dump all of the debug info from a file.""" |
32 | p = subprocess.Popen([disassembler, '-WliaprmfsoRt', objfile], stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
33 | (out, err) = p.communicate() |
34 | if p.returncode or err: |
35 | print("Dump debug failed: {}".format(objfile)) |
36 | sys.exit(1) |
37 | return [line for line in out.split(os.linesep) if keep_line(line)] |
38 | |
39 | def first_diff(a, b, fromfile, tofile): |
40 | """Returns the first few lines of a difference, if there is one. Python |
41 | diff can be very slow with large objects and the most interesting changes |
42 | are the first ones. Truncate data before sending to difflib. Returns None |
43 | is there is no difference.""" |
44 | |
45 | # Find first diff |
46 | first_diff_idx = None |
47 | for idx, val in enumerate(a): |
48 | if val != b[idx]: |
49 | first_diff_idx = idx |
50 | break |
51 | |
52 | if first_diff_idx == None: |
53 | # No difference |
54 | return None |
55 | |
56 | # Diff to first line of diff plus some lines |
57 | context = 3 |
58 | diff = difflib.unified_diff(a[:first_diff_idx+context], |
59 | b[:first_diff_idx+context], |
60 | fromfile, |
61 | tofile) |
62 | difference = "\n".join(diff) |
63 | if first_diff_idx + context < len(a): |
64 | difference += "\n*** Diff truncated ***" |
65 | return difference |
66 | |
67 | def compare_object_files(objfilea, objfileb): |
68 | """Compare disassembly of two different files. |
69 | Allowing unavoidable differences, such as filenames. |
70 | Return the first difference if the disassembly differs, or None. |
71 | """ |
72 | disa = disassemble(objfilea) |
73 | disb = disassemble(objfileb) |
74 | return first_diff(disa, disb, objfilea, objfileb) |
75 | |
76 | def compare_debug_info(objfilea, objfileb): |
77 | """Compare debug info of two different files. |
78 | Allowing unavoidable differences, such as filenames. |
79 | Return the first difference if the debug info differs, or None. |
80 | If there are differences in the code, there will almost certainly be differences in the debug info too. |
81 | """ |
82 | dbga = dump_debug(objfilea) |
83 | dbgb = dump_debug(objfileb) |
84 | return first_diff(dbga, dbgb, objfilea, objfileb) |
85 | |
86 | def compare_exact(objfilea, objfileb): |
87 | """Byte for byte comparison between object files. |
88 | Returns True if equal, False otherwise. |
89 | """ |
90 | return filecmp.cmp(objfilea, objfileb) |
91 | |
92 | if __name__ == '__main__': |
93 | parser = argparse.ArgumentParser() |
94 | parser.add_argument('objfilea', nargs=1) |
95 | parser.add_argument('objfileb', nargs=1) |
96 | parser.add_argument('-v', '--verbose', action='store_true') |
97 | args = parser.parse_args() |
98 | diff = compare_object_files(args.objfilea[0], args.objfileb[0]) |
99 | if diff: |
100 | print("Difference detected") |
101 | if args.verbose: |
102 | print(diff) |
103 | sys.exit(1) |
104 | else: |
105 | print("The same") |
106 | |