diff options
| author | Random <28494085+Random06457@users.noreply.github.com> | 2020-06-05 19:18:39 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-06-05 13:18:39 -0400 |
| commit | 94d810193a8b74e14766a3227812fab50f8df492 (patch) | |
| tree | 929a3b5c14597fd861b5de8bf8c4c2675be7b22c /diff.py | |
| parent | e67c51b155f2fe189d643929bc4cb155565fad3f (diff) | |
ucode_disas.c progress (#188)
* ucode_disas.c progress
* fix
* minor fixes
* minor comment changes
Diffstat (limited to 'diff.py')
| -rwxr-xr-x | diff.py | 527 |
1 files changed, 337 insertions, 190 deletions
@@ -12,12 +12,16 @@ import threading import queue import time + def fail(msg): print(msg, file=sys.stderr) sys.exit(1) -MISSING_PREREQUISITES = "Missing prerequisite python module {}. " \ + +MISSING_PREREQUISITES = ( + "Missing prerequisite python module {}. " "Run `python3 -m pip install --user colorama ansiwrap attrs watchdog python-Levenshtein` to install prerequisites (python-Levenshtein only needed for --algorithm=levenshtein)." +) try: import attr @@ -28,7 +32,7 @@ except ModuleNotFoundError as e: fail(MISSING_PREREQUISITES.format(e.name)) # Prefer to use diff_settings.py from the current working directory -sys.path.insert(0, '.') +sys.path.insert(0, ".") try: import diff_settings except ModuleNotFoundError: @@ -36,40 +40,96 @@ except ModuleNotFoundError: # ==== CONFIG ==== -parser = argparse.ArgumentParser( - description="Diff MIPS assembly.") -parser.add_argument('start', - help="Function name or address to start diffing from.") -parser.add_argument('end', nargs='?', - help="Address to end diff at.") -parser.add_argument('-o', dest='diff_obj', action='store_true', - help="Diff .o files rather than a whole binary. This makes it possible to see symbol names. (Recommended)") -parser.add_argument('--base-asm', dest='base_asm', metavar='FILE', - help="Read assembly from given file instead of configured base img.") -parser.add_argument('--write-asm', dest='write_asm', metavar='FILE', - help="Write the current assembly output to file, e.g. for use with --base-asm.") -parser.add_argument('-m', '--make', dest='make', action='store_true', - help="Automatically run 'make' on the .o file or binary before diffing.") -parser.add_argument('-l', '--skip-lines', dest='skip_lines', type=int, default=0, - help="Skip the first N lines of output.") -parser.add_argument('-s', '--stop-jr-ra', dest='stop_jrra', action='store_true', - help="Stop disassembling at the first 'jr ra'. Some functions have multiple return points, so use with care!") -parser.add_argument('-i', '--ignore-large-imms', dest='ignore_large_imms', action='store_true', - help="Pretend all large enough immediates are the same.") -parser.add_argument('-B', '--no-show-branches', dest='show_branches', action='store_false', - help="Don't visualize branches/branch targets.") -parser.add_argument('-S', '--base-shift', dest='base_shift', type=str, default='0', - help="Diff position X in our img against position X + shift in the base img. " - "Arithmetic is allowed, so e.g. |-S \"0x1234 - 0x4321\"| is a reasonable " - "flag to pass if it is known that position 0x1234 in the base img syncs " - "up with position 0x4321 in our img. Not supported together with -o.") -parser.add_argument('-w', '--watch', dest='watch', action='store_true', - help="Automatically update when source/object files change. " - "Recommended in combination with -m.") -parser.add_argument('--width', dest='column_width', type=int, default=50, - help="Sets the width of the left and right view column.") -parser.add_argument('--algorithm', dest='algorithm', default='difflib', - choices=['levenshtein', 'difflib'], help="Diff algorithm to use.") +parser = argparse.ArgumentParser(description="Diff MIPS assembly.") +parser.add_argument("start", help="Function name or address to start diffing from.") +parser.add_argument("end", nargs="?", help="Address to end diff at.") +parser.add_argument( + "-o", + dest="diff_obj", + action="store_true", + help="Diff .o files rather than a whole binary. This makes it possible to see symbol names. (Recommended)", +) +parser.add_argument( + "--base-asm", + dest="base_asm", + metavar="FILE", + help="Read assembly from given file instead of configured base img.", +) +parser.add_argument( + "--write-asm", + dest="write_asm", + metavar="FILE", + help="Write the current assembly output to file, e.g. for use with --base-asm.", +) +parser.add_argument( + "-m", + "--make", + dest="make", + action="store_true", + help="Automatically run 'make' on the .o file or binary before diffing.", +) +parser.add_argument( + "-l", + "--skip-lines", + dest="skip_lines", + type=int, + default=0, + help="Skip the first N lines of output.", +) +parser.add_argument( + "-s", + "--stop-jr-ra", + dest="stop_jrra", + action="store_true", + help="Stop disassembling at the first 'jr ra'. Some functions have multiple return points, so use with care!", +) +parser.add_argument( + "-i", + "--ignore-large-imms", + dest="ignore_large_imms", + action="store_true", + help="Pretend all large enough immediates are the same.", +) +parser.add_argument( + "-B", + "--no-show-branches", + dest="show_branches", + action="store_false", + help="Don't visualize branches/branch targets.", +) +parser.add_argument( + "-S", + "--base-shift", + dest="base_shift", + type=str, + default="0", + help="Diff position X in our img against position X + shift in the base img. " + 'Arithmetic is allowed, so e.g. |-S "0x1234 - 0x4321"| is a reasonable ' + "flag to pass if it is known that position 0x1234 in the base img syncs " + "up with position 0x4321 in our img. Not supported together with -o.", +) +parser.add_argument( + "-w", + "--watch", + dest="watch", + action="store_true", + help="Automatically update when source/object files change. " + "Recommended in combination with -m.", +) +parser.add_argument( + "--width", + dest="column_width", + type=int, + default=50, + help="Sets the width of the left and right view column.", +) +parser.add_argument( + "--algorithm", + dest="algorithm", + default="difflib", + choices=["levenshtein", "difflib"], + help="Diff algorithm to use.", +) # Project-specific flags, e.g. different versions/make arguments. if hasattr(diff_settings, "add_custom_arguments"): @@ -81,14 +141,14 @@ args = parser.parse_args() config = {} diff_settings.apply(config, args) -baseimg = config.get('baseimg', None) -myimg = config.get('myimg', None) -mapfile = config.get('mapfile', None) -makeflags = config.get('makeflags', []) -source_directories = config.get('source_directories', None) +baseimg = config.get("baseimg", None) +myimg = config.get("myimg", None) +mapfile = config.get("mapfile", None) +makeflags = config.get("makeflags", []) +source_directories = config.get("source_directories", None) -MAX_FUNCTION_SIZE_LINES = 1024 -MAX_FUNCTION_SIZE_BYTES = 1024 * 4 +MAX_FUNCTION_SIZE_LINES = 4096 +MAX_FUNCTION_SIZE_BYTES = MAX_FUNCTION_SIZE_LINES * 4 COLOR_ROTATION = [ Fore.MAGENTA, @@ -102,15 +162,15 @@ COLOR_ROTATION = [ Fore.LIGHTBLACK_EX, ] -BUFFER_CMD = ["tail", "-c", str(10**9)] +BUFFER_CMD = ["tail", "-c", str(10 ** 9)] LESS_CMD = ["less", "-Ric"] DEBOUNCE_DELAY = 0.1 -FS_WATCH_EXTENSIONS = ['.c', '.h'] +FS_WATCH_EXTENSIONS = [".c", ".h"] # ==== LOGIC ==== -if args.algorithm == 'levenshtein': +if args.algorithm == "levenshtein": try: import Levenshtein except ModuleNotFoundError as e: @@ -118,9 +178,13 @@ if args.algorithm == 'levenshtein': binutils_prefix = None -for binutils_cand in ['mips-linux-gnu-', 'mips64-elf-']: +for binutils_cand in ["mips-linux-gnu-", "mips64-elf-"]: try: - subprocess.check_call([binutils_cand + "objdump", "--version"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.check_call( + [binutils_cand + "objdump", "--version"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) binutils_prefix = binutils_cand break except subprocess.CalledProcessError: @@ -129,7 +193,10 @@ for binutils_cand in ['mips-linux-gnu-', 'mips64-elf-']: pass if not binutils_prefix: - fail("Missing binutils; please ensure mips-linux-gnu-objdump or mips64-elf-objdump exist.") + fail( + "Missing binutils; please ensure mips-linux-gnu-objdump or mips64-elf-objdump exist." + ) + def eval_int(expr, emsg=None): try: @@ -142,33 +209,46 @@ def eval_int(expr, emsg=None): fail(emsg) return None + def run_make(target, capture_output=False): if capture_output: - return subprocess.run(["make"] + makeflags + [target], stderr=subprocess.PIPE, stdout=subprocess.PIPE) + return subprocess.run( + ["make"] + makeflags + [target], + stderr=subprocess.PIPE, + stdout=subprocess.PIPE, + ) else: subprocess.check_call(["make"] + makeflags + [target]) + def restrict_to_function(dump, fn_name): out = [] - search = f'<{fn_name}>:' + search = f"<{fn_name}>:" found = False - for line in dump.split('\n'): + for line in dump.split("\n"): if found: if len(out) >= MAX_FUNCTION_SIZE_LINES: break out.append(line) elif search in line: found = True - return '\n'.join(out) + return "\n".join(out) + def run_objdump(cmd): flags, target, restrict = cmd - out = subprocess.check_output([binutils_prefix + "objdump"] + flags + [target], universal_newlines=True) + out = subprocess.check_output( + [binutils_prefix + "objdump"] + flags + [target], universal_newlines=True + ) if restrict is not None: return restrict_to_function(out, restrict) return out -base_shift = eval_int(args.base_shift, "Failed to parse --base-shift (-S) argument as an integer.") + +base_shift = eval_int( + args.base_shift, "Failed to parse --base-shift (-S) argument as an integer." +) + def search_map_file(fn_name): if not mapfile: @@ -176,7 +256,7 @@ def search_map_file(fn_name): try: with open(mapfile) as f: - lines = f.read().split('\n') + lines = f.read().split("\n") except Exception: fail(f"Failed to open map file {mapfile} for reading.") @@ -184,22 +264,23 @@ def search_map_file(fn_name): cur_objfile = None ram_to_rom = None cands = [] - last_line = '' + last_line = "" for line in lines: - if line.startswith(' .text'): + if line.startswith(" .text"): cur_objfile = line.split()[3] - if 'load address' in line: + if "load address" in line: tokens = last_line.split() + line.split() ram = int(tokens[1], 0) rom = int(tokens[5], 0) ram_to_rom = rom - ram - if line.endswith(' ' + fn_name): + if line.endswith(" " + fn_name): ram = int(line.split()[0], 0) if cur_objfile is not None and ram_to_rom is not None: cands.append((cur_objfile, ram + ram_to_rom)) last_line = line except Exception as e: import traceback + traceback.print_exc() fail(f"Internal error while parsing map file") @@ -209,12 +290,13 @@ def search_map_file(fn_name): return cands[0] return None, None + def dump_objfile(): if base_shift: fail("--base-shift not compatible with -o") if args.end is not None: fail("end address not supported together with -o") - if args.start.startswith('0'): + if args.start.startswith("0"): fail("numerical start address not supported with -o; pass a function name") objfile, _ = search_map_file(args.start) @@ -235,9 +317,10 @@ def dump_objfile(): return ( objfile, (objdump_flags, refobjfile, args.start), - (objdump_flags, objfile, args.start) + (objdump_flags, objfile, args.start), ) + def dump_binary(): if not baseimg or not myimg: fail("Missing myimg/baseimg in config.") @@ -252,41 +335,52 @@ def dump_binary(): end_addr = eval_int(args.end, "End address must be an integer expression.") else: end_addr = start_addr + MAX_FUNCTION_SIZE_BYTES - objdump_flags = ['-Dz', '-bbinary', '-mmips', '-EB'] - flags1 = [f"--start-address={start_addr + base_shift}", f"--stop-address={end_addr + base_shift}"] + objdump_flags = ["-Dz", "-bbinary", "-mmips", "-EB"] + flags1 = [ + f"--start-address={start_addr + base_shift}", + f"--stop-address={end_addr + base_shift}", + ] flags2 = [f"--start-address={start_addr}", f"--stop-address={end_addr}"] return ( myimg, (objdump_flags + flags1, baseimg, None), - (objdump_flags + flags2, myimg, None) + (objdump_flags + flags2, myimg, None), ) + # Alignment with ANSI colors is broken, let's fix it. def ansi_ljust(s, width): needed = width - ansiwrap.ansilen(s) if needed > 0: - return s + ' ' * needed + return s + " " * needed else: return s -re_int = re.compile(r'[0-9]+') -re_comments = re.compile(r'<.*?>') -re_regs = re.compile(r'\$?\b(a[0-3]|t[0-9]|s[0-7]|at|v[01]|f[12]?[0-9]|f3[01]|fp)\b') -re_sprel = re.compile(r',([1-9][0-9]*|0x[1-9a-f][0-9a-f]*)\(sp\)') -re_large_imm = re.compile(r'-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}') -re_imm = re.compile(r'(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(sp)|%(lo|hi)\([^)]*\)') -forbidden = set(string.ascii_letters + '_') + +re_int = re.compile(r"[0-9]+") +re_comments = re.compile(r"<.*?>") +re_regs = re.compile(r"\$?\b(a[0-3]|t[0-9]|s[0-8]|at|v[01]|f[12]?[0-9]|f3[01]|fp)\b") +re_sprel = re.compile(r",([1-9][0-9]*|0x[1-9a-f][0-9a-f]*)\(sp\)") +re_large_imm = re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}") +re_imm = re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(sp)|%(lo|hi)\([^)]*\)") +forbidden = set(string.ascii_letters + "_") branch_likely_instructions = { - 'beql', 'bnel', 'beqzl', 'bnezl', 'bgezl', 'bgtzl', 'blezl', 'bltzl', - 'bc1tl', 'bc1fl' + "beql", + "bnel", + "beqzl", + "bnezl", + "bgezl", + "bgtzl", + "blezl", + "bltzl", + "bc1tl", + "bc1fl", } -branch_instructions = branch_likely_instructions.union({ - 'b', 'beq', 'bne', 'beqz', 'bnez', 'bgez', 'bgtz', 'blez', 'bltz', - 'bc1t', 'bc1f' -}) -jump_instructions = branch_instructions.union({ - 'jal', 'j' -}) +branch_instructions = branch_likely_instructions.union( + {"b", "beq", "bne", "beqz", "bnez", "bgez", "bgtz", "blez", "bltz", "bc1t", "bc1f"} +) +jump_instructions = branch_instructions.union({"jal", "j"}) + def hexify_int(row, pat): full = pat.group(0) @@ -300,40 +394,43 @@ def hexify_int(row, pat): return full return hex(int(full)) + def parse_relocated_line(line): try: - ind2 = line.rindex(',') + ind2 = line.rindex(",") except ValueError: - ind2 = line.rindex('\t') - before = line[:ind2+1] - after = line[ind2+1:] - ind2 = after.find('(') + ind2 = line.rindex("\t") + before = line[: ind2 + 1] + after = line[ind2 + 1 :] + ind2 = after.find("(") if ind2 == -1: - imm, after = after, '' + imm, after = after, "" else: imm, after = after[:ind2], after[ind2:] - if imm == '0x0': - imm = '0' + if imm == "0x0": + imm = "0" return before, imm, after + def process_reloc(row, prev): before, imm, after = parse_relocated_line(prev) repl = row.split()[-1] - if imm != '0': - if before.strip() == 'jal' and not imm.startswith('0x'): - imm = '0x' + imm - repl += '+' + imm if int(imm,0) > 0 else imm - if 'R_MIPS_LO16' in row: - repl = f'%lo({repl})' - elif 'R_MIPS_HI16' in row: + if imm != "0": + if before.strip() == "jal" and not imm.startswith("0x"): + imm = "0x" + imm + repl += "+" + imm if int(imm, 0) > 0 else imm + if "R_MIPS_LO16" in row: + repl = f"%lo({repl})" + elif "R_MIPS_HI16" in row: # Ideally we'd pair up R_MIPS_LO16 and R_MIPS_HI16 to generate a # correct addend for each, but objdump doesn't give us the order of # the relocations, so we can't find the right LO16. :( - repl = f'%hi({repl})' + repl = f"%hi({repl})" else: - assert 'R_MIPS_26' in row, f"unknown relocation type '{row}'" + assert "R_MIPS_26" in row, f"unknown relocation type '{row}'" return before + repl + after + def process(lines): mnemonics = [] diff_rows = [] @@ -348,41 +445,41 @@ def process(lines): lines.pop() for row in lines: - if args.diff_obj and ('>:' in row or not row): + if args.diff_obj and (">:" in row or not row): continue - if 'R_MIPS_' in row: + if "R_MIPS_" in row: # N.B. Don't transform the diff rows, they already ignore immediates # if diff_rows[-1] != '<delay-slot>': - # diff_rows[-1] = process_reloc(row, rows_with_imms[-1]) + # diff_rows[-1] = process_reloc(row, rows_with_imms[-1]) originals[-1] = process_reloc(row, originals[-1]) continue - row = re.sub(re_comments, '', row) + row = re.sub(re_comments, "", row) row = row.rstrip() - tabs = row.split('\t') - row = '\t'.join(tabs[2:]) + tabs = row.split("\t") + row = "\t".join(tabs[2:]) line_num = tabs[0].strip() - row_parts = row.split('\t', 1) + row_parts = row.split("\t", 1) mnemonic = row_parts[0].strip() if mnemonic not in jump_instructions: row = re.sub(re_int, lambda s: hexify_int(row, s), row) original = row if skip_next: skip_next = False - row = '<delay-slot>' - mnemonic = '<delay-slot>' + row = "<delay-slot>" + mnemonic = "<delay-slot>" if mnemonic in branch_likely_instructions: skip_next = True - row = re.sub(re_regs, '<reg>', row) - row = re.sub(re_sprel, ',addr(sp)', row) + row = re.sub(re_regs, "<reg>", row) + row = re.sub(re_sprel, ",addr(sp)", row) row_with_imm = row if mnemonic in jump_instructions: row = row.strip() row, _ = split_off_branch(row) - row += '<imm>' + row += "<imm>" else: - row = re.sub(re_imm, '<imm>', row) + row = re.sub(re_imm, "<imm>", row) mnemonics.append(mnemonic) rows_with_imms.append(row_with_imm) @@ -390,24 +487,28 @@ def process(lines): originals.append(original) line_nums.append(line_num) if mnemonic in branch_instructions: - target = row_parts[1].strip().split(',')[-1] + target = row_parts[1].strip().split(",")[-1] if mnemonic in branch_likely_instructions: target = hex(int(target, 16) - 4)[2:] branch_targets.append(target) else: branch_targets.append(None) - if args.stop_jrra and mnemonic == 'jr' and row_parts[1].strip() == 'ra': + if args.stop_jrra and mnemonic == "jr" and row_parts[1].strip() == "ra": break # Cleanup whitespace originals = [original.strip() for original in originals] - originals = [''.join(f'{o:<8s}' for o in original.split('\t')) for original in originals] + originals = [ + "".join(f"{o:<8s}" for o in original.split("\t")) for original in originals + ] # return diff_rows, diff_rows, line_nums return mnemonics, diff_rows, originals, line_nums, branch_targets + def format_single_line_diff(line1, line2, column_width): return f"{ansi_ljust(line1,column_width)}{ansi_ljust(line2,column_width)}" + class SymbolColorer: def __init__(self, base_index): self.color_index = base_index @@ -421,23 +522,27 @@ class SymbolColorer: self.color_index += 1 self.symbol_colors[s] = color t = t or s - return f'{color}{t}{Fore.RESET}' + return f"{color}{t}{Fore.RESET}" + def maybe_normalize_large_imms(row): if args.ignore_large_imms: - row = re.sub(re_large_imm, '<imm>', row) + row = re.sub(re_large_imm, "<imm>", row) return row + def normalize_imms(row): - return re.sub(re_imm, '<imm>', row) + return re.sub(re_imm, "<imm>", row) + def split_off_branch(line): - parts = line.split(',') + parts = line.split(",") if len(parts) < 2: parts = line.split() off = len(line) - len(parts[-1]) return line[:off], line[off:] + def color_imms(out1, out2): g1 = [] g2 = [] @@ -446,31 +551,40 @@ def color_imms(out1, out2): if len(g1) == len(g2): diffs = [x != y for (x, y) in zip(g1, g2)] it = iter(diffs) + def maybe_color(s): - return f'{Fore.LIGHTBLUE_EX}{s}{Style.RESET_ALL}' if next(it) else s + return f"{Fore.LIGHTBLUE_EX}{s}{Style.RESET_ALL}" if next(it) else s + out1 = re.sub(re_imm, lambda s: maybe_color(s.group()), out1) it = iter(diffs) out2 = re.sub(re_imm, lambda s: maybe_color(s.group()), out2) return out1, out2 + def color_branch_imms(br1, br2): if br1 != br2: - br1 = f'{Fore.LIGHTBLUE_EX}{br1}{Style.RESET_ALL}' - br2 = f'{Fore.LIGHTBLUE_EX}{br2}{Style.RESET_ALL}' + br1 = f"{Fore.LIGHTBLUE_EX}{br1}{Style.RESET_ALL}" + br2 = f"{Fore.LIGHTBLUE_EX}{br2}{Style.RESET_ALL}" return br1, br2 + def diff_sequences_difflib(seq1, seq2): differ = difflib.SequenceMatcher(a=seq1, b=seq2, autojunk=False) return differ.get_opcodes() + def diff_sequences(seq1, seq2): - if (args.algorithm != 'levenshtein' or len(seq1) * len(seq2) > 4 * 10**8 or - len(seq1) + len(seq2) >= 0x110000): + if ( + args.algorithm != "levenshtein" + or len(seq1) * len(seq2) > 4 * 10 ** 8 + or len(seq1) + len(seq2) >= 0x110000 + ): return diff_sequences_difflib(seq1, seq2) # The Levenshtein library assumes that we compare strings, not lists. Convert. # (Per the check above we know we have fewer than 0x110000 unique elements, so chr() works.) remapping = {} + def remap(seq): seq = seq[:] for i in range(len(seq)): @@ -479,23 +593,28 @@ def diff_sequences(seq1, seq2): val = chr(len(remapping)) remapping[seq[i]] = val seq[i] = val - return ''.join(seq) + return "".join(seq) seq1 = remap(seq1) seq2 = remap(seq2) return Levenshtein.opcodes(seq1, seq2) + def do_diff(basedump, mydump): - asm_lines1 = basedump.split('\n') - asm_lines2 = mydump.split('\n') + asm_lines1 = basedump.split("\n") + asm_lines2 = mydump.split("\n") output = [] # TODO: status line? # output.append(sha1sum(mydump)) - mnemonics1, asm_lines1, originals1, line_nums1, branch_targets1 = process(asm_lines1) - mnemonics2, asm_lines2, originals2, line_nums2, branch_targets2 = process(asm_lines2) + mnemonics1, asm_lines1, originals1, line_nums1, branch_targets1 = process( + asm_lines1 + ) + mnemonics2, asm_lines2, originals2, line_nums2, branch_targets2 = process( + asm_lines2 + ) sc1 = SymbolColorer(0) sc2 = SymbolColorer(0) @@ -507,7 +626,10 @@ def do_diff(basedump, mydump): bts2 = set() if args.show_branches: - for (bts, btset, sc) in [(branch_targets1, bts1, sc5), (branch_targets2, bts2, sc6)]: + for (bts, btset, sc) in [ + (branch_targets1, bts1, sc5), + (branch_targets2, bts2, sc6), + ]: for bt in bts: if bt is not None: btset.add(bt + ":") @@ -518,38 +640,40 @@ def do_diff(basedump, mydump): lines2 = asm_lines2[j1:j2] for k, (line1, line2) in enumerate(itertools.zip_longest(lines1, lines2)): - if tag == 'replace': + if tag == "replace": if line1 is None: - tag = 'insert' + tag = "insert" elif line2 is None: - tag = 'delete' + tag = "delete" try: - original1 = originals1[i1+k] - line_num1 = line_nums1[i1+k] + original1 = originals1[i1 + k] + line_num1 = line_nums1[i1 + k] except: - original1 = '' - line_num1 = '' + original1 = "" + line_num1 = "" try: - original2 = originals2[j1+k] - line_num2 = line_nums2[j1+k] + original2 = originals2[j1 + k] + line_num2 = line_nums2[j1 + k] except: - original2 = '' - line_num2 = '' + original2 = "" + line_num2 = "" line_color1 = line_color2 = sym_color = Fore.RESET - line_prefix = ' ' + line_prefix = " " if line1 == line2: - if maybe_normalize_large_imms(original1) == maybe_normalize_large_imms(original2): - out1 = f'{original1}' - out2 = f'{original2}' - elif line1 == '<delay-slot>': - out1 = f'{Style.DIM}{original1}' - out2 = f'{Style.DIM}{original2}' + if maybe_normalize_large_imms(original1) == maybe_normalize_large_imms( + original2 + ): + out1 = f"{original1}" + out2 = f"{original2}" + elif line1 == "<delay-slot>": + out1 = f"{Style.DIM}{original1}" + out2 = f"{Style.DIM}{original2}" else: mnemonic = original1.split()[0] out1, out2 = original1, original2 - branch1 = branch2 = '' + branch1 = branch2 = "" if mnemonic in jump_instructions: out1, branch1 = split_off_branch(original1) out2, branch2 = split_off_branch(original2) @@ -562,60 +686,72 @@ def do_diff(basedump, mydump): if normalize_imms(branchless1) == normalize_imms(branchless2): # only imms differences sym_color = Fore.LIGHTBLUE_EX - line_prefix = 'i' + line_prefix = "i" else: # regs differences and maybe imms as well line_color1 = line_color2 = sym_color = Fore.YELLOW - line_prefix = 'r' - out1 = re.sub(re_regs, lambda s: sc1.color_symbol(s.group()), out1) - out2 = re.sub(re_regs, lambda s: sc2.color_symbol(s.group()), out2) - out1 = re.sub(re_sprel, lambda s: sc3.color_symbol(s.group()), out1) - out2 = re.sub(re_sprel, lambda s: sc4.color_symbol(s.group()), out2) - out1 = f'{Fore.YELLOW}{out1}{Style.RESET_ALL}' - out2 = f'{Fore.YELLOW}{out2}{Style.RESET_ALL}' - elif tag in ['replace', 'equal']: - line_prefix = '|' + line_prefix = "r" + out1 = re.sub( + re_regs, lambda s: sc1.color_symbol(s.group()), out1 + ) + out2 = re.sub( + re_regs, lambda s: sc2.color_symbol(s.group()), out2 + ) + out1 = re.sub( + re_sprel, lambda s: sc3.color_symbol(s.group()), out1 + ) + out2 = re.sub( + re_sprel, lambda s: sc4.color_symbol(s.group()), out2 + ) + out1 = f"{Fore.YELLOW}{out1}{Style.RESET_ALL}" + out2 = f"{Fore.YELLOW}{out2}{Style.RESET_ALL}" + elif tag in ["replace", "equal"]: + line_prefix = "|" line_color1 = Fore.LIGHTBLUE_EX line_color2 = Fore.LIGHTBLUE_EX sym_color = Fore.LIGHTBLUE_EX out1 = f"{Fore.LIGHTBLUE_EX}{original1}{Style.RESET_ALL}" out2 = f"{Fore.LIGHTBLUE_EX}{original2}{Style.RESET_ALL}" - elif tag == 'delete': - line_prefix = '<' + elif tag == "delete": + line_prefix = "<" line_color1 = line_color2 = sym_color = Fore.RED out1 = f"{Fore.RED}{original1}{Style.RESET_ALL}" - out2 = '' - elif tag == 'insert': - line_prefix = '>' + out2 = "" + elif tag == "insert": + line_prefix = ">" line_color1 = line_color2 = sym_color = Fore.GREEN - out1 = '' + out1 = "" out2 = f"{Fore.GREEN}{original2}{Style.RESET_ALL}" - in_arrow1 = ' ' - in_arrow2 = ' ' - out_arrow1 = '' - out_arrow2 = '' - line_num1 = line_num1 if out1 else '' - line_num2 = line_num2 if out2 else '' + in_arrow1 = " " + in_arrow2 = " " + out_arrow1 = "" + out_arrow2 = "" + line_num1 = line_num1 if out1 else "" + line_num2 = line_num2 if out2 else "" if args.show_branches and out1: if line_num1 in bts1: - in_arrow1 = sc5.color_symbol(line_num1, '~>') - if branch_targets1[i1+k] is not None: - out_arrow1 = ' ' + sc5.color_symbol(branch_targets1[i1+k] + ":", '~>') + in_arrow1 = sc5.color_symbol(line_num1, "~>") + if branch_targets1[i1 + k] is not None: + out_arrow1 = " " + sc5.color_symbol( + branch_targets1[i1 + k] + ":", "~>" + ) if args.show_branches and out2: if line_num2 in bts2: - in_arrow2 = sc6.color_symbol(line_num2, '~>') - if branch_targets2[j1+k] is not None: - out_arrow2 = ' ' + sc6.color_symbol(branch_targets2[j1+k] + ":", '~>') + in_arrow2 = sc6.color_symbol(line_num2, "~>") + if branch_targets2[j1 + k] is not None: + out_arrow2 = " " + sc6.color_symbol( + branch_targets2[j1 + k] + ":", "~>" + ) if sym_color == line_color2: - line_color2 = '' - out1 = f"{line_color1}{line_num1} {in_arrow1} {out1}{Style.RESET_ALL}{out_arrow1}" + line_color2 = "" + out1 = f"{line_color1}{line_num1} {in_arrow1} {out1}{Style.RESET_ALL}{out_arrow1}" out2 = f"{sym_color}{line_prefix} {line_color2}{line_num2} {in_arrow2} {out2}{Style.RESET_ALL}{out_arrow2}" output.append(format_single_line_diff(out1, out2, args.column_width)) - return output[args.skip_lines:] + return output[args.skip_lines :] def debounced_fs_watch(targets, outq, debounce_delay): @@ -639,7 +775,9 @@ def debounced_fs_watch(targets, outq, debounce_delay): for target in self.file_targets: if path == target: return True - if args.make and any(path.endswith(suffix) for suffix in FS_WATCH_EXTENSIONS): + if args.make and any( + path.endswith(suffix) for suffix in FS_WATCH_EXTENSIONS + ): return True return False @@ -658,7 +796,7 @@ def debounced_fs_watch(targets, outq, debounce_delay): observer.schedule(event_handler, target, recursive=True) else: file_targets.append(target) - target = os.path.dirname(target) or '.' + target = os.path.dirname(target) or "." if target not in observed: observed.add(target) observer.schedule(event_handler, target) @@ -679,11 +817,12 @@ def debounced_fs_watch(targets, outq, debounce_delay): except queue.Empty: pass outq.put(t) + th = threading.Thread(target=debounce_thread, daemon=True) th.start() -class Display(): +class Display: def __init__(self, basedump, mydump): self.basedump = basedump self.mydump = mydump @@ -693,14 +832,15 @@ class Display(): if self.emsg is not None: output = self.emsg else: - output = '\n'.join(do_diff(self.basedump, self.mydump)) + output = "\n".join(do_diff(self.basedump, self.mydump)) # Pipe the output through 'tail' and only then to less, to ensure the # write call doesn't block. ('tail' has to buffer all its input before # it starts writing.) This also means we don't have to deal with pipe # closure errors. - buffer_proc = subprocess.Popen(BUFFER_CMD, stdin=subprocess.PIPE, - stdout=subprocess.PIPE) + buffer_proc = subprocess.Popen( + BUFFER_CMD, stdin=subprocess.PIPE, stdout=subprocess.PIPE + ) less_proc = subprocess.Popen(LESS_CMD, stdin=buffer_proc.stdout) buffer_proc.stdin.write(output.encode()) buffer_proc.stdin.close() @@ -798,8 +938,10 @@ def main(): display.run_sync() else: if not args.make: - yn = input("Warning: watch-mode (-w) enabled without auto-make (-m). You will have to run make manually. Ok? (Y/n) ") - if yn.lower() == 'n': + yn = input( + "Warning: watch-mode (-w) enabled without auto-make (-m). You will have to run make manually. Ok? (Y/n) " + ) + if yn.lower() == "n": return if args.make: watch_sources = None @@ -826,11 +968,16 @@ def main(): display.progress("Building...") ret = run_make(make_target, capture_output=True) if ret.returncode != 0: - display.update(ret.stderr.decode('utf-8-sig', 'replace') or ret.stdout.decode('utf-8-sig', 'replace'), error=True) + display.update( + ret.stderr.decode("utf-8-sig", "replace") + or ret.stdout.decode("utf-8-sig", "replace"), + error=True, + ) continue mydump = run_objdump(mycmd) display.update(mydump, error=False) except KeyboardInterrupt: display.terminate() -main() + +main()
\ No newline at end of file |
