From f030992c67933947684d1c769a1c2ee834034cc2 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Thu, 3 Sep 2026 22:35:12 -0600 Subject: [PATCH] =?UTF-8?q?fix(psyq=5Fidentify):=20read=20.text=20bytes,?= =?UTF-8?q?=20not=20objdump's=20rendering=20=E2=80=94=2010=20more=20object?= =?UTF-8?q?s=20located?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit obj_text_pattern parsed ONE WORD PER DISASSEMBLY LINE, and objdump collapses a run of identical words into a single `...` line. Every collapsed word was silently missing from the pattern, so from the first run onward the pattern was MISALIGNED against the image and find() returned None -- printed as the confident, wrong sentence "not linked by EXE". Measured on 2D_BG0.o (libgs): 3 `...` lines, 520 words parsed for a 526-word object. It was listed as absent while 507 of its 507 non-relocated words match the EXE exactly at 0x8005080C. Any object whose .text holds a run of >=3 identical words was invisible -- to the map the entire library-linking pipeline consumes for placement. That is why 2D_BG0.o was never linked: not excluded by a reason, just invisible. It sits in config/splat.us.exe.yaml under a scattered-.bss exclusion that cannot apply to it, since the object has no .bss section at all. Reading the section bytes and taking relocation offsets from `objdump -r` removes the pretty-printer from the loop (R33). MEASURED: libgs goes from 36/201 to 46/201 objects located. --- tools/psyq_identify.py | 56 ++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/tools/psyq_identify.py b/tools/psyq_identify.py index acda4269a..12c304fd5 100644 --- a/tools/psyq_identify.py +++ b/tools/psyq_identify.py @@ -34,33 +34,41 @@ text = b[TLO - VRAM_BASE: THI - VRAM_BASE] twords = [struct.unpack_from("> 26) in (2, 3) else 0xFFFFFFFF) - elif "R_MIPS" in line and words: - # relocation annotation follows its instruction line -> mask low 16 (hi/lo/pc16) - if "_26" in line: - mask[-1] = 0 - else: - mask[-1] = 0xFFFF0000 + WHY NOT `objdump -dr` (P31 S77). The first version parsed one word per DISASSEMBLY LINE, and + objdump COLLAPSES a run of identical words into a single `...` line. Every collapsed word was + silently missing from the pattern, so from the first run onward the pattern was MISALIGNED + against the image and `find` returned None -- reported as the confident, wrong sentence + "not linked by EXE". + + Measured on `2D_BG0.o` (libgs): 3 `...` lines, **520 words parsed for a 526-word object**, so + it was listed as absent while 507 of its 507 non-relocated words match the EXE exactly at + 0x8005080C. It had been excluded from the LINKED build under a `.bss` reason that cannot even + apply to it -- the object has no `.bss` section at all. Any object whose `.text` holds a run of + >=3 identical words was invisible to this map, which is the map the whole library-linking + pipeline consumes. + + Reading the section bytes and taking relocation offsets from `objdump -r` removes the rendering + from the loop entirely (R33: derive from the bytes, do not re-parse a pretty-printer).""" + h = subprocess.run(["mipsel-linux-gnu-objdump", "-h", o], capture_output=True, text=True).stdout + m = re.search(r"^\s*\d+\s+\.text\s+([0-9a-f]+)\s+\S+\s+\S+\s+([0-9a-f]+)", h, re.M) + if not m: + return [], [] # data-only object (e.g. libgs GLOBAL.o) + size, off = int(m.group(1), 16), int(m.group(2), 16) + blob = open(o, "rb").read()[off:off + size] + words = list(struct.unpack("<%dI" % (len(blob) // 4), blob[:len(blob) // 4 * 4])) + mask = [0 if (w >> 26) in (2, 3) else 0xFFFFFFFF for w in words] # jal/j always link-resolved + rr = subprocess.run(["mipsel-linux-gnu-objdump", "-r", o], capture_output=True, text=True).stdout + body = re.search(r"RELOCATION RECORDS FOR \[\.text\]:(.*?)(?=\nRELOCATION RECORDS|\Z)", rr, re.S) + if body: + for rm in re.finditer(r"^([0-9a-f]+)\s+(R_MIPS_\w+)", body.group(1), re.M): + i = int(rm.group(1), 16) // 4 + if 0 <= i < len(mask): + mask[i] = 0 if "_26" in rm.group(2) else 0xFFFF0000 return words, mask + def find(words, mask): n = len(words) if n < 2: