From 06604585e9a009302b04ff65c68901a89e4f4f96 Mon Sep 17 00:00:00 2001 From: Anghelo Carvajal Date: Thu, 21 Nov 2024 03:05:10 -0300 Subject: [PATCH] `asmtu` segment (#421) * Remove redundant `asm` segments * create asmtu * Avoid writing sections that start with a dot * Hack to avoid bss not being generated until the bug is fixed upstream * docs * Update tests and run black * Remove hack --- CHANGELOG.md | 8 +++ README.md | 2 +- docs/Configuration.md | 4 +- docs/Segments.md | 24 ++++++++ pyproject.toml | 2 +- src/splat/__init__.py | 2 +- src/splat/segtypes/common/asm.py | 23 ++++++- src/splat/segtypes/common/asmtu.py | 65 ++++++++++++++++++++ src/splat/segtypes/common/bss.py | 5 ++ src/splat/segtypes/common/data.py | 5 +- src/splat/segtypes/common/rodata.py | 3 + src/splat/segtypes/n64/__init__.py | 2 - src/splat/segtypes/n64/asm.py | 28 --------- src/splat/segtypes/n64/hasm.py | 28 --------- src/splat/segtypes/ps2/__init__.py | 1 - src/splat/segtypes/ps2/asm.py | 23 ------- src/splat/segtypes/psx/__init__.py | 1 - src/splat/segtypes/psx/asm.py | 23 ------- src/splat/util/options.py | 6 +- test/basic_app/expected/asm/data/main.bss.s | 2 +- test/basic_app/expected/asm/data/main.data.s | 2 +- test/basic_app/expected/asm/handwritten.s | 7 +-- 22 files changed, 145 insertions(+), 121 deletions(-) create mode 100644 src/splat/segtypes/common/asmtu.py delete mode 100644 src/splat/segtypes/n64/asm.py delete mode 100644 src/splat/segtypes/n64/hasm.py delete mode 100644 src/splat/segtypes/ps2/asm.py delete mode 100644 src/splat/segtypes/psx/asm.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c9e2650..3938b338 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # splat Release Notes +### 0.30.0 + +* New `asmtu` segment type. + * Allows writing the disassembly of every section for a given object into the same assembly file. + * Useful for dealing with symbols with local visibility. +* Cleanup some redundant code regarding duplicated `asm` segments. +* The global option `add_set_gp_64` now defaults to `False` on psx and ps2 platforms. + ### 0.29.0 * Fix bss/sbss asm files not being generated. diff --git a/README.md b/README.md index 437164d6..8c120d92 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ The brackets corresponds to the optional dependencies to install while installin If you use a `requirements.txt` file in your repository, then you can add this library with the following line: ```txt -splat64[mips]>=0.29.0,<1.0.0 +splat64[mips]>=0.30.0,<1.0.0 ``` ### Optional dependencies diff --git a/docs/Configuration.md b/docs/Configuration.md index 00b5304c..3bc85a65 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -610,7 +610,9 @@ Determines whether functions inside c files should have named registers ### add_set_gp_64 -Determines whether to add ".set gp=64" to asm/hasm files +Determines whether to add ".set gp=64" to asm/hasm files. + +Defaults to `False` on psx and ps2 platforms, `True` for every other platform. ### create_asm_dependencies diff --git a/docs/Segments.md b/docs/Segments.md index d65d2d41..77561a3b 100644 --- a/docs/Segments.md +++ b/docs/Segments.md @@ -43,6 +43,30 @@ Hand-written Assembly, `hasm`, similar to `asm` except it will not overwrite any start: 0xABC ``` +### `asmtu` + +**Description:** + +Allows disassembling every section of an object that share the same name into the same assembly file. +This is a better parallel to how an object is compiled from a [TU](https://en.wikipedia.org/wiki/Translation_unit_(programming)) than disassembling each section to individual assembly files. + +This is specially useful when dealing with symbols that may not be globally visible (locally binded symbols), because those symbols should be visible to the whole TU but disassembling each section individually disallows this visibility. + +This segment requires that every other segment that shares the same name must have their segment type be prefixed with a dot. + +```yaml + subsegments: + # ... + - [0x000100, asmtu, code/allai] + # ... + - [0x324680, .data, code/allai] # Note `.data` instead of `data` + # ... + - [0x350100, .rodata, code/allai] + # ... + - { type: .bss, vram: 0x004B10C8, name: code/allai } + # ... +``` + ## `bin` **Description:** diff --git a/pyproject.toml b/pyproject.toml index 2b5bc094..2c022071 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "splat64" # Should be synced with src/splat/__init__.py -version = "0.29.0" +version = "0.30.0" description = "A binary splitting tool to assist with decompilation and modding projects" readme = "README.md" license = {file = "LICENSE"} diff --git a/src/splat/__init__.py b/src/splat/__init__.py index 02b8c423..f9ba189d 100644 --- a/src/splat/__init__.py +++ b/src/splat/__init__.py @@ -1,7 +1,7 @@ __package_name__ = __name__ # Should be synced with pyproject.toml -__version__ = "0.29.0" +__version__ = "0.30.0" __author__ = "ethteck" from . import util as util diff --git a/src/splat/segtypes/common/asm.py b/src/splat/segtypes/common/asm.py index b83fe8a3..a9f8edc6 100644 --- a/src/splat/segtypes/common/asm.py +++ b/src/splat/segtypes/common/asm.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Optional +from typing import Optional, List from ...util import options @@ -25,8 +25,25 @@ def scan(self, rom_bytes: bytes): ): self.scan_code(rom_bytes) - def get_file_header(self): - return [] + def get_file_header(self) -> List[str]: + ret = [] + + ret.append('.include "macro.inc"') + ret.append("") + ret.append(".set noat") # allow manual use of $at + ret.append(".set noreorder") # don't insert nops after branches + if options.opts.add_set_gp_64: + ret.append(".set gp=64") # allow use of 64-bit general purpose registers + ret.append("") + preamble = options.opts.generated_s_preamble + if preamble: + ret.append(preamble) + ret.append("") + + ret.append(self.get_section_asm_line()) + ret.append("") + + return ret def split(self, rom_bytes: bytes): if not self.rom_start == self.rom_end and self.spim_section is not None: diff --git a/src/splat/segtypes/common/asmtu.py b/src/splat/segtypes/common/asmtu.py new file mode 100644 index 00000000..c726571b --- /dev/null +++ b/src/splat/segtypes/common/asmtu.py @@ -0,0 +1,65 @@ +from pathlib import Path +from typing import Optional, TextIO + +from ...util import log, options + +from .asm import CommonSegAsm +from .codesubsegment import CommonSegCodeSubsegment + + +class CommonSegAsmtu(CommonSegAsm): + def split(self, rom_bytes: bytes): + if self.rom_start == self.rom_end: + return + + if self.spim_section is None: + return + + out_path = self.out_path() + assert out_path is not None, str(self) + + out_path.parent.mkdir(parents=True, exist_ok=True) + + self.print_file_boundaries() + + with open(out_path, "w", newline="\n") as f: + # Write `.text` contents + for line in self.get_file_header(): + f.write(line + "\n") + f.write(self.spim_section.disassemble()) + + # Disassemble the siblings to this file by respecting the `section_order` + for sect in self.section_order: + if sect == self.get_linker_section_linksection(): + continue + + sibling = self.siblings.get(sect) + if sibling is None: + continue + + if ( + isinstance(sibling, CommonSegCodeSubsegment) + and sibling.spim_section is not None + and not sibling.should_split() + ): + f.write("\n") + f.write(f"{sibling.get_section_asm_line()}\n\n") + f.write(sibling.spim_section.disassemble()) + + # Another loop to check anything that somehow may not be present on the `section_order` + for sect, sibling in self.siblings.items(): + if sect == self.get_linker_section_linksection(): + continue + + if sect in self.section_order: + # Already handled on the above loop + continue + + if ( + isinstance(sibling, CommonSegCodeSubsegment) + and sibling.spim_section is not None + and not sibling.should_split() + ): + f.write("\n") + f.write(f"{sibling.get_section_asm_line()}\n\n") + f.write(sibling.spim_section.disassemble()) diff --git a/src/splat/segtypes/common/bss.py b/src/splat/segtypes/common/bss.py index a411d88b..0f71c214 100644 --- a/src/splat/segtypes/common/bss.py +++ b/src/splat/segtypes/common/bss.py @@ -1,3 +1,5 @@ +from typing import Optional + from ...util import options, symbols, log from .data import CommonSegData @@ -11,6 +13,9 @@ class CommonSegBss(CommonSegData): def get_linker_section(self) -> str: return ".bss" + def get_section_flags(self) -> Optional[str]: + return "wa" + @staticmethod def is_data() -> bool: if not options.opts.ld_bss_is_noload: diff --git a/src/splat/segtypes/common/data.py b/src/splat/segtypes/common/data.py index 58e04ef2..ba252bd5 100644 --- a/src/splat/segtypes/common/data.py +++ b/src/splat/segtypes/common/data.py @@ -73,7 +73,7 @@ def should_scan(self) -> bool: return True def should_split(self) -> bool: - return True + return not self.type.startswith(".") def cache(self): return [CommonSegCodeSubsegment.cache(self), CommonSegGroup.cache(self)] @@ -81,6 +81,9 @@ def cache(self): def get_linker_section(self) -> str: return ".data" + def get_section_flags(self) -> Optional[str]: + return "wa" + def get_linker_entries(self): return CommonSegCodeSubsegment.get_linker_entries(self) diff --git a/src/splat/segtypes/common/rodata.py b/src/splat/segtypes/common/rodata.py index 241976a4..b5bc0a96 100644 --- a/src/splat/segtypes/common/rodata.py +++ b/src/splat/segtypes/common/rodata.py @@ -15,6 +15,9 @@ class CommonSegRodata(CommonSegData): def get_linker_section(self) -> str: return ".rodata" + def get_section_flags(self) -> Optional[str]: + return "a" + @staticmethod def is_data() -> bool: return False diff --git a/src/splat/segtypes/n64/__init__.py b/src/splat/segtypes/n64/__init__.py index b55f213c..70eb16e2 100644 --- a/src/splat/segtypes/n64/__init__.py +++ b/src/splat/segtypes/n64/__init__.py @@ -1,10 +1,8 @@ -from . import asm as asm from . import ci as ci from . import ci4 as ci4 from . import ci8 as ci8 from . import decompressor as decompressor from . import gfx as gfx -from . import hasm as hasm from . import header as header from . import i1 as i1 from . import i4 as i4 diff --git a/src/splat/segtypes/n64/asm.py b/src/splat/segtypes/n64/asm.py deleted file mode 100644 index 91b99728..00000000 --- a/src/splat/segtypes/n64/asm.py +++ /dev/null @@ -1,28 +0,0 @@ -from ...util import options - -from ..common.asm import CommonSegAsm - - -class N64SegAsm(CommonSegAsm): - def get_file_header(self): - ret = [] - - ret.append('.include "macro.inc"') - ret.append("") - ret.append("/* assembler directives */") - ret.append(".set noat /* allow manual use of $at */") - ret.append(".set noreorder /* don't insert nops after branches */") - if options.opts.add_set_gp_64: - ret.append( - ".set gp=64 /* allow use of 64-bit general purpose registers */" - ) - ret.append("") - preamble = options.opts.generated_s_preamble - if preamble: - ret.append(preamble) - ret.append("") - - ret.append(self.get_section_asm_line()) - ret.append("") - - return ret diff --git a/src/splat/segtypes/n64/hasm.py b/src/splat/segtypes/n64/hasm.py deleted file mode 100644 index e46319c3..00000000 --- a/src/splat/segtypes/n64/hasm.py +++ /dev/null @@ -1,28 +0,0 @@ -from ...util import options - -from ..common.hasm import CommonSegHasm - - -class N64SegHasm(CommonSegHasm): - def get_file_header(self): - ret = [] - - ret.append('.include "macro.inc"') - ret.append("") - ret.append("/* assembler directives */") - ret.append(".set noat /* allow manual use of $at */") - ret.append(".set noreorder /* don't insert nops after branches */") - if options.opts.add_set_gp_64: - ret.append( - ".set gp=64 /* allow use of 64-bit general purpose registers */" - ) - ret.append("") - preamble = options.opts.generated_s_preamble - if preamble: - ret.append(preamble) - ret.append("") - - ret.append(self.get_section_asm_line()) - ret.append("") - - return ret diff --git a/src/splat/segtypes/ps2/__init__.py b/src/splat/segtypes/ps2/__init__.py index c0342bc4..e69de29b 100644 --- a/src/splat/segtypes/ps2/__init__.py +++ b/src/splat/segtypes/ps2/__init__.py @@ -1 +0,0 @@ -from . import asm as asm diff --git a/src/splat/segtypes/ps2/asm.py b/src/splat/segtypes/ps2/asm.py deleted file mode 100644 index a8e7680b..00000000 --- a/src/splat/segtypes/ps2/asm.py +++ /dev/null @@ -1,23 +0,0 @@ -from ...util import options - -from ..common.asm import CommonSegAsm - - -class Ps2SegAsm(CommonSegAsm): - def get_file_header(self): - ret = [] - - ret.append('.include "macro.inc"') - ret.append("") - ret.append(".set noat") - ret.append(".set noreorder") - ret.append("") - preamble = options.opts.generated_s_preamble - if preamble: - ret.append(preamble) - ret.append("") - - ret.append(self.get_section_asm_line()) - ret.append("") - - return ret diff --git a/src/splat/segtypes/psx/__init__.py b/src/splat/segtypes/psx/__init__.py index 21a37dc5..c6101214 100644 --- a/src/splat/segtypes/psx/__init__.py +++ b/src/splat/segtypes/psx/__init__.py @@ -1,2 +1 @@ -from . import asm as asm from . import header as header diff --git a/src/splat/segtypes/psx/asm.py b/src/splat/segtypes/psx/asm.py deleted file mode 100644 index 11ba1abd..00000000 --- a/src/splat/segtypes/psx/asm.py +++ /dev/null @@ -1,23 +0,0 @@ -from ...util import options - -from ..common.asm import CommonSegAsm - - -class PsxSegAsm(CommonSegAsm): - def get_file_header(self): - ret = [] - - ret.append('.include "macro.inc"') - ret.append("") - ret.append(".set noat") - ret.append(".set noreorder") - ret.append("") - preamble = options.opts.generated_s_preamble - if preamble: - ret.append(preamble) - ret.append("") - - ret.append(self.get_section_asm_line()) - ret.append("") - - return ret diff --git a/src/splat/util/options.py b/src/splat/util/options.py index ae2e658d..8af16029 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -384,6 +384,10 @@ def parse_endianness() -> Literal["big", "little"]: if platform == "psx": default_ld_bss_is_noload = False + default_add_set_gp_64 = True + if platform in ("psx", "ps2"): + default_add_set_gp_64 = False + ret = SplatOpts( verbose=verbose, dump_symbols=p.parse_opt("dump_symbols", bool, False), @@ -523,7 +527,7 @@ def parse_endianness() -> Literal["big", "little"]: "numeric", ), named_regs_for_c_funcs=p.parse_opt("named_regs_for_c_funcs", bool, True), - add_set_gp_64=p.parse_opt("add_set_gp_64", bool, True), + add_set_gp_64=p.parse_opt("add_set_gp_64", bool, default_add_set_gp_64), create_asm_dependencies=p.parse_opt("create_asm_dependencies", bool, False), string_encoding=p.parse_optional_opt("string_encoding", str), data_string_encoding=p.parse_optional_opt("data_string_encoding", str), diff --git a/test/basic_app/expected/asm/data/main.bss.s b/test/basic_app/expected/asm/data/main.bss.s index 244e606e..ff599a47 100644 --- a/test/basic_app/expected/asm/data/main.bss.s +++ b/test/basic_app/expected/asm/data/main.bss.s @@ -1,6 +1,6 @@ .include "macro.inc" -.section .bss +.section .bss, "wa" glabel D_80000540 /* 80000540 */ .space 0x80 diff --git a/test/basic_app/expected/asm/data/main.data.s b/test/basic_app/expected/asm/data/main.data.s index 262b33f3..f026c79e 100644 --- a/test/basic_app/expected/asm/data/main.data.s +++ b/test/basic_app/expected/asm/data/main.data.s @@ -1,6 +1,6 @@ .include "macro.inc" -.section .data +.section .data, "wa" glabel D_80000500 /* 1100 80000500 00000001 */ .word 0x00000001 diff --git a/test/basic_app/expected/asm/handwritten.s b/test/basic_app/expected/asm/handwritten.s index d2f5f579..b472ca57 100644 --- a/test/basic_app/expected/asm/handwritten.s +++ b/test/basic_app/expected/asm/handwritten.s @@ -1,9 +1,8 @@ .include "macro.inc" -/* assembler directives */ -.set noat /* allow manual use of $at */ -.set noreorder /* don't insert nops after branches */ -.set gp=64 /* allow use of 64-bit general purpose registers */ +.set noat +.set noreorder +.set gp=64 .section .text, "ax"