mirror of
https://github.com/zeldaret/oot.git
synced 2024-12-01 15:26:01 +00:00
230 lines
6.7 KiB
Python
Executable file
230 lines
6.7 KiB
Python
Executable file
#!/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 information 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(
|
|
"-v",
|
|
"--version",
|
|
dest="oot_version",
|
|
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.oot_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.oot_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()
|