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:
parent
e67c51b155
commit
94d810193a
24 changed files with 1749 additions and 2099 deletions
527
diff.py
527
diff.py
|
@ -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()
|
Loading…
Add table
Add a link
Reference in a new issue