2021-08-01 02:29:21 +09:00
|
|
|
import logging
|
2021-09-16 15:31:15 -04:00
|
|
|
import subprocess
|
2024-01-16 22:42:43 -07:00
|
|
|
import shlex
|
2022-07-03 10:43:59 -04:00
|
|
|
from pathlib import Path
|
2024-03-13 06:02:51 +00:00
|
|
|
from typing import List, Dict, Any
|
2024-03-10 08:26:08 +00:00
|
|
|
from functools import lru_cache
|
2021-08-01 02:29:21 +09:00
|
|
|
|
2022-12-30 11:55:03 +00:00
|
|
|
import diff as asm_differ
|
2021-08-01 02:29:21 +09:00
|
|
|
|
2022-03-13 13:33:19 -04:00
|
|
|
from coreapp.platforms import DUMMY, Platform
|
2022-11-14 03:33:08 +00:00
|
|
|
from coreapp.flags import ASMDIFF_FLAG_PREFIX
|
2023-01-14 16:28:48 +00:00
|
|
|
from django.conf import settings
|
2022-03-13 13:33:19 -04:00
|
|
|
|
|
|
|
|
from .compiler_wrapper import DiffResult, PATH
|
|
|
|
|
|
2022-02-17 18:47:04 +00:00
|
|
|
from .error import AssemblyError, DiffError, NmError, ObjdumpError
|
|
|
|
|
from .models.scratch import Assembly
|
|
|
|
|
from .sandbox import Sandbox
|
|
|
|
|
|
2021-08-01 02:29:21 +09:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2022-10-28 01:04:21 +01:00
|
|
|
MAX_FUNC_SIZE_LINES = 25000
|
2021-08-01 02:42:11 +09:00
|
|
|
|
2021-12-26 06:30:21 -05:00
|
|
|
|
2022-04-05 09:19:04 -04:00
|
|
|
class DiffWrapper:
|
|
|
|
|
@staticmethod
|
|
|
|
|
def filter_objdump_flags(compiler_flags: str) -> str:
|
|
|
|
|
# Remove irrelevant flags that are part of the base objdump configs, but clutter the compiler settings field.
|
|
|
|
|
# TODO: use cfg for this?
|
|
|
|
|
skip_flags_with_args: set[str] = set()
|
|
|
|
|
skip_flags = {
|
|
|
|
|
"--disassemble",
|
|
|
|
|
"--disassemble-zeroes",
|
|
|
|
|
"--line-numbers",
|
|
|
|
|
"--reloc",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
skip_next = False
|
|
|
|
|
flags = []
|
|
|
|
|
for flag in compiler_flags.split():
|
|
|
|
|
if skip_next:
|
|
|
|
|
skip_next = False
|
|
|
|
|
continue
|
|
|
|
|
if flag in skip_flags:
|
|
|
|
|
continue
|
|
|
|
|
if flag in skip_flags_with_args:
|
|
|
|
|
skip_next = True
|
|
|
|
|
continue
|
|
|
|
|
if any(flag.startswith(f) for f in skip_flags_with_args):
|
|
|
|
|
continue
|
|
|
|
|
flags.append(flag)
|
|
|
|
|
return " ".join(flags)
|
|
|
|
|
|
2021-08-01 03:35:42 +09:00
|
|
|
@staticmethod
|
2022-11-14 03:33:08 +00:00
|
|
|
def create_config(
|
|
|
|
|
arch: asm_differ.ArchSettings, diff_flags: List[str]
|
|
|
|
|
) -> asm_differ.Config:
|
|
|
|
|
show_rodata_refs = "-DIFFno_show_rodata_refs" not in diff_flags
|
2022-11-15 00:21:38 +00:00
|
|
|
algorithm = "difflib" if "-DIFFdifflib" in diff_flags else "levenshtein"
|
2024-04-02 19:17:10 +02:00
|
|
|
diff_function_symbols = "-DIFFdiff_function_symbols" in diff_flags
|
2022-11-14 03:33:08 +00:00
|
|
|
|
2021-09-18 12:43:02 -04:00
|
|
|
return asm_differ.Config(
|
2021-08-08 21:00:03 +09:00
|
|
|
arch=arch,
|
2021-08-01 03:35:42 +09:00
|
|
|
# Build/objdump options
|
|
|
|
|
diff_obj=True,
|
2023-09-12 06:40:08 +09:00
|
|
|
file="",
|
2021-08-01 03:35:42 +09:00
|
|
|
make=False,
|
2021-09-21 18:28:49 -04:00
|
|
|
source_old_binutils=True,
|
2022-02-04 11:40:05 -05:00
|
|
|
diff_section=".text",
|
2021-08-01 03:35:42 +09:00
|
|
|
inlines=False,
|
|
|
|
|
max_function_size_lines=MAX_FUNC_SIZE_LINES,
|
|
|
|
|
max_function_size_bytes=MAX_FUNC_SIZE_LINES * 4,
|
|
|
|
|
# Display options
|
2024-03-13 06:02:51 +00:00
|
|
|
formatter=asm_differ.PythonFormatter(arch_str=arch.name),
|
2022-12-19 09:42:11 -05:00
|
|
|
diff_mode=asm_differ.DiffMode.NORMAL,
|
2021-08-01 03:35:42 +09:00
|
|
|
base_shift=0,
|
|
|
|
|
skip_lines=0,
|
2021-08-27 01:39:06 +09:00
|
|
|
compress=None,
|
2021-08-01 03:35:42 +09:00
|
|
|
show_branches=True,
|
2021-09-21 18:28:49 -04:00
|
|
|
show_line_numbers=False,
|
|
|
|
|
show_source=False,
|
2022-09-03 03:29:21 +01:00
|
|
|
stop_at_ret=False,
|
2021-08-01 03:35:42 +09:00
|
|
|
ignore_large_imms=False,
|
2022-02-06 20:18:27 +01:00
|
|
|
ignore_addr_diffs=True,
|
2022-11-15 00:21:38 +00:00
|
|
|
algorithm=algorithm,
|
2022-09-03 03:29:21 +01:00
|
|
|
reg_categories={},
|
2022-11-14 03:33:08 +00:00
|
|
|
show_rodata_refs=show_rodata_refs,
|
2024-04-02 19:17:10 +02:00
|
|
|
diff_function_symbols=diff_function_symbols,
|
2021-08-01 03:35:42 +09:00
|
|
|
)
|
|
|
|
|
|
2022-02-05 18:42:45 +01:00
|
|
|
@staticmethod
|
2022-02-20 09:21:38 -05:00
|
|
|
def get_objdump_target_function_flags(
|
2022-07-03 10:43:59 -04:00
|
|
|
sandbox: Sandbox, target_path: Path, platform: Platform, label: str
|
2022-02-20 09:21:38 -05:00
|
|
|
) -> List[str]:
|
2022-02-05 18:42:45 +01:00
|
|
|
if not label:
|
|
|
|
|
return ["--start-address=0"]
|
|
|
|
|
|
2022-03-13 13:33:19 -04:00
|
|
|
if platform.supports_objdump_disassemble:
|
2022-02-05 18:42:45 +01:00
|
|
|
return [f"--disassemble={label}"]
|
|
|
|
|
|
2022-03-13 13:33:19 -04:00
|
|
|
if not platform.nm_cmd:
|
|
|
|
|
raise NmError(f"No nm command for {platform.id}")
|
2022-02-05 18:42:45 +01:00
|
|
|
|
|
|
|
|
try:
|
2023-01-20 18:04:27 +00:00
|
|
|
nm_proc = sandbox.run_subprocess(
|
2022-03-13 13:33:19 -04:00
|
|
|
[platform.nm_cmd] + [sandbox.rewrite_path(target_path)],
|
2022-02-05 18:42:45 +01:00
|
|
|
shell=True,
|
|
|
|
|
env={
|
|
|
|
|
"PATH": PATH,
|
2023-07-03 13:51:58 +01:00
|
|
|
"COMPILER_BASE_PATH": sandbox.rewrite_path(
|
|
|
|
|
settings.COMPILER_BASE_PATH
|
|
|
|
|
),
|
2022-02-05 18:42:45 +01:00
|
|
|
},
|
2023-01-20 18:04:27 +00:00
|
|
|
timeout=settings.OBJDUMP_TIMEOUT_SECONDS,
|
2022-02-05 18:42:45 +01:00
|
|
|
)
|
2025-09-22 19:23:55 +09:00
|
|
|
except subprocess.TimeoutExpired:
|
2023-01-20 18:04:27 +00:00
|
|
|
raise NmError("Timeout expired")
|
2022-02-05 18:42:45 +01:00
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
|
raise NmError.from_process_error(e)
|
|
|
|
|
|
|
|
|
|
if nm_proc.stdout:
|
|
|
|
|
# e.g.
|
|
|
|
|
# 00000000 T osEepromRead
|
|
|
|
|
# U osMemSize
|
|
|
|
|
for line in nm_proc.stdout.splitlines():
|
|
|
|
|
nm_line = line.split()
|
|
|
|
|
if len(nm_line) == 3 and label == nm_line[2]:
|
|
|
|
|
start_addr = int(nm_line[0], 16)
|
|
|
|
|
return [f"--start-address={start_addr}"]
|
|
|
|
|
|
|
|
|
|
return ["--start-address=0"]
|
|
|
|
|
|
2022-04-05 09:19:04 -04:00
|
|
|
@staticmethod
|
2022-07-03 10:43:59 -04:00
|
|
|
def parse_objdump_flags(diff_flags: List[str]) -> List[str]:
|
2025-01-28 22:00:44 +01:00
|
|
|
known_objdump_flags = ["-Mno-aliases", "--reloc"]
|
2024-01-16 23:52:41 -07:00
|
|
|
known_objdump_flag_prefixes = ["-Mreg-names=", "--disassemble="]
|
2022-04-05 09:19:04 -04:00
|
|
|
ret = []
|
|
|
|
|
|
2024-01-16 23:52:41 -07:00
|
|
|
for flag in diff_flags:
|
|
|
|
|
if flag in known_objdump_flags or flag.startswith(
|
|
|
|
|
tuple(known_objdump_flag_prefixes)
|
|
|
|
|
):
|
2022-12-25 02:02:43 -03:00
|
|
|
ret.append(flag)
|
2022-04-05 09:19:04 -04:00
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
2024-03-10 08:26:08 +00:00
|
|
|
@lru_cache()
|
2021-08-01 03:35:42 +09:00
|
|
|
@staticmethod
|
2022-02-20 09:21:38 -05:00
|
|
|
def run_objdump(
|
|
|
|
|
target_data: bytes,
|
2022-03-13 13:33:19 -04:00
|
|
|
platform: Platform,
|
2024-03-10 08:26:08 +00:00
|
|
|
arch_flags: tuple[str, ...],
|
2022-06-12 08:03:24 -04:00
|
|
|
label: str,
|
2024-03-10 08:26:08 +00:00
|
|
|
objdump_flags: tuple[str, ...],
|
2022-02-20 09:21:38 -05:00
|
|
|
) -> str:
|
2024-03-10 08:26:08 +00:00
|
|
|
flags = [
|
|
|
|
|
flag for flag in objdump_flags if not flag.startswith(ASMDIFF_FLAG_PREFIX)
|
|
|
|
|
]
|
2022-04-05 09:19:04 -04:00
|
|
|
flags += [
|
2021-09-21 18:28:49 -04:00
|
|
|
"--disassemble-zeroes",
|
|
|
|
|
"--line-numbers",
|
|
|
|
|
]
|
2021-08-01 03:35:42 +09:00
|
|
|
|
2024-02-05 15:48:32 +00:00
|
|
|
# --reloc can cause issues with DOS disasm?
|
|
|
|
|
if platform.id != "msdos":
|
2023-07-03 11:06:14 +01:00
|
|
|
flags += ["--reloc"]
|
|
|
|
|
|
2021-08-11 13:37:24 -04:00
|
|
|
with Sandbox() as sandbox:
|
2021-08-10 22:44:39 -04:00
|
|
|
target_path = sandbox.path / "out.s"
|
|
|
|
|
target_path.write_bytes(target_data)
|
2021-08-09 02:38:22 -04:00
|
|
|
|
2024-01-16 23:52:41 -07:00
|
|
|
# If the flags contain `--disassemble=[symbol]`,
|
|
|
|
|
# use that instead of `--start-address`.
|
|
|
|
|
has_symbol = False
|
|
|
|
|
for flag in flags:
|
|
|
|
|
if flag.startswith("--disassemble="):
|
|
|
|
|
has_symbol = True
|
|
|
|
|
if not has_symbol:
|
|
|
|
|
flags.append("--disassemble")
|
|
|
|
|
flags += DiffWrapper.get_objdump_target_function_flags(
|
|
|
|
|
sandbox, target_path, platform, label
|
|
|
|
|
)
|
2021-10-11 16:59:33 +01:00
|
|
|
|
2024-03-10 08:26:08 +00:00
|
|
|
flags += arch_flags
|
2022-04-05 09:19:04 -04:00
|
|
|
|
2022-03-13 13:33:19 -04:00
|
|
|
if platform.objdump_cmd:
|
2021-10-11 16:59:33 +01:00
|
|
|
try:
|
2023-01-20 18:04:27 +00:00
|
|
|
objdump_proc = sandbox.run_subprocess(
|
2022-04-05 09:19:04 -04:00
|
|
|
platform.objdump_cmd.split()
|
2024-01-16 22:42:43 -07:00
|
|
|
+ list(map(shlex.quote, flags))
|
2022-02-20 09:21:38 -05:00
|
|
|
+ [sandbox.rewrite_path(target_path)],
|
2021-10-11 16:59:33 +01:00
|
|
|
shell=True,
|
|
|
|
|
env={
|
|
|
|
|
"PATH": PATH,
|
2023-07-03 13:51:58 +01:00
|
|
|
"COMPILER_BASE_PATH": sandbox.rewrite_path(
|
|
|
|
|
settings.COMPILER_BASE_PATH
|
|
|
|
|
),
|
2021-10-11 16:59:33 +01:00
|
|
|
},
|
2023-01-20 18:04:27 +00:00
|
|
|
timeout=settings.OBJDUMP_TIMEOUT_SECONDS,
|
2021-10-11 16:59:33 +01:00
|
|
|
)
|
2025-09-22 19:23:55 +09:00
|
|
|
except subprocess.TimeoutExpired:
|
2023-01-20 18:04:27 +00:00
|
|
|
raise ObjdumpError("Timeout expired")
|
2021-10-11 16:59:33 +01:00
|
|
|
except subprocess.CalledProcessError as e:
|
2021-12-26 06:30:21 -05:00
|
|
|
raise ObjdumpError.from_process_error(e)
|
2021-10-11 16:59:33 +01:00
|
|
|
else:
|
2022-03-13 13:33:19 -04:00
|
|
|
raise ObjdumpError(f"No objdump command for {platform.id}")
|
2021-08-01 03:35:42 +09:00
|
|
|
|
2021-08-10 22:44:39 -04:00
|
|
|
out = objdump_proc.stdout
|
2021-08-01 03:35:42 +09:00
|
|
|
return out
|
|
|
|
|
|
2021-08-26 14:49:33 -04:00
|
|
|
@staticmethod
|
2022-02-20 09:21:38 -05:00
|
|
|
def get_dump(
|
|
|
|
|
elf_object: bytes,
|
2022-03-13 13:33:19 -04:00
|
|
|
platform: Platform,
|
2022-06-12 08:03:24 -04:00
|
|
|
diff_label: str,
|
2022-02-20 09:21:38 -05:00
|
|
|
config: asm_differ.Config,
|
2022-07-03 10:43:59 -04:00
|
|
|
diff_flags: List[str],
|
2022-02-20 09:21:38 -05:00
|
|
|
) -> str:
|
2022-02-17 13:11:21 -05:00
|
|
|
if len(elf_object) == 0:
|
|
|
|
|
raise AssemblyError("Asm empty")
|
|
|
|
|
|
2022-04-05 09:19:04 -04:00
|
|
|
basedump = DiffWrapper.run_objdump(
|
2024-03-10 08:26:08 +00:00
|
|
|
elf_object,
|
|
|
|
|
platform,
|
|
|
|
|
tuple(config.arch.arch_flags),
|
|
|
|
|
diff_label,
|
|
|
|
|
tuple(diff_flags),
|
2022-02-20 09:21:38 -05:00
|
|
|
)
|
2022-02-17 13:11:21 -05:00
|
|
|
if not basedump:
|
|
|
|
|
raise ObjdumpError("Error running objdump")
|
|
|
|
|
|
|
|
|
|
# Preprocess the dump
|
|
|
|
|
try:
|
2022-02-20 09:21:38 -05:00
|
|
|
basedump = asm_differ.preprocess_objdump_out(
|
|
|
|
|
None, elf_object, basedump, config
|
|
|
|
|
)
|
2022-02-17 13:11:21 -05:00
|
|
|
except AssertionError as e:
|
2025-02-09 11:24:46 +00:00
|
|
|
logger.exception("Error preprocessing dump: %s", e)
|
2022-02-17 13:11:21 -05:00
|
|
|
raise DiffError(f"Error preprocessing dump: {e}")
|
|
|
|
|
except Exception as e:
|
2025-02-09 11:24:46 +00:00
|
|
|
logger.exception("Error preprocessing dump: %s", e)
|
2022-02-17 13:11:21 -05:00
|
|
|
raise DiffError(f"Error preprocessing dump: {e}")
|
|
|
|
|
|
|
|
|
|
return basedump
|
|
|
|
|
|
2024-03-13 06:02:51 +00:00
|
|
|
@staticmethod
|
2025-06-16 08:04:20 +02:00
|
|
|
def run_diff(
|
|
|
|
|
base_lines: list[str], my_lines: list[str], config: Any
|
|
|
|
|
) -> Dict[str, Any]:
|
2024-03-13 06:02:51 +00:00
|
|
|
diff_output = asm_differ.do_diff(base_lines, my_lines, config)
|
|
|
|
|
table_data = asm_differ.align_diffs(diff_output, diff_output, config)
|
|
|
|
|
return config.formatter.raw(table_data)
|
|
|
|
|
|
2022-02-17 13:11:21 -05:00
|
|
|
@staticmethod
|
2022-02-20 09:21:38 -05:00
|
|
|
def diff(
|
|
|
|
|
target_assembly: Assembly,
|
2022-03-13 13:33:19 -04:00
|
|
|
platform: Platform,
|
2022-06-12 08:03:24 -04:00
|
|
|
diff_label: str,
|
2022-02-20 09:21:38 -05:00
|
|
|
compiled_elf: bytes,
|
2022-07-03 10:43:59 -04:00
|
|
|
diff_flags: List[str],
|
2022-02-20 09:21:38 -05:00
|
|
|
) -> DiffResult:
|
2022-03-13 13:33:19 -04:00
|
|
|
if platform == DUMMY:
|
2021-12-26 06:30:21 -05:00
|
|
|
# Todo produce diff for dummy
|
2024-08-22 23:43:26 -06:00
|
|
|
return DiffResult({"rows": ["a", "b"]})
|
2021-12-26 06:30:21 -05:00
|
|
|
|
2021-09-18 12:43:02 -04:00
|
|
|
try:
|
2022-03-13 13:33:19 -04:00
|
|
|
arch = asm_differ.get_arch(platform.arch or "")
|
2021-09-18 12:43:02 -04:00
|
|
|
except ValueError:
|
2022-03-13 13:33:19 -04:00
|
|
|
logger.error(f"Unsupported arch: {platform.arch}. Continuing assuming mips")
|
2021-09-18 12:43:02 -04:00
|
|
|
arch = asm_differ.get_arch("mips")
|
|
|
|
|
|
2022-04-05 09:19:04 -04:00
|
|
|
objdump_flags = DiffWrapper.parse_objdump_flags(diff_flags)
|
2021-08-10 02:51:57 +09:00
|
|
|
|
2022-11-14 03:33:08 +00:00
|
|
|
config = DiffWrapper.create_config(arch, diff_flags)
|
2024-03-31 19:38:14 +01:00
|
|
|
try:
|
|
|
|
|
basedump = DiffWrapper.get_dump(
|
|
|
|
|
bytes(target_assembly.elf_object),
|
|
|
|
|
platform,
|
|
|
|
|
diff_label,
|
|
|
|
|
config,
|
|
|
|
|
objdump_flags,
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
2025-02-09 11:24:46 +00:00
|
|
|
logger.exception("Error dumping target assembly: %s", e)
|
2024-03-31 19:38:14 +01:00
|
|
|
raise DiffError(f"Error dumping target assembly: {e}")
|
2021-12-26 06:30:21 -05:00
|
|
|
try:
|
2022-04-05 09:19:04 -04:00
|
|
|
mydump = DiffWrapper.get_dump(
|
|
|
|
|
compiled_elf, platform, diff_label, config, objdump_flags
|
2022-02-20 09:21:38 -05:00
|
|
|
)
|
2025-09-22 19:23:55 +09:00
|
|
|
except Exception:
|
2022-12-14 09:40:41 -05:00
|
|
|
mydump = ""
|
2021-08-02 22:45:57 +09:00
|
|
|
|
2021-10-05 20:51:30 +09:00
|
|
|
try:
|
2025-06-16 08:04:20 +02:00
|
|
|
base_lines = asm_differ.process(basedump, config)
|
|
|
|
|
my_lines = asm_differ.process(mydump, config)
|
|
|
|
|
result = DiffWrapper.run_diff(base_lines, my_lines, config)
|
|
|
|
|
diff_result = DiffResult(result)
|
|
|
|
|
if any(x.startswith("--disassemble=") for x in objdump_flags):
|
|
|
|
|
if len(base_lines) and len(my_lines) == 0:
|
|
|
|
|
diff_result.errors = (
|
|
|
|
|
"Warning: No diff rows. Is your function signature correct?"
|
|
|
|
|
)
|
2021-12-26 06:30:21 -05:00
|
|
|
except Exception as e:
|
2025-02-09 11:24:46 +00:00
|
|
|
logger.exception("Error running asm-differ: %s", e)
|
2021-12-26 06:30:21 -05:00
|
|
|
raise DiffError(f"Error running asm-differ: {e}")
|
2021-08-01 03:35:42 +09:00
|
|
|
|
2025-06-16 08:04:20 +02:00
|
|
|
return diff_result
|