1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
|
#!/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()
|