Skip to content

Commit feaf9e1

Browse files
authored
Internal rework for bss_size attribute. (#533)
* Internal rework for `bss_size` attribute. Now internally every segment has a `bss_size` attribute, which is considered in most calculations now. This fixes some usages where bss segments would report the wrong `vram_end`. Now `bss_size` is known when constructing the `Segment` instance, replacing the old approach of letting the segment itself calculate the `bss_size` on its own when it needed it. I also cleaned up some code a bit * Allow zero sized bss segments * format
1 parent e0821fd commit feaf9e1

15 files changed

Lines changed: 222 additions & 113 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# splat Release Notes
22

3+
### 0.40.1
4+
5+
* Internal rework for `bss_size` attribute.
6+
* Now internally every segment has a `bss_size` attribute, which is considered in most calculations now.
7+
* This fixes some usages where bss segments would report the wrong `vram_end`.
8+
* Consider zero-sized bss segments as valid if they are the last subsegment of a code segment.
9+
310
### 0.40.0
411

512
* Append an `-include` directive to the generated elf dependency file.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ The brackets corresponds to the optional dependencies to install while installin
2121
If you use a `requirements.txt` file in your repository, then you can add this library with the following line:
2222

2323
```txt
24-
splat64[mips]>=0.40.0,<1.0.0
24+
splat64[mips]>=0.40.1,<1.0.0
2525
```
2626

2727
### Optional dependencies

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "splat64"
33
# Should be synced with src/splat/__init__.py
4-
version = "0.40.0"
4+
version = "0.40.1"
55
description = "A binary splitting tool to assist with decompilation and modding projects"
66
readme = "README.md"
77
license = {file = "LICENSE"}

src/splat/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
__package_name__ = __name__
22

33
# Should be synced with pyproject.toml
4-
__version__ = "0.40.0"
4+
__version__ = "0.40.1"
55
__author__ = "ethteck"
66

77
from . import util as util

src/splat/segtypes/common/bss.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,14 +60,9 @@ def disassemble_data(self, rom_bytes: bytes):
6060
f"Segment '{self.name}' (type '{self.type}') requires a vram address. Got '{self.vram_start}'"
6161
)
6262

63-
next_subsegment = self.parent.get_next_subsegment_for_ram(
64-
self.vram_start, self.index_within_group
65-
)
66-
if next_subsegment is None:
67-
bss_end = self.get_most_parent().vram_end
68-
else:
69-
bss_end = next_subsegment.vram_start
70-
assert isinstance(bss_end, int), f"{self.name} {bss_end}"
63+
# Supposedly logic error, not user error
64+
assert isinstance(self.bss_size, int), f"{self.name} {self.bss_size}"
65+
bss_end = self.vram_start + self.bss_size
7166

7267
self.spim_section = make_bss_section(
7368
self.rom_start,

src/splat/segtypes/common/code.py

Lines changed: 50 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections import OrderedDict
2-
from typing import List, Optional, Type, Tuple
2+
from typing import List, Optional, Type, Tuple, Union
33

44
from ...util import log, options, utils
55

@@ -21,9 +21,10 @@ def __init__(
2121
name: str,
2222
vram_start: Optional[int],
2323
args: list,
24-
yaml,
24+
yaml: Union[dict, list],
25+
bss_size: Optional[int] = None,
2526
):
26-
self.bss_size: int = yaml.get("bss_size", 0) if isinstance(yaml, dict) else 0
27+
self.bss_size = bss_size if bss_size is not None else self.parse_bss_size(yaml)
2728

2829
super().__init__(
2930
rom_start,
@@ -33,6 +34,7 @@ def __init__(
3334
vram_start,
3435
args=args,
3536
yaml=yaml,
37+
bss_size=self.bss_size,
3638
)
3739

3840
self.reported_file_split = False
@@ -41,17 +43,6 @@ def __init__(
4143
if self.align is None:
4244
self.align = 0x10
4345

44-
@property
45-
def needs_symbols(self) -> bool:
46-
return True
47-
48-
@property
49-
def vram_end(self) -> Optional[int]:
50-
if self.vram_start is not None and self.size is not None:
51-
return self.vram_start + self.size + self.bss_size
52-
else:
53-
return None
54-
5546
# Generates a placeholder segment for the auto_link_sections option
5647
def _generate_segment_from_all(
5748
self,
@@ -62,13 +53,15 @@ def _generate_segment_from_all(
6253
rom_start: Optional[int] = None,
6354
rom_end: Optional[int] = None,
6455
vram_start: Optional[int] = None,
56+
bss_size: Optional[int] = None,
6557
) -> Segment:
6658
rep: Segment = replace_class(
6759
rom_start=rom_start,
6860
rom_end=rom_end,
6961
type=rep_type,
7062
name=base_name,
7163
vram_start=vram_start,
64+
bss_size=bss_size,
7265
args=[],
7366
yaml={},
7467
)
@@ -146,19 +139,22 @@ def _insert_all_auto_sections(
146139
return ret
147140

148141
def parse_subsegments(self, segment_yaml) -> List[Segment]:
142+
ret: List[Segment] = []
143+
149144
if "subsegments" not in segment_yaml:
150145
if not self.parent:
151146
raise Exception(
152147
f"No subsegments provided in top-level code segment {self.name}"
153148
)
154-
return []
149+
return ret
150+
yaml_subsegments = segment_yaml["subsegments"]
155151

156152
base_segments: OrderedDict[str, Segment] = OrderedDict()
157-
ret: List[Segment] = []
158-
prev_start: Optional[int] = -1
159-
prev_vram: Optional[int] = -1
153+
prev_start: Optional[int] = None
154+
prev_vram: Optional[int] = None
160155

161-
last_rom_end = None
156+
# Start as the "start" address of the group.
157+
last_rom_end = self.rom_start
162158

163159
# Determine what comes first, either text or rodata/rdata
164160
readonly_before = False
@@ -172,39 +168,41 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]:
172168
if rdata_index is not None:
173169
readonly_before = rdata_index < text_index
174170

175-
for i, subsegment_yaml in enumerate(segment_yaml["subsegments"]):
171+
for i, subsegment_yaml in enumerate(yaml_subsegments):
176172
# endpos marker
177173
if isinstance(subsegment_yaml, list) and len(subsegment_yaml) == 1:
178174
continue
179175

176+
next_subsegment_yaml = (
177+
yaml_subsegments[i + 1] if i + 1 < len(yaml_subsegments) else None
178+
)
179+
180180
typ = Segment.parse_segment_type(subsegment_yaml)
181181
start, is_auto_segment = Segment.parse_segment_start(subsegment_yaml)
182182

183183
segment_class = Segment.get_class_for_type(typ)
184184

185185
if start is None:
186-
# Attempt to infer the start address
187-
if i == 0:
188-
# The start address of this segment is the start address of the group
189-
start = self.rom_start
190-
else:
191-
# The start address is the end address of the previous segment
192-
start = last_rom_end
186+
# Attempt to infer the start address.
187+
# The start address is the end address of the previous segment.
188+
# If this is the first subsegment then this value will fallback
189+
# to the start address of the group.
190+
start = last_rom_end
193191

194192
# First, try to get the end address from the next segment's start address
195193
# Second, try to get the end address from the estimated size of this segment
196194
# Third, try to get the end address from the next segment with a start address
197195
end: Optional[int] = None
198-
if i < len(segment_yaml["subsegments"]) - 1:
196+
if next_subsegment_yaml is not None:
199197
end, end_is_auto_segment = Segment.parse_segment_start(
200-
segment_yaml["subsegments"][i + 1]
198+
next_subsegment_yaml
201199
)
202200
if start is not None and end is None:
203201
est_size = segment_class.estimate_size(subsegment_yaml)
204202
if est_size is not None:
205203
end = start + est_size
206204
if end is None:
207-
end = self.get_next_seg_start(i, segment_yaml["subsegments"])
205+
end = self.get_next_seg_start(i, yaml_subsegments)
208206

209207
if start is not None and prev_start is not None and start < prev_start:
210208
log.error(
@@ -216,14 +214,25 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]:
216214
assert isinstance(start, int)
217215
vram = self.get_most_parent().rom_to_ram(start)
218216

219-
if segment_class.is_noload() and last_rom_end is not None:
220-
# Pretend bss's rom address is after the last actual rom segment
221-
start = last_rom_end
222-
# and it has a rom size of zero
223-
end = last_rom_end
217+
# noload (bss) segments need a bit of special calculation
218+
start, end, vram, bss_size = self._calculate_noload_values(
219+
segment_class,
220+
subsegment_yaml,
221+
next_subsegment_yaml,
222+
start,
223+
end,
224+
vram,
225+
last_rom_end,
226+
)
224227

225-
segment: Segment = Segment.from_yaml(
226-
segment_class, subsegment_yaml, start, end, self, vram
228+
segment = Segment.from_yaml(
229+
segment_class,
230+
subsegment_yaml,
231+
start,
232+
end,
233+
self,
234+
vram,
235+
bss_size,
227236
)
228237
segment.is_auto_segment = is_auto_segment
229238

@@ -275,6 +284,10 @@ def parse_subsegments(self, segment_yaml) -> List[Segment]:
275284

276285
return ret
277286

287+
@property
288+
def needs_symbols(self) -> bool:
289+
return True
290+
278291
def scan(self, rom_bytes):
279292
# Always scan code first
280293
for sub in self.subsegments:

0 commit comments

Comments
 (0)