1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2025-07-05 15:34:41 +00:00

ucode_disas.c progress (#188)

* ucode_disas.c progress

* fix

* minor fixes

* minor comment changes
This commit is contained in:
Random 2020-06-05 19:18:39 +02:00 committed by GitHub
parent e67c51b155
commit 94d810193a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1749 additions and 2099 deletions

527
diff.py
View file

@ -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()