mirror of
https://github.com/zeldaret/oot.git
synced 2024-12-26 14:46:16 +00:00
Add "disasm sym_info.py" script (#2054)
* Add "disasm sym_info.py" script for investigating the expected memory layout * review * add friendly error if context.csv is missing * rom as hexadecimal * require spimdisasm 1.28.1 (technically not *required* but may as well) * some fixups on symbol lookup logic
This commit is contained in:
parent
d191e8714e
commit
ebbc820ef6
3 changed files with 239 additions and 2 deletions
|
@ -18,4 +18,4 @@ toml
|
|||
mapfile-parser>=2.3.5,<3.0.0
|
||||
pyelftools==0.30
|
||||
rabbitizer>=1.0.0,<2.0.0
|
||||
spimdisasm>=1.21.0,<2.0.0
|
||||
spimdisasm>=1.28.1,<2.0.0
|
||||
|
|
|
@ -85,6 +85,11 @@ def main():
|
|||
|
||||
args = parser.parse_args()
|
||||
|
||||
if spimdisasm.__version_info__ < (1, 28, 1):
|
||||
print(f"Error: spimdisasm>=1.28.1 is required (you have {spimdisasm.__version__})")
|
||||
print("Hint: run `make setup` to update the venv.")
|
||||
exit(1)
|
||||
|
||||
context = spimdisasm.common.Context()
|
||||
context.parseArgs(args)
|
||||
context.changeGlobalSegmentRanges(0x00000000, 0x01000000, 0x8000000, 0x81000000)
|
||||
|
@ -117,9 +122,12 @@ def main():
|
|||
print()
|
||||
print("Analyzing done.")
|
||||
|
||||
print("Writing disassembled sections...")
|
||||
output_dir: Path = args.output_dir
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
context.saveContextToFile(output_dir / "context.csv")
|
||||
|
||||
print("Writing disassembled sections...")
|
||||
for i, file_splits in enumerate(all_file_splits):
|
||||
f = i / len(all_file_splits)
|
||||
spimdisasm.common.Utils.printQuietless(
|
||||
|
|
229
tools/disasm/sym_info.py
Executable file
229
tools/disasm/sym_info.py
Executable file
|
@ -0,0 +1,229 @@
|
|||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: © 2024 ZeldaRET
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import dataclasses
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Sym:
|
||||
name: str
|
||||
value: int
|
||||
type: Optional[str]
|
||||
size: Optional[int]
|
||||
vrom: int
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class FileSection:
|
||||
file: str
|
||||
section: str
|
||||
syms: list[Sym]
|
||||
vma_start: int
|
||||
|
||||
|
||||
LABELS_TYPES = {"@branchlabel", "@jumptablelabel"}
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Print informations on a symbol/address"
|
||||
" (and possibly surrounding symbols)"
|
||||
" from the spimdisasm disassembly context saved in"
|
||||
" expected/build/VERSION/context.csv"
|
||||
)
|
||||
parser.add_argument("sym_or_vma")
|
||||
default_version = "ntsc-1.2"
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
"-v",
|
||||
default=default_version,
|
||||
help=f"oot version (default: {default_version})",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--around",
|
||||
"-n",
|
||||
type=int,
|
||||
default=0,
|
||||
help="how many symbols to show around the target (at least)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--range",
|
||||
"-r",
|
||||
type=lambda v: int(v, 0),
|
||||
default=0,
|
||||
help="show symbols within this range around the target (at least)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--file",
|
||||
"-f",
|
||||
action="store_true",
|
||||
help="show symbols within the same file and section as the target (at least)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--labels",
|
||||
"-l",
|
||||
action="store_true",
|
||||
help="also show branch and jump table labels symbols",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
sym_or_vma = args.sym_or_vma
|
||||
if "_" in sym_or_vma:
|
||||
# special case to avoid parsing e.g. `D_80123456` as hexadecimal 0xD80123456
|
||||
sym_or_vma_is_sym = True
|
||||
else:
|
||||
try:
|
||||
target_sym_name = None
|
||||
target_vma = int(sym_or_vma, 16)
|
||||
sym_or_vma_is_sym = False
|
||||
except ValueError:
|
||||
sym_or_vma_is_sym = True
|
||||
if sym_or_vma_is_sym:
|
||||
target_sym_name = sym_or_vma
|
||||
target_vma = None
|
||||
|
||||
syms_by_section_by_file = dict[str, dict[str, list[Sym]]]()
|
||||
|
||||
context_csv_p = Path(f"expected/build/{args.version}/context.csv")
|
||||
if not context_csv_p.exists():
|
||||
print(f"Context file does not exist: {context_csv_p}")
|
||||
print(f"Hint: run `make VERSION={args.version} disasm`")
|
||||
exit(1)
|
||||
|
||||
with context_csv_p.open() as f:
|
||||
for e in csv.DictReader(f):
|
||||
if e["category"] != "symbol":
|
||||
continue
|
||||
sym_name = e["getName"]
|
||||
sym_value = e["address"]
|
||||
sym_type = e["getType"]
|
||||
sym_size = e["getSize"]
|
||||
sym_vrom = e["getVrom"]
|
||||
sym_section = e["sectionType"]
|
||||
sym_file = e["parentFileName"]
|
||||
|
||||
if sym_file == "None":
|
||||
sym_file = None
|
||||
|
||||
if not sym_section or not sym_file:
|
||||
continue
|
||||
|
||||
sym_value_int = int(sym_value, 0)
|
||||
sym_size_int = int(sym_size, 0) if sym_size else None
|
||||
sym_vrom_int = int(sym_vrom, 0)
|
||||
|
||||
syms_by_section_by_file.setdefault(sym_file, dict()).setdefault(
|
||||
sym_section, list()
|
||||
).append(
|
||||
Sym(
|
||||
sym_name,
|
||||
sym_value_int,
|
||||
sym_type if sym_type else None,
|
||||
sym_size_int,
|
||||
sym_vrom_int,
|
||||
)
|
||||
)
|
||||
|
||||
if sym_name == target_sym_name:
|
||||
target_vma = sym_value_int
|
||||
|
||||
if target_vma is None:
|
||||
parser.error(f"No symbol '{target_sym_name}'")
|
||||
else:
|
||||
if target_sym_name is not None:
|
||||
print(f"{target_sym_name} = 0x{target_vma:08X}")
|
||||
|
||||
del target_sym_name
|
||||
|
||||
filesections = list[FileSection]()
|
||||
|
||||
for file, syms_by_section in syms_by_section_by_file.items():
|
||||
for section, syms in syms_by_section.items():
|
||||
syms.sort(key=lambda sym: sym.value)
|
||||
vma_start = syms[0].value
|
||||
filesections.append(FileSection(file, section, syms, vma_start))
|
||||
|
||||
filesections.sort(key=lambda fs: fs.vma_start)
|
||||
|
||||
def get_first_print_sym():
|
||||
prev_syms = list[Sym]()
|
||||
for fs in filesections:
|
||||
for sym in fs.syms:
|
||||
if not args.labels and sym.type in LABELS_TYPES:
|
||||
continue
|
||||
if target_vma < sym.value:
|
||||
return prev_syms[0]
|
||||
prev_syms.append(sym)
|
||||
while (
|
||||
len(prev_syms) - 1 > args.around
|
||||
and prev_syms[0].value < target_vma - args.range
|
||||
):
|
||||
prev_syms.pop(0)
|
||||
|
||||
first_print_sym = get_first_print_sym()
|
||||
|
||||
def get_last_print_sym():
|
||||
min_skip_count = args.around
|
||||
for fs in filesections:
|
||||
for sym in fs.syms:
|
||||
if not args.labels and sym.type in LABELS_TYPES:
|
||||
continue
|
||||
if target_vma <= sym.value:
|
||||
min_skip_count -= 1
|
||||
if min_skip_count < 0 and sym.value >= args.range + target_vma:
|
||||
return sym
|
||||
|
||||
last_print_sym = get_last_print_sym()
|
||||
|
||||
is_near_target = False
|
||||
|
||||
indent = " " * 4
|
||||
|
||||
for i_fs, fs in enumerate(filesections):
|
||||
fs_printed = False
|
||||
is_first_fs_sym = True
|
||||
fs_printed_end_ellipsis = False
|
||||
for sym in fs.syms:
|
||||
if not args.labels and sym.type in LABELS_TYPES:
|
||||
continue
|
||||
|
||||
if sym == first_print_sym:
|
||||
is_near_target = True
|
||||
|
||||
print_sym = is_near_target or (
|
||||
args.file
|
||||
and fs.vma_start <= target_vma
|
||||
and (
|
||||
target_vma < filesections[i_fs + 1].vma_start
|
||||
if i_fs + 1 < len(filesections)
|
||||
else True
|
||||
)
|
||||
)
|
||||
if not print_sym and fs_printed:
|
||||
if not fs_printed_end_ellipsis:
|
||||
print(f"{indent}...")
|
||||
fs_printed_end_ellipsis = True
|
||||
if print_sym:
|
||||
if not fs_printed:
|
||||
print(fs.file, fs.section)
|
||||
fs_printed = True
|
||||
if not is_first_fs_sym:
|
||||
print(f"{indent}...")
|
||||
print(
|
||||
f"{indent}{sym.name} 0x{sym.value:X} ROM:0x{sym.vrom:X}"
|
||||
+ (f" ({sym.type})" if sym.type else "")
|
||||
+ (f" (sz=0x{sym.size:X})" if sym.size else "")
|
||||
)
|
||||
is_first_fs_sym = False
|
||||
|
||||
if sym == last_print_sym:
|
||||
is_near_target = False
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
Loading…
Reference in a new issue