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