diff options
| author | EllipticEllipsis <73679967+EllipticEllipsis@users.noreply.github.com> | 2022-03-31 18:22:19 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-03-31 18:22:19 +0100 |
| commit | 15dfaf0862e761b97edcda9c1dcc01639cfba5f4 (patch) | |
| tree | 953f08d3ff5e07c9df094191c413d39b76948dfa /tools/asm-differ | |
| parent | 54a4d1eb1202c912f6c3b269e6223ea189e7c42c (diff) | |
ABI FPR names (#767)
* Add o32 to disassembler and update macro.inc
* Add a variable for asm processor to makefile
and improve objdump flags
* Update diff settings
* git subrepo pull --force tools/asm-differ
subrepo:
subdir: "tools/asm-differ"
merged: "1236288d1"
upstream:
origin: "https://github.com/simonlindholm/asm-differ"
branch: "main"
commit: "1236288d1"
git-subrepo:
version: "0.4.3"
origin: "https://github.com/ingydotnet/git-subrepo.git"
commit: "2f68596"
* Remove * import and implement option
* Fix some stuff in the makefile
* Update asm-processor
* Review
* Fix old var name
Diffstat (limited to 'tools/asm-differ')
| -rw-r--r-- | tools/asm-differ/.gitrepo | 4 | ||||
| -rw-r--r-- | tools/asm-differ/.pre-commit-config.yaml | 3 | ||||
| -rw-r--r-- | tools/asm-differ/README.md | 4 | ||||
| -rwxr-xr-x | tools/asm-differ/diff.py | 196 |
4 files changed, 143 insertions, 64 deletions
diff --git a/tools/asm-differ/.gitrepo b/tools/asm-differ/.gitrepo index 48e215c08..8d0a6445d 100644 --- a/tools/asm-differ/.gitrepo +++ b/tools/asm-differ/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = https://github.com/simonlindholm/asm-differ branch = main - commit = 6f8f80b719359d018a2b734288c977aae6538870 - parent = 91a6e9f647d2035eba281d286c78f089f70f269f + commit = 1236288d1520335c2bfb672078fec65084d7cb5c + parent = 2c5690701a350c7e7c3d6252dff925ad65d59910 method = merge cmdver = 0.4.3 diff --git a/tools/asm-differ/.pre-commit-config.yaml b/tools/asm-differ/.pre-commit-config.yaml index 6695f71ac..ccc4afc0e 100644 --- a/tools/asm-differ/.pre-commit-config.yaml +++ b/tools/asm-differ/.pre-commit-config.yaml @@ -1,6 +1,5 @@ repos: - repo: https://github.com/psf/black - rev: 20.8b1 + rev: 22.1.0 hooks: - id: black - language_version: python3.6 diff --git a/tools/asm-differ/README.md b/tools/asm-differ/README.md index 4a7f329b3..205310d39 100644 --- a/tools/asm-differ/README.md +++ b/tools/asm-differ/README.md @@ -11,10 +11,10 @@ Nice differ for assembly code. Currently supports MIPS, PPC, AArch64, and ARM32; ## 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.py` in some directory (see the one in this repo for an example). Then from that directory, run ```bash -/path/to/diff.sh [flags] (function|rom addr) +/path/to/diff.py [flags] (function|rom addr) ``` Recommended flags are `-mwo` (automatically run `make` on source file changes, and include symbols in diff). See `--help` for more details. diff --git a/tools/asm-differ/diff.py b/tools/asm-differ/diff.py index 9781420bb..4b5bc892e 100755 --- a/tools/asm-differ/diff.py +++ b/tools/asm-differ/diff.py @@ -110,6 +110,15 @@ if __name__ == "__main__": see symbol names. (Recommended)""", ) parser.add_argument( + "-f", + "--objfile", + dest="objfile", + type=str, + help="""File path for an object file being diffed. When used + the map file isn't searched for the function given. Useful for dynamically + linked libraries.""", + ) + parser.add_argument( "-e", "--elf", dest="diff_elf_symbol", @@ -133,6 +142,14 @@ if __name__ == "__main__": Implies --source.""", ) parser.add_argument( + "-j", + "--section", + dest="diff_section", + default=".text", + metavar="SECTION", + help="Diff restricted to a given output section.", + ) + parser.add_argument( "-L", "--line-numbers", dest="show_line_numbers", @@ -356,6 +373,7 @@ except ModuleNotFoundError as e: class ProjectSettings: arch_str: str objdump_executable: str + objdump_flags: List[str] build_command: List[str] map_format: str mw_build_dir: str @@ -365,6 +383,7 @@ class ProjectSettings: source_directories: Optional[List[str]] source_extensions: List[str] show_line_numbers_default: bool + disassemble_all: bool @dataclass @@ -379,8 +398,10 @@ class Config: # Build/objdump options diff_obj: bool + objfile: Optional[str] make: bool source_old_binutils: bool + diff_section: str inlines: bool max_function_size_lines: int max_function_size_bytes: int @@ -422,9 +443,11 @@ def create_project_settings(settings: Dict[str, Any]) -> ProjectSettings: "source_extensions", [".c", ".h", ".cpp", ".hpp", ".s"] ), objdump_executable=get_objdump_executable(settings.get("objdump_executable")), + objdump_flags=settings.get("objdump_flags", []), map_format=settings.get("map_format", "gnu"), mw_build_dir=settings.get("mw_build_dir", "build/"), show_line_numbers_default=settings.get("show_line_numbers_default", True), + disassemble_all=settings.get("disassemble_all", False), ) @@ -461,8 +484,10 @@ def create_config(args: argparse.Namespace, project: ProjectSettings) -> Config: arch=arch, # Build/objdump options diff_obj=args.diff_obj, + objfile=args.objfile, make=args.make, source_old_binutils=args.source_old_binutils, + diff_section=args.diff_section, inlines=args.inlines, max_function_size_lines=args.max_lines, max_function_size_bytes=args.max_lines * 4, @@ -518,7 +543,7 @@ def get_arch(arch_str: str) -> "ArchSettings": raise ValueError(f"Unknown architecture: {arch_str}") -BUFFER_CMD: List[str] = ["tail", "-c", str(10 ** 9)] +BUFFER_CMD: List[str] = ["tail", "-c", str(10**9)] # -S truncates long lines instead of wrapping them # -R interprets color escape sequences @@ -809,12 +834,7 @@ class JsonFormatter(Formatter): return {"text": s, "format": f.name.lower()} elif isinstance(f, RotationFormat): attrs = asdict(f) - attrs.update( - { - "text": s, - "format": "rotation", - } - ) + attrs.update({"text": s, "format": "rotation"}) return attrs else: static_assert_unreachable(f) @@ -962,7 +982,7 @@ def restrict_to_function(dump: str, fn_name: str) -> str: return "" -def serialize_data_references(references: List[Tuple[int, int, str]]) -> str: +def serialize_rodata_references(references: List[Tuple[int, int, str]]) -> str: return "".join( f"DATAREF {text_offset} {from_offset} {from_section}\n" for (text_offset, from_offset, from_section) in references @@ -991,7 +1011,11 @@ def run_objdump(cmd: ObjdumpCommand, config: Config, project: ProjectSettings) - flags, target, restrict = cmd try: out = subprocess.run( - [project.objdump_executable] + config.arch.arch_flags + flags + [target], + [project.objdump_executable] + + config.arch.arch_flags + + project.objdump_flags + + flags + + [target], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -1009,11 +1033,11 @@ def run_objdump(cmd: ObjdumpCommand, config: Config, project: ProjectSettings) - with open(target, "rb") as f: obj_data = f.read() - return preprocess_objdump_out(restrict, obj_data, out) + return preprocess_objdump_out(restrict, obj_data, out, config) def preprocess_objdump_out( - restrict: Optional[str], obj_data: Optional[bytes], objdump_out: str + restrict: Optional[str], obj_data: Optional[bytes], objdump_out: str, config: Config ) -> str: """ Preprocess the output of objdump into a format that `process()` expects. @@ -1033,13 +1057,16 @@ def preprocess_objdump_out( out = out.rstrip("\n") if obj_data: - out = serialize_data_references(parse_elf_data_references(obj_data)) + out + out = ( + serialize_rodata_references(parse_elf_rodata_references(obj_data, config)) + + out + ) return out def search_map_file( - fn_name: str, project: ProjectSettings + fn_name: str, project: ProjectSettings, config: Config ) -> Tuple[Optional[str], Optional[int]]: if not project.mapfile: fail(f"No map file configured; cannot find function {fn_name}.") @@ -1059,7 +1086,7 @@ def search_map_file( cands = [] last_line = "" for line in lines: - if line.startswith(" .text"): + if line.startswith(" " + config.diff_section): cur_objfile = line.split()[3] if "load address" in line: tokens = last_line.split() + line.split() @@ -1084,9 +1111,12 @@ def search_map_file( re.compile( # ram elf rom r" \S+ \S+ (\S+) (\S+) . " - + fn_name - # object name - + r"(?: \(entry of \.(?:init|text)\))? \t(\S+)" + + re.escape(fn_name) + + r"(?: \(entry of " + + re.escape(config.diff_section) + + r"\))? \t" + # object name + + "(\S+)" ), contents, ) @@ -1121,7 +1151,9 @@ def search_map_file( return None, None -def parse_elf_data_references(data: bytes) -> List[Tuple[int, int, str]]: +def parse_elf_rodata_references( + data: bytes, config: Config +) -> List[Tuple[int, int, str]]: e_ident = data[:16] if e_ident[:4] != b"\x7FELF": return [] @@ -1134,7 +1166,6 @@ def parse_elf_data_references(data: bytes) -> List[Tuple[int, int, str]]: is_little_endian = e_ident[5] == 1 str_end = "<" if is_little_endian else ">" str_off = "I" if is_32bit else "Q" - sym_size = {"B": 1, "H": 2, "I": 4, "Q": 8} def read(spec: str, offset: int) -> Tuple[int, ...]: spec = spec.replace("P", str_off) @@ -1186,7 +1217,12 @@ def parse_elf_data_references(data: bytes) -> List[Tuple[int, int, str]]: assert len(symtab_sections) == 1 symtab = sections[symtab_sections[0]] - text_sections = [i for i in range(e_shnum) if sec_names[i] == b".text" and sections[i].sh_size != 0] + section_name = config.diff_section.encode("utf-8") + text_sections = [ + i + for i in range(e_shnum) + if sec_names[i] == section_name and sections[i].sh_size != 0 + ] if len(text_sections) != 1: return [] text_section = text_sections[0] @@ -1195,11 +1231,10 @@ def parse_elf_data_references(data: bytes) -> List[Tuple[int, int, str]]: for s in sections: if s.sh_type == SHT_REL or s.sh_type == SHT_RELA: if s.sh_info == text_section: - # Skip .text -> .text references + # Skip section_name -> section_name references continue sec_name = sec_names[s.sh_info].decode("latin1") - if sec_name == ".mwcats.text": - # Skip Metrowerks CATS Utility section + if sec_name != ".rodata": continue sec_base = sections[s.sh_info].sh_offset for i in range(0, s.sh_size, s.sh_entsize): @@ -1257,11 +1292,16 @@ def dump_elf( f"--stop-address={end_addr}", ] + if project.disassemble_all: + disassemble_flag = "-D" + else: + disassemble_flag = "-d" + flags2 = [ f"--disassemble={diff_elf_symbol}", ] - objdump_flags = ["-drz", "-j", ".text"] + objdump_flags = [disassemble_flag, "-rz", "-j", config.diff_section] return ( project.myimg, (objdump_flags + flags1, project.baseimg, None), @@ -1283,7 +1323,10 @@ def dump_objfile( if start.startswith("0"): fail("numerical start address not supported with -o; pass a function name") - objfile, _ = search_map_file(start, project) + objfile = config.objfile + if not objfile: + objfile, _ = search_map_file(start, project, config) + if not objfile: fail("Not able to find .o file for function.") @@ -1297,7 +1340,12 @@ def dump_objfile( if not os.path.isfile(refobjfile): fail(f'Please ensure an OK .o file exists at "{refobjfile}".') - objdump_flags = ["-drz", "-j", ".text"] + if project.disassemble_all: + disassemble_flag = "-D" + else: + disassemble_flag = "-d" + + objdump_flags = [disassemble_flag, "-rz", "-j", config.diff_section] return ( objfile, (objdump_flags, refobjfile, start), @@ -1314,7 +1362,7 @@ def dump_binary( run_make(project.myimg, project) start_addr = maybe_eval_int(start) if start_addr is None: - _, start_addr = search_map_file(start, project) + _, start_addr = search_map_file(start, project, config) if start_addr is None: fail("Not able to find function in map file.") if end is not None: @@ -1333,6 +1381,7 @@ def dump_binary( (objdump_flags + flags2, project.myimg, None), ) + # Example: "ldr r4, [pc, #56] ; (4c <AddCoins+0x4c>)" ARM32_LOAD_POOL_PATTERN = r"(ldr\s+r([0-9]|1[0-3]),\s+\[pc,.*;\s*)(\([a-fA-F0-9]+.*\))" @@ -1363,7 +1412,7 @@ class AsmProcessor: class AsmProcessorMIPS(AsmProcessor): def process_reloc(self, row: str, prev: str) -> str: arch = self.config.arch - if "R_MIPS_NONE" in row: + if "R_MIPS_NONE" in row or "R_MIPS_JALR" in row: # GNU as emits no-op relocations immediately after real ones when # assembling with -mabi=64. Return without trying to parse 'imm' as an # integer. @@ -1378,12 +1427,9 @@ class AsmProcessorMIPS(AsmProcessor): # TODO: handle unambiguous cases where all addends for a symbol are the # same, or show "+???". mnemonic = prev.split()[0] - if ( - mnemonic in arch.instructions_with_address_immediates - and not imm.startswith("0x") - ): - imm = "0x" + imm - repl += "+" + imm if int(imm, 0) > 0 else imm + if mnemonic in arch.instructions_with_address_immediates: + imm = hex(int(imm, 16)) + repl += ("" if imm.startswith("-") else "+") + imm if "R_MIPS_LO16" in row: repl = f"%lo({repl})" elif "R_MIPS_HI16" in row: @@ -1398,6 +1444,12 @@ class AsmProcessorMIPS(AsmProcessor): # Branch to glabel. This gives confusing output, but there's not much # we can do here. pass + elif "R_MIPS_GPREL16" in row: + repl = f"%gp_rel({repl})" + elif "R_MIPS_GOT16" in row: + repl = f"%got({repl})" + elif "R_MIPS_CALL16" in row: + repl = f"%call16({repl})" else: assert False, f"unknown relocation type '{row}' for line '{prev}'" return before + repl + after @@ -1544,6 +1596,7 @@ class ArchSettings: big_endian: Optional[bool] = True delay_slot_instructions: Set[str] = field(default_factory=set) + MIPS_BRANCH_LIKELY_INSTRUCTIONS = { "beql", "bnel", @@ -1651,10 +1704,13 @@ PPC_BRANCH_INSTRUCTIONS = { MIPS_SETTINGS = ArchSettings( name="mips", re_int=re.compile(r"[0-9]+"), - re_comment=re.compile(r"<.*?>"), - re_reg=re.compile( - r"\$?\b(a[0-7]|t[0-9]|s[0-8]|at|v[01]|f[12]?[0-9]|f3[01]|kt?[01]|fp|ra|zero)\b" - ), + re_comment=re.compile(r"<.*>"), + # Includes: + # - General purpose registers v0..1, a0..7, t0..9, s0..8, zero, at, fp, k0..1/kt0..1 + # - Float registers f0..31, or fv0..1, fa0..7, ft0..15, fs0..8 plus odd complements + # (actually used number depends on ABI) + # sp, gp should not be in this list + re_reg=re.compile(r"\$?\b([astv][0-9]|at|f[astv]?[0-9]+f?|kt?[01]|fp|ra|zero)\b"), re_sprel=re.compile(r"(?<=,)([0-9]+|0x[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)\([^)]*\)"), @@ -1672,7 +1728,7 @@ MIPSEL_SETTINGS = replace(MIPS_SETTINGS, name="mipsel", big_endian=False) ARM32_SETTINGS = ArchSettings( name="arm32", re_int=re.compile(r"[0-9]+"), - re_comment=re.compile(r"(<.*?>|//.*$)"), + re_comment=re.compile(r"(<.*>|//.*$)"), # Includes: # - General purpose registers: r0..13 # - Frame pointer registers: lr (r14), pc (r15) @@ -1690,26 +1746,33 @@ ARM32_SETTINGS = ArchSettings( proc=AsmProcessorARM32, ) +ARMEL_SETTINGS = replace(ARM32_SETTINGS, name="armel", big_endian=False) + AARCH64_SETTINGS = ArchSettings( name="aarch64", re_int=re.compile(r"[0-9]+"), - re_comment=re.compile(r"(<.*?>|//.*$)"), - # GPRs and FP registers: X0-X30, W0-W30, [DSHQ]0..31 + re_comment=re.compile(r"(<.*>|//.*$)"), + # GPRs and FP registers: X0-X30, W0-W30, [BHSDVQ]0..31 + # (FP registers may be followed by data width and number of elements, e.g. V0.4S) # The zero registers and SP should not be in this list. - re_reg=re.compile(r"\$?\b([dshq][12]?[0-9]|[dshq]3[01]|[xw][12]?[0-9]|[xw]30)\b"), + re_reg=re.compile( + r"\$?\b([bhsdvq]([12]?[0-9]|3[01])(\.\d\d?[bhsdvq])?|[xw][12]?[0-9]|[xw]30)\b" + ), re_sprel=re.compile(r"sp, #-?(0x[0-9a-fA-F]+|[0-9]+)\b"), re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), re_imm=re.compile(r"(?<!sp, )#-?(0x[0-9a-fA-F]+|[0-9]+)\b"), re_reloc=re.compile(r"R_AARCH64_"), branch_instructions=AARCH64_BRANCH_INSTRUCTIONS, - instructions_with_address_immediates=AARCH64_BRANCH_INSTRUCTIONS.union({"bl", "adrp"}), + instructions_with_address_immediates=AARCH64_BRANCH_INSTRUCTIONS.union( + {"bl", "adrp"} + ), proc=AsmProcessorAArch64, ) PPC_SETTINGS = ArchSettings( name="ppc", re_int=re.compile(r"[0-9]+"), - re_comment=re.compile(r"(<.*?>|//.*$)"), + re_comment=re.compile(r"(<.*>|//.*$)"), re_reg=re.compile(r"\$?\b([rf][0-9]+)\b"), re_sprel=re.compile(r"(?<=,)(-?[0-9]+|-?0x[0-9a-f]+)\(r1\)"), re_large_imm=re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}"), @@ -1724,6 +1787,7 @@ ARCH_SETTINGS = [ MIPS_SETTINGS, MIPSEL_SETTINGS, ARM32_SETTINGS, + ARMEL_SETTINGS, AARCH64_SETTINGS, PPC_SETTINGS, ] @@ -1743,6 +1807,7 @@ def hexify_int(row: str, pat: Match[str], arch: ArchSettings) -> str: def parse_relocated_line(line: str) -> Tuple[str, str, str]: + # Pick out the last argument for c in ",\t ": if c in line: ind2 = line.rindex(c) @@ -1751,6 +1816,7 @@ def parse_relocated_line(line: str) -> Tuple[str, str, str]: raise Exception(f"failed to parse relocated line: {line}") before = line[: ind2 + 1] after = line[ind2 + 1 :] + # Move an optional ($reg) part of it to 'after' ind2 = after.find("(") if ind2 == -1: imm, after = after, "" @@ -1879,8 +1945,15 @@ def process(dump: str, config: Config) -> List[Line]: row_parts = [part.lstrip() for part in row.split(" ", 1)] mnemonic = row_parts[0].strip() - if mnemonic not in arch.instructions_with_address_immediates: - row = re.sub(arch.re_int, lambda m: hexify_int(row, m, arch), row) + addr = "" + if mnemonic in arch.instructions_with_address_immediates: + row, addr = split_off_address(row) + # objdump prefixes addresses with 0x/-0x if they don't resolve to some + # symbol + offset. Strip that. + addr = addr.replace("0x", "") + + row = re.sub(arch.re_int, lambda m: hexify_int(row, m, arch), row) + row += addr # Let 'original' be 'row' with relocations applied, while we continue # transforming 'row' into a coarser version that ignores registers and @@ -1900,9 +1973,6 @@ def process(dump: str, config: Config) -> List[Line]: scorable_line = normalized_original if not config.score_stack_differences: scorable_line = re.sub(arch.re_sprel, "addr(sp)", scorable_line) - if mnemonic in arch.branch_instructions: - # Replace the final argument with "<target>" - scorable_line = re.sub(r"[^, \t]+$", "<target>", scorable_line) if skip_next: skip_next = False @@ -1925,8 +1995,6 @@ def process(dump: str, config: Config) -> List[Line]: branch_target = None if mnemonic in arch.branch_instructions: branch_target = int(row_parts[1].strip().split(",")[-1], 16) - if mnemonic in arch.branch_likely_instructions: - branch_target -= 4 output.append( Line( @@ -1974,7 +2042,9 @@ def split_off_address(line: str) -> Tuple[str, str]: parts = line.split(",") if len(parts) < 2: parts = line.split(None, 1) - off = len(line) - len(parts[-1]) + if len(parts) < 2: + parts.append("") + off = len(line) - len(parts[-1].strip()) return line[:off], line[off:] @@ -1990,7 +2060,7 @@ def diff_sequences( ) -> List[Tuple[str, int, int, int, int]]: if ( algorithm != "levenshtein" - or len(seq1) * len(seq2) > 4 * 10 ** 8 + or len(seq1) * len(seq2) > 4 * 10**8 or len(seq1) + len(seq2) >= 0x110000 ): return diff_sequences_difflib(seq1, seq2) @@ -2177,10 +2247,15 @@ class Diff: def trim_nops(lines: List[Line], arch: ArchSettings) -> List[Line]: lines = lines[:] - while lines and lines[-1].mnemonic == "nop" and (len(lines) == 1 or lines[-2].mnemonic not in arch.delay_slot_instructions): + while ( + lines + and lines[-1].mnemonic == "nop" + and (len(lines) == 1 or lines[-2].mnemonic not in arch.delay_slot_instructions) + ): lines.pop() return lines + def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: if config.show_source: import cxxfilt @@ -2212,7 +2287,6 @@ def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: lines2 = trim_nops(lines2, arch) diffed_lines = diff_lines(lines1, lines2, config.algorithm) - score = score_diff_lines(diffed_lines, config) max_score = len(lines1) * config.penalty_deletion line_num_base = -1 @@ -2281,10 +2355,15 @@ def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: line2_line = line_num_2to1[line2.line_num] line2_target = (line2_line[0] + (target - line2.line_num), 0) - # Set the key for three-way diffing to a normalized version. + # Adjust the branch target for scoring and three-way diffing. norm2, norm_branch2 = split_off_address(line2.normalized_original) - if norm_branch2 != "<ign>": - line2.normalized_original = norm2 + str(line2_target) + if norm_branch2 != "<ignore>": + retargetted = hex(line2_target[0]).replace("0x", "") + if line2_target[1] != 0: + retargetted += f"+{line2_target[1]}" + line2.normalized_original = norm2 + retargetted + sc_base, _ = split_off_address(line2.scorable_line) + line2.scorable_line = sc_base + retargetted same_target = line2_target == (line1.branch_target, 0) else: # Do a naive comparison for non-branches (e.g. function calls). @@ -2444,6 +2523,7 @@ def do_diff(lines1: List[Line], lines2: List[Line], config: Config) -> Diff: ) ) + score = score_diff_lines(diffed_lines, config) output = output[config.skip_lines :] return Diff(lines=output, score=score, max_score=max_score) |
