1#!/usr/bin/env python
2
3from __future__ import absolute_import, division, print_function
4
5import argparse
6import difflib
7import filecmp
8import os
9import subprocess
10import sys
11
12disassembler = 'objdump'
13
14def 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
19def 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
30def 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
39def 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
67def 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
76def 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
86def 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
92if __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