From e5d6ab840c7cbd3388c6985a971e5c06cef5f4ca Mon Sep 17 00:00:00 2001 From: Anghelo Carvajal Date: Thu, 4 Sep 2025 13:07:51 -0400 Subject: [PATCH 1/6] ld_gp_expression --- docs/Configuration.md | 40 ++++++++++++++++++++++++++++++ src/splat/scripts/create_config.py | 1 + src/splat/segtypes/linker_entry.py | 6 +++-- src/splat/util/options.py | 5 ++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 5fc27b9b..c6ffcbd6 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -155,6 +155,8 @@ Defaults to `False`. The value of the `$gp` register to correctly calculate offset to `%gp_rel` relocs. +It is strongly encouraged to provide a [`ld_gp_expression`](#ld_gp_expression) once the sections of the binary are properly understood. +Not setting this expression makes splat to always emit a hardcoded value for `_gp` in the linker script, preventing proper shiftability. ### check_consecutive_segment_types @@ -615,6 +617,44 @@ Sets the default option for the `bss_contains_common` attribute of all segments. Defaults to `False`. +### ld_gp_expression + +Provides an expression for the `_gp` symbol to be emitted in the generated linker script. + +Most projects start by only setting a [`gp_value`](#gp_value) on their yamls. This is fine while matching the project, but it is bad for shiftable builds, becuase it prevents the `_gp` symbol to shift around as needed. + +This expression is used as-is in the generated linker script, so care must be taken to provide an expression that makes sense for shiftable builds and also matches the original gp value on matching builds. + +Usually this expression is relative to the start of an "small section" (`.sdata`, `.srodata`, `.sbss`, etc.) plus an optional offset (usually `0x7FF0` or `0x8000`). + +The recommended approach to know what to set here is to try to understand where the first small section is, take the difference between the [`gp_value`](#gp_value) and the address of the small section to know what the offset is and then use the linker symbol for the start of that small section. + +For example, say your gp value is `0x00397FF0` and you found the first small section on the rom is a `.sdata` section at vram address `0x00390000` (note __vram__ address, not rom address) which is part of the top-level segment `main`. The difference between those two addresses is `0x7FF0`, so that's your offset. Given this information the expression you want for gp is `main_SDATA_START + 0x7FF0`; where `main` is the top-level segment, `SDATA` comes from the `.sdata` section and the `+ 0x7FF0` is your calculated offset. If you did this process right, then the matching build should still match after setting the gp expression. + +#### Usage + +Not using `ld_gp_expression` makes splat to hardcoded the value: + +```yaml + gp_value: 0x0039DD70 +``` + +```txt + _gp = 0x0039DD70; +``` + +When a `ld_gp_expression` is given then splat is able to emit a non-hardcoded expression for `_gp` without interfering with the disassembly process: + +```yaml + gp_value: 0x0039DD70 + ld_gp_expression: main_SDATA_START + 0x7FF0 +``` + +```txt + _gp = main_SDATA_START + 0x7FF0; +``` + + ## C file options ### create_c_files diff --git a/src/splat/scripts/create_config.py b/src/splat/scripts/create_config.py index 9ab823fe..06e4539a 100644 --- a/src/splat/scripts/create_config.py +++ b/src/splat/scripts/create_config.py @@ -286,6 +286,7 @@ def create_psx_config(exe_path: Path, exe_bytes: bytes): find_file_boundaries: False gp_value: 0x{exe.initial_gp:08X} + # ld_gp_expression: main_SCOMMON_START + 0x7FF0 o_as_suffix: True use_legacy_include_asm: False diff --git a/src/splat/segtypes/linker_entry.py b/src/splat/segtypes/linker_entry.py index 5159abbe..88eb5d7b 100644 --- a/src/splat/segtypes/linker_entry.py +++ b/src/splat/segtypes/linker_entry.py @@ -204,8 +204,10 @@ def __init__(self, is_partial: bool = False): if not self.is_partial: self._writeln(f"__romPos = {options.opts.ld_rom_start};") - if options.opts.gp is not None: - self._writeln("_gp = " + f"0x{options.opts.gp:X};") + if options.opts.ld_gp_expression is not None: + self._writeln(f"_gp = {options.opts.ld_gp_expression};") + elif options.opts.gp is not None: + self._writeln(f"_gp = 0x{options.opts.gp:X};") # Write a series of statements which compute a symbol that represents the highest address among a list of segments' end addresses def write_max_vram_end_sym(self, symbol: str, overlays: List[Segment]): diff --git a/src/splat/util/options.py b/src/splat/util/options.py index 772e1359..201257e2 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -150,6 +150,8 @@ class SplatOpts: ld_generate_symbol_per_data_segment: bool # Sets the default option for the `bss_contains_common` attribute of all segments. ld_bss_contains_common: bool + # + ld_gp_expression: Optional[str] ################################################################################ # C file options @@ -507,6 +509,9 @@ def parse_endianness() -> Literal["big", "little"]: "ld_generate_symbol_per_data_segment", bool, False ), ld_bss_contains_common=p.parse_opt("ld_bss_contains_common", bool, False), + ld_gp_expression=p.parse_optional_opt_with_default( + "ld_gp_expression", str, None + ), create_c_files=p.parse_opt("create_c_files", bool, True), auto_decompile_empty_functions=p.parse_opt( "auto_decompile_empty_functions", bool, True From 24a3efaf1b16ae7b25d370f117b8a19e392383db Mon Sep 17 00:00:00 2001 From: Anghelo Carvajal Date: Thu, 4 Sep 2025 13:12:12 -0400 Subject: [PATCH 2/6] version bump --- CHANGELOG.md | 5 +++++ README.md | 2 +- pyproject.toml | 2 +- src/splat/__init__.py | 2 +- src/splat/util/options.py | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa72f34..9d4191cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # splat Release Notes +### 0.36.0 + +* New yaml options: + * `ld_gp_expression`: Allows setting a custom expression for the `_gp` symbol in the generated linker script, making this symbol to properly shift around instead of hardcoding it to the given `gp_value`. + ### 0.35.2 * Miscellaneous updates to generated macro labels. diff --git a/README.md b/README.md index c22424c8..b241a5a7 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.35.2,<1.0.0 +splat64[mips]>=0.36.0,<1.0.0 ``` ### Optional dependencies diff --git a/pyproject.toml b/pyproject.toml index e2b78806..cfc54839 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "splat64" # Should be synced with src/splat/__init__.py -version = "0.35.2" +version = "0.36.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 0d66e452..9643d292 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.35.2" +__version__ = "0.36.0" __author__ = "ethteck" from . import util as util diff --git a/src/splat/util/options.py b/src/splat/util/options.py index 201257e2..0cea37ec 100644 --- a/src/splat/util/options.py +++ b/src/splat/util/options.py @@ -150,7 +150,7 @@ class SplatOpts: ld_generate_symbol_per_data_segment: bool # Sets the default option for the `bss_contains_common` attribute of all segments. ld_bss_contains_common: bool - # + # Specify an expression to be used for the `_gp` symbol in the generated linker script instead of a hardcoded value. ld_gp_expression: Optional[str] ################################################################################ From 2f20650ef106e1d38cd4e94c451adaaba68c781a Mon Sep 17 00:00:00 2001 From: Anghelo Carvajal Date: Thu, 4 Sep 2025 15:05:35 -0400 Subject: [PATCH 3/6] mention people should not remove the gp_value --- docs/Configuration.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index c6ffcbd6..4c16d435 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -629,7 +629,9 @@ Usually this expression is relative to the start of an "small section" (`.sdata` The recommended approach to know what to set here is to try to understand where the first small section is, take the difference between the [`gp_value`](#gp_value) and the address of the small section to know what the offset is and then use the linker symbol for the start of that small section. -For example, say your gp value is `0x00397FF0` and you found the first small section on the rom is a `.sdata` section at vram address `0x00390000` (note __vram__ address, not rom address) which is part of the top-level segment `main`. The difference between those two addresses is `0x7FF0`, so that's your offset. Given this information the expression you want for gp is `main_SDATA_START + 0x7FF0`; where `main` is the top-level segment, `SDATA` comes from the `.sdata` section and the `+ 0x7FF0` is your calculated offset. If you did this process right, then the matching build should still match after setting the gp expression. +For example, say your gp value is `0x00397FF0` and you found the first small section on the rom is a `.sdata` section at vram address `0x00390000` (note *vram* address, not rom address) which is part of the top-level segment `main`. The difference between those two addresses is `0x7FF0`, so that's your offset. Given this information the expression you want for gp is `main_SDATA_START + 0x7FF0`; where `main` is the top-level segment, `SDATA` comes from the `.sdata` section and the `+ 0x7FF0` is your calculated offset. If you did this process right, then the matching build should still match after setting the gp expression. + +Note you should **not** remove the `gp_value` from your yaml when you set `ld_gp_expression`. `gp_value` is still used for the disassembly, without it you wouldn't get symbolized gp-accesses. #### Usage From 5681d63128393fa6b9d767912ba95bff9f58088b Mon Sep 17 00:00:00 2001 From: Anghelo Carvajal Date: Mon, 8 Sep 2025 15:42:00 -0300 Subject: [PATCH 4/6] Update docs/Configuration.md Co-authored-by: Ethan Roseman --- docs/Configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 4c16d435..2a62838e 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -156,7 +156,7 @@ Defaults to `False`. The value of the `$gp` register to correctly calculate offset to `%gp_rel` relocs. It is strongly encouraged to provide a [`ld_gp_expression`](#ld_gp_expression) once the sections of the binary are properly understood. -Not setting this expression makes splat to always emit a hardcoded value for `_gp` in the linker script, preventing proper shiftability. +Not setting this expression makes splat always emit a hardcoded value for `_gp` in the linker script, preventing proper shiftability. ### check_consecutive_segment_types From c78f445b3a836736a068ff32c31f0d533aed53f3 Mon Sep 17 00:00:00 2001 From: Anghelo Carvajal Date: Mon, 8 Sep 2025 15:42:06 -0300 Subject: [PATCH 5/6] Update docs/Configuration.md Co-authored-by: Ethan Roseman --- docs/Configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index 2a62838e..d4396732 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -631,7 +631,7 @@ The recommended approach to know what to set here is to try to understand where For example, say your gp value is `0x00397FF0` and you found the first small section on the rom is a `.sdata` section at vram address `0x00390000` (note *vram* address, not rom address) which is part of the top-level segment `main`. The difference between those two addresses is `0x7FF0`, so that's your offset. Given this information the expression you want for gp is `main_SDATA_START + 0x7FF0`; where `main` is the top-level segment, `SDATA` comes from the `.sdata` section and the `+ 0x7FF0` is your calculated offset. If you did this process right, then the matching build should still match after setting the gp expression. -Note you should **not** remove the `gp_value` from your yaml when you set `ld_gp_expression`. `gp_value` is still used for the disassembly, without it you wouldn't get symbolized gp-accesses. +Note you should **not** remove the `gp_value` from your yaml when you set `ld_gp_expression`. `gp_value` is still used for the disassembly; without it you wouldn't get symbolized gp-accesses. #### Usage From bc03998c06fcfcbc03fcfac82fbe981f1006fd13 Mon Sep 17 00:00:00 2001 From: Anghelo Carvajal Date: Mon, 8 Sep 2025 15:42:15 -0300 Subject: [PATCH 6/6] Update docs/Configuration.md Co-authored-by: Ethan Roseman --- docs/Configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Configuration.md b/docs/Configuration.md index d4396732..d0b9ee1e 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -635,7 +635,7 @@ Note you should **not** remove the `gp_value` from your yaml when you set `ld_gp #### Usage -Not using `ld_gp_expression` makes splat to hardcoded the value: +Not using `ld_gp_expression` makes splat hardcode the value: ```yaml gp_value: 0x0039DD70