Update asm-processor and diff.py (#278)

* fix asm differ branch

* git subrepo pull --force tools/asm-differ

subrepo:
  subdir:   "tools/asm-differ"
  merged:   "fd0984c97"
upstream:
  origin:   "https://github.com/simonlindholm/asm-differ.git"
  branch:   "main"
  commit:   "fd0984c97"
git-subrepo:
  version:  "0.4.3"
  origin:   "???"
  commit:   "???"

* delete asm-processor

* git subrepo clone git@github.com:simonlindholm/asm-processor.git tools/asm-processor

subrepo:
  subdir:   "tools/asm-processor"
  merged:   "755f734fb"
upstream:
  origin:   "git@github.com:simonlindholm/asm-processor.git"
  branch:   "main"
  commit:   "755f734fb"
git-subrepo:
  version:  "0.4.3"
  origin:   "???"
  commit:   "???"

* re-add build.py

* remove subrepo

* git subrepo pull --force tools/asm-differ

subrepo:
  subdir:   "tools/asm-differ"
  merged:   "1dfba80e1"
upstream:
  origin:   "https://github.com/simonlindholm/asm-differ.git"
  branch:   "main"
  commit:   "1dfba80e1"
git-subrepo:
  version:  "0.4.3"
  origin:   "???"
  commit:   "???"
This commit is contained in:
Anghelo Carvajal 2021-08-25 01:11:41 -04:00 committed by GitHub
parent 5ece221fe2
commit 97e066f23f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 1762 additions and 740 deletions

View File

@ -1 +1,2 @@
.mypy_cache/ .mypy_cache/
__pycache__/

View File

@ -5,8 +5,8 @@
; ;
[subrepo] [subrepo]
remote = https://github.com/simonlindholm/asm-differ.git remote = https://github.com/simonlindholm/asm-differ.git
branch = master branch = main
commit = eaf72269cf7329bc061e50d8788229575f656f06 commit = 1dfba80e1b36bb31c9ea64cb04583e7b36d839a6
parent = fa02cf86ffdb88253181cda15d047b08576d3f99 parent = f14e8c9d9e68107609c9eeb4408bbfd1cd850eee
method = merge method = merge
cmdver = 0.4.3 cmdver = 0.4.3

24
tools/asm-differ/LICENSE Normal file
View File

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>

View File

@ -1,17 +1,17 @@
# asm-differ # asm-differ
Nice differ for assembly code (currently MIPS, but should be easy to hack to support other instruction sets). Nice differ for assembly code (MIPS and AArch64; should be easy to hack to support other instruction sets).
![](screenshot.png) ![](screenshot.png)
## Dependencies ## Dependencies
- Python >= 3.6 - Python >= 3.6
- `python3 -m pip install --user colorama ansiwrap attrs watchdog` - `python3 -m pip install --user colorama watchdog python-Levenshtein` (also `dataclasses` if on 3.6)
## Usage ## Usage
Create a file `diff-settings.sh` in some directory (see the one in this repo for an example). Then from that directory, run Create a file `diff_settings.sh` in some directory (see the one in this repo for an example). Then from that directory, run
``` ```
/path/to/diff.sh [flags] (function|rom addr) /path/to/diff.sh [flags] (function|rom addr)

View File

@ -0,0 +1,64 @@
table.diff {
border: none;
font-family: Monospace;
white-space: pre;
}
.immediate {
color: lightblue;
}
.stack {
color: yellow;
}
.register {
color: yellow;
}
.delay-slot {
font-weight: bold;
color: gray;
}
.diff-change {
color: lightblue;
}
.diff-add {
color: green;
}
.diff-remove {
color: red;
}
.source-filename {
font-weight: bold;
}
.source-function {
font-weight: bold;
text-decoration: underline;
}
.source-other {
font-style: italic;
}
.rotation-0 {
color: magenta;
}
.rotation-1 {
color: cyan;
}
.rotation-2 {
color: green;
}
.rotation-3 {
color: red;
}
.rotation-4 {
color: yellow;
}
.rotation-5 {
color: pink;
}
.rotation-6 {
color: blue;
}
.rotation-7 {
color: lime;
}
.rotation-8 {
color: gray;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,10 @@
#!/usr/bin/env python3
def apply(config, args): def apply(config, args):
config['baseimg'] = 'target.bin' config["baseimg"] = "target.bin"
config['myimg'] = 'source.bin' config["myimg"] = "source.bin"
config['mapfile'] = 'build.map' config["mapfile"] = "build.map"
config['source_directories'] = ['.'] config["source_directories"] = ["."]
# config["arch"] = "mips"
# config["map_format"] = "gnu" # gnu or mw
# config["mw_build_dir"] = "build/" # only needed for mw map format
# config["makeflags"] = []
# config["objdump_executable"] = ""

View File

@ -11,6 +11,7 @@ warn_redundant_casts = True
warn_return_any = True warn_return_any = True
warn_unused_ignores = True warn_unused_ignores = True
python_version = 3.6 python_version = 3.6
files = diff.py
[mypy-diff_settings] [mypy-diff_settings]
ignore_errors = True ignore_errors = True

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -269,6 +269,43 @@ class Section:
assert self.sh_type == SHT_SYMTAB assert self.sh_type == SHT_SYMTAB
return self.symbol_entries[self.sh_info:] return self.symbol_entries[self.sh_info:]
def relocate_mdebug(self, original_offset):
assert self.sh_type == SHT_MIPS_DEBUG
new_data = bytearray(self.data)
shift_by = self.sh_offset - original_offset
# Update the file-relative offsets in the Symbolic HDRR
hdrr_magic, hdrr_vstamp, hdrr_ilineMax, hdrr_cbLine, \
hdrr_cbLineOffset, hdrr_idnMax, hdrr_cbDnOffset, hdrr_ipdMax, \
hdrr_cbPdOffset, hdrr_isymMax, hdrr_cbSymOffset, hdrr_ioptMax, \
hdrr_cbOptOffset, hdrr_iauxMax, hdrr_cbAuxOffset, hdrr_issMax, \
hdrr_cbSsOffset, hdrr_issExtMax, hdrr_cbSsExtOffset, hdrr_ifdMax, \
hdrr_cbFdOffset, hdrr_crfd, hdrr_cbRfdOffset, hdrr_iextMax, \
hdrr_cbExtOffset = struct.unpack(">HHIIIIIIIIIIIIIIIIIIIIIII", self.data[0:0x60])
assert hdrr_magic == 0x7009 , "Invalid magic value for .mdebug symbolic header"
hdrr_cbLineOffset += shift_by
hdrr_cbDnOffset += shift_by
hdrr_cbPdOffset += shift_by
hdrr_cbSymOffset += shift_by
hdrr_cbOptOffset += shift_by
hdrr_cbAuxOffset += shift_by
hdrr_cbSsOffset += shift_by
hdrr_cbSsExtOffset += shift_by
hdrr_cbFdOffset += shift_by
hdrr_cbRfdOffset += shift_by
hdrr_cbExtOffset += shift_by
new_data[0:0x60] = struct.pack(">HHIIIIIIIIIIIIIIIIIIIIIII", hdrr_magic, hdrr_vstamp, hdrr_ilineMax, hdrr_cbLine, \
hdrr_cbLineOffset, hdrr_idnMax, hdrr_cbDnOffset, hdrr_ipdMax, \
hdrr_cbPdOffset, hdrr_isymMax, hdrr_cbSymOffset, hdrr_ioptMax, \
hdrr_cbOptOffset, hdrr_iauxMax, hdrr_cbAuxOffset, hdrr_issMax, \
hdrr_cbSsOffset, hdrr_issExtMax, hdrr_cbSsExtOffset, hdrr_ifdMax, \
hdrr_cbFdOffset, hdrr_crfd, hdrr_cbRfdOffset, hdrr_iextMax, \
hdrr_cbExtOffset)
self.data = bytes(new_data)
class ElfFile: class ElfFile:
def __init__(self, data): def __init__(self, data):
@ -317,7 +354,7 @@ class ElfFile:
s.late_init(self.sections) s.late_init(self.sections)
return s return s
def drop_irrelevant_sections(self): def drop_mdebug_gptab(self):
# We can only drop sections at the end, since otherwise section # We can only drop sections at the end, since otherwise section
# references might be wrong. Luckily, these sections typically are. # references might be wrong. Luckily, these sections typically are.
while self.sections[-1].sh_type in [SHT_MIPS_DEBUG, SHT_MIPS_GPTAB]: while self.sections[-1].sh_type in [SHT_MIPS_DEBUG, SHT_MIPS_GPTAB]:
@ -340,7 +377,11 @@ class ElfFile:
for s in self.sections: for s in self.sections:
if s.sh_type != SHT_NOBITS and s.sh_type != SHT_NULL: if s.sh_type != SHT_NOBITS and s.sh_type != SHT_NULL:
pad_out(s.sh_addralign) pad_out(s.sh_addralign)
old_offset = s.sh_offset
s.sh_offset = outidx s.sh_offset = outidx
if s.sh_type == SHT_MIPS_DEBUG and s.sh_offset != old_offset:
# The .mdebug section has moved, relocate offsets
s.relocate_mdebug(old_offset)
write_out(s.data) write_out(s.data)
pad_out(4) pad_out(4)
@ -380,7 +421,7 @@ class Failure(Exception):
class GlobalState: class GlobalState:
def __init__(self, min_instr_count, skip_instr_count, use_jtbl_for_rodata): def __init__(self, min_instr_count, skip_instr_count, use_jtbl_for_rodata, mips1):
# A value that hopefully never appears as a 32-bit rodata constant (or we # A value that hopefully never appears as a 32-bit rodata constant (or we
# miscompile late rodata). Increases by 1 in each step. # miscompile late rodata). Increases by 1 in each step.
self.late_rodata_hex = 0xE0123456 self.late_rodata_hex = 0xE0123456
@ -388,6 +429,7 @@ class GlobalState:
self.min_instr_count = min_instr_count self.min_instr_count = min_instr_count
self.skip_instr_count = skip_instr_count self.skip_instr_count = skip_instr_count
self.use_jtbl_for_rodata = use_jtbl_for_rodata self.use_jtbl_for_rodata = use_jtbl_for_rodata
self.mips1 = mips1
def next_late_rodata_hex(self): def next_late_rodata_hex(self):
dummy_bytes = struct.pack('>I', self.late_rodata_hex) dummy_bytes = struct.pack('>I', self.late_rodata_hex)
@ -608,12 +650,14 @@ class GlobalAsmBlock:
size = self.fn_section_sizes['.late_rodata'] // 4 size = self.fn_section_sizes['.late_rodata'] // 4
skip_next = False skip_next = False
needs_double = (self.late_rodata_alignment != 0) needs_double = (self.late_rodata_alignment != 0)
extra_mips1_nop = False
jtbl_size = 11 if state.mips1 else 9
for i in range(size): for i in range(size):
if skip_next: if skip_next:
skip_next = False skip_next = False
continue continue
# Jump tables give 9 instructions for >= 5 words of rodata, and should be # Jump tables give 9 instructions (11 with -mips1) for >= 5 words of rodata,
# emitted when: # and should be emitted when:
# - -O2 or -O2 -g3 are used, which give the right codegen # - -O2 or -O2 -g3 are used, which give the right codegen
# - we have emitted our first .float/.double (to ensure that we find the # - we have emitted our first .float/.double (to ensure that we find the
# created rodata in the binary) # created rodata in the binary)
@ -624,11 +668,12 @@ class GlobalAsmBlock:
# - we have at least 10 more instructions to go in this function (otherwise our # - we have at least 10 more instructions to go in this function (otherwise our
# function size computation will be wrong since the delay slot goes unused) # function size computation will be wrong since the delay slot goes unused)
if (not needs_double and state.use_jtbl_for_rodata and i >= 1 and if (not needs_double and state.use_jtbl_for_rodata and i >= 1 and
size - i >= 5 and num_instr - len(late_rodata_fn_output) >= 10): size - i >= 5 and num_instr - len(late_rodata_fn_output) >= jtbl_size + 1):
cases = " ".join("case {}:".format(case) for case in range(size - i)) cases = " ".join("case {}:".format(case) for case in range(size - i))
late_rodata_fn_output.append("switch (*(volatile int*)0) { " + cases + " ; }") late_rodata_fn_output.append("switch (*(volatile int*)0) { " + cases + " ; }")
late_rodata_fn_output.extend([""] * 8) late_rodata_fn_output.extend([""] * (jtbl_size - 1))
jtbl_rodata_size = (size - i) * 4 jtbl_rodata_size = (size - i) * 4
extra_mips1_nop = i != 2
break break
dummy_bytes = state.next_late_rodata_hex() dummy_bytes = state.next_late_rodata_hex()
late_rodata_dummy_bytes.append(dummy_bytes) late_rodata_dummy_bytes.append(dummy_bytes)
@ -638,12 +683,20 @@ class GlobalAsmBlock:
fval, = struct.unpack('>d', dummy_bytes + dummy_bytes2) fval, = struct.unpack('>d', dummy_bytes + dummy_bytes2)
late_rodata_fn_output.append('*(volatile double*)0 = {};'.format(fval)) late_rodata_fn_output.append('*(volatile double*)0 = {};'.format(fval))
skip_next = True skip_next = True
needs_double = True needs_double = False
if state.mips1:
# mips1 does not have ldc1/sdc1
late_rodata_fn_output.append('')
late_rodata_fn_output.append('')
extra_mips1_nop = False
else: else:
fval, = struct.unpack('>f', dummy_bytes) fval, = struct.unpack('>f', dummy_bytes)
late_rodata_fn_output.append('*(volatile float*)0 = {}f;'.format(fval)) late_rodata_fn_output.append('*(volatile float*)0 = {}f;'.format(fval))
extra_mips1_nop = True
late_rodata_fn_output.append('') late_rodata_fn_output.append('')
late_rodata_fn_output.append('') late_rodata_fn_output.append('')
if state.mips1 and extra_mips1_nop:
late_rodata_fn_output.append('')
text_name = None text_name = None
if self.fn_section_sizes['.text'] > 0 or late_rodata_fn_output: if self.fn_section_sizes['.text'] > 0 or late_rodata_fn_output:
@ -722,7 +775,7 @@ float_regexpr = re.compile(r"[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?f")
def repl_float_hex(m): def repl_float_hex(m):
return str(struct.unpack(">I", struct.pack(">f", float(m.group(0).strip().rstrip("f"))))[0]) return str(struct.unpack(">I", struct.pack(">f", float(m.group(0).strip().rstrip("f"))))[0])
def parse_source(f, opt, framepointer, input_enc, output_enc, out_dependencies, print_source=None): def parse_source(f, opt, framepointer, mips1, input_enc, output_enc, out_dependencies, print_source=None):
if opt in ['O2', 'O1']: if opt in ['O2', 'O1']:
if framepointer: if framepointer:
min_instr_count = 6 min_instr_count = 6
@ -751,7 +804,7 @@ def parse_source(f, opt, framepointer, input_enc, output_enc, out_dependencies,
if opt in ['O2', 'g3'] and not framepointer: if opt in ['O2', 'g3'] and not framepointer:
use_jtbl_for_rodata = True use_jtbl_for_rodata = True
state = GlobalState(min_instr_count, skip_instr_count, use_jtbl_for_rodata) state = GlobalState(min_instr_count, skip_instr_count, use_jtbl_for_rodata, mips1)
global_asm = None global_asm = None
asm_functions = [] asm_functions = []
@ -803,7 +856,7 @@ def parse_source(f, opt, framepointer, input_enc, output_enc, out_dependencies,
out_dependencies.append(fname) out_dependencies.append(fname)
include_src = StringIO() include_src = StringIO()
with open(fname, encoding=input_enc) as include_file: with open(fname, encoding=input_enc) as include_file:
parse_source(include_file, opt, framepointer, input_enc, output_enc, out_dependencies, include_src) parse_source(include_file, opt, framepointer, mips1, input_enc, output_enc, out_dependencies, include_src)
include_src.write('#line ' + str(line_no + 1) + ' "' + f.name + '"') include_src.write('#line ' + str(line_no + 1) + ' "' + f.name + '"')
output_lines[-1] = include_src.getvalue() output_lines[-1] = include_src.getvalue()
include_src.close() include_src.close()
@ -831,7 +884,7 @@ def parse_source(f, opt, framepointer, input_enc, output_enc, out_dependencies,
return asm_functions return asm_functions
def fixup_objfile(objfile_name, functions, asm_prelude, assembler, output_enc): def fixup_objfile(objfile_name, functions, asm_prelude, assembler, output_enc, drop_mdebug_gptab):
SECTIONS = ['.data', '.text', '.rodata', '.bss'] SECTIONS = ['.data', '.text', '.rodata', '.bss']
with open(objfile_name, 'rb') as f: with open(objfile_name, 'rb') as f:
@ -927,9 +980,12 @@ def fixup_objfile(objfile_name, functions, asm_prelude, assembler, output_enc):
with open(o_name, 'rb') as f: with open(o_name, 'rb') as f:
asm_objfile = ElfFile(f.read()) asm_objfile = ElfFile(f.read())
# Remove some clutter from objdump output # Remove clutter from objdump output for tests, and make the tests
# portable by avoiding absolute paths. Outside of tests .mdebug is
# useful for showing source together with asm, though.
mdebug_section = objfile.find_section('.mdebug') mdebug_section = objfile.find_section('.mdebug')
objfile.drop_irrelevant_sections() if drop_mdebug_gptab:
objfile.drop_mdebug_gptab()
# Unify reginfo sections # Unify reginfo sections
target_reginfo = objfile.find_section('.reginfo') target_reginfo = objfile.find_section('.reginfo')
@ -1176,9 +1232,11 @@ def run_wrapped(argv, outfile, functions):
parser.add_argument('--post-process', dest='objfile', help="path to .o file to post-process") parser.add_argument('--post-process', dest='objfile', help="path to .o file to post-process")
parser.add_argument('--assembler', dest='assembler', help="assembler command (e.g. \"mips-linux-gnu-as -march=vr4300 -mabi=32\")") parser.add_argument('--assembler', dest='assembler', help="assembler command (e.g. \"mips-linux-gnu-as -march=vr4300 -mabi=32\")")
parser.add_argument('--asm-prelude', dest='asm_prelude', help="path to a file containing a prelude to the assembly file (with .set and .macro directives, e.g.)") parser.add_argument('--asm-prelude', dest='asm_prelude', help="path to a file containing a prelude to the assembly file (with .set and .macro directives, e.g.)")
parser.add_argument('--input-enc', default='latin1', help="Input encoding (default: latin1)") parser.add_argument('--input-enc', default='latin1', help="input encoding (default: %(default)s)")
parser.add_argument('--output-enc', default='latin1', help="Output encoding (default: latin1)") parser.add_argument('--output-enc', default='latin1', help="output encoding (default: %(default)s)")
parser.add_argument('--drop-mdebug-gptab', dest='drop_mdebug_gptab', action='store_true', help="drop mdebug and gptab sections")
parser.add_argument('-framepointer', dest='framepointer', action='store_true') parser.add_argument('-framepointer', dest='framepointer', action='store_true')
parser.add_argument('-mips1', dest='mips1', action='store_true')
parser.add_argument('-g3', dest='g3', action='store_true') parser.add_argument('-g3', dest='g3', action='store_true')
group = parser.add_mutually_exclusive_group(required=True) group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('-O1', dest='opt', action='store_const', const='O1') group.add_argument('-O1', dest='opt', action='store_const', const='O1')
@ -1190,25 +1248,27 @@ def run_wrapped(argv, outfile, functions):
if opt != 'O2': if opt != 'O2':
raise Failure("-g3 is only supported together with -O2") raise Failure("-g3 is only supported together with -O2")
opt = 'g3' opt = 'g3'
if args.mips1 and (opt != 'O2' or args.framepointer):
raise Failure("-mips1 is only supported together with -O2")
if args.objfile is None: if args.objfile is None:
with open(args.filename, encoding=args.input_enc) as f: with open(args.filename, encoding=args.input_enc) as f:
deps = [] deps = []
functions = parse_source(f, opt=opt, framepointer=args.framepointer, input_enc=args.input_enc, output_enc=args.output_enc, out_dependencies=deps, print_source=outfile) functions = parse_source(f, opt=opt, framepointer=args.framepointer, mips1=args.mips1, input_enc=args.input_enc, output_enc=args.output_enc, out_dependencies=deps, print_source=outfile)
return functions, deps return functions, deps
else: else:
if args.assembler is None: if args.assembler is None:
raise Failure("must pass assembler command") raise Failure("must pass assembler command")
if functions is None: if functions is None:
with open(args.filename, encoding=args.input_enc) as f: with open(args.filename, encoding=args.input_enc) as f:
functions = parse_source(f, opt=opt, framepointer=args.framepointer, input_enc=args.input_enc, out_dependencies=[], output_enc=args.output_enc) functions = parse_source(f, opt=opt, framepointer=args.framepointer, mips1=args.mips1, input_enc=args.input_enc, out_dependencies=[], output_enc=args.output_enc)
if not functions: if not functions:
return return
asm_prelude = b'' asm_prelude = b''
if args.asm_prelude: if args.asm_prelude:
with open(args.asm_prelude, 'rb') as f: with open(args.asm_prelude, 'rb') as f:
asm_prelude = f.read() asm_prelude = f.read()
fixup_objfile(args.objfile, functions, asm_prelude, args.assembler, args.output_enc) fixup_objfile(args.objfile, functions, asm_prelude, args.assembler, args.output_enc, args.drop_mdebug_gptab)
def run(argv, outfile=sys.stdout.buffer, functions=None): def run(argv, outfile=sys.stdout.buffer, functions=None):
try: try: