Skip to content

Add MIPS HI16/LO16 relocation fixtures with RELA addends - #210

Open
zardus wants to merge 2 commits into
masterfrom
feature/mips-rela-hilo-fixtures
Open

Add MIPS HI16/LO16 relocation fixtures with RELA addends#210
zardus wants to merge 2 commits into
masterfrom
feature/mips-rela-hilo-fixtures

Conversation

@zardus

@zardus zardus commented Aug 28, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Problem

MIPS R_MIPS_HI16/R_MIPS_LO16 reach a loader in two spellings, and only one is represented here. The only HI16/LO16 object in the repository, tests/mips/mips-hilo.o, is o32 and therefore SHT_REL, where the addend is read out of the instruction. Searching every MIPS object under tests/:

searched 736 ELF objects under tests/ for: EM_MIPS object carrying R_MIPS_HI16 in an SHT_RELA section
0 match

So the SHT_RELA path — where the addend is in the relocation entry and the pairing rule is different — has no input at all, and neither does the case of a %hi whose %lo never follows it.

Root cause

n64 and n32 assemble to SHT_RELA and o32 to SHT_REL; every MIPS fixture here is o32, so the RELA form never appeared.

Fix

Add three objects built from one assembly source, so the same five relocations reach a loader as Elf64_Rela, Elf32_Rela and Elf32_Rel:

    tests/mips64/mips64-hilo-rela.o   --  .rela.text is SHT_RELA with 3 R_MIPS_HI16 and 2 R_MIPS_LO16
    tests/mips64/mipsn32-hilo-rela.o  --  .rela.text is SHT_RELA with 3 R_MIPS_HI16 and 2 R_MIPS_LO16

plus tests/mips/mips-hilo-unpaired.o, the o32 SHT_REL build of the same source. Each holds two matched %hi/%lo pairs and a trailing %hi with no %lo after it, which is the arrangement a consumer that insists on pairing cannot handle. Source and generate script are in tests_src/relocs/mips.

Testing

The generate script, run with GNU as 2.46 from nixpkgs, reproduces all three objects byte for byte, so the recipe and the artifacts agree. Section types, e_flags and the relocation lists were read back with pyelftools. All three raise StopIteration on cle master today and load with the expected bytes on the consumer branch; that comparison and the digests are in the validation record.

Validation: #210 (comment)

sync: angr/cle#722

session: sharpen

zardus added a commit to angr/cle that referenced this pull request Aug 28, 2026
R_MIPS_HI16.relocate ran the REL algorithm unconditionally: it recovered the low
half of the addend from the immediate field of a matching R_MIPS_LO16, found
with a next() that had no default. A RELA object carries its whole addend in
r_addend and leaves those immediate fields zero, so the search was both
unnecessary and, whenever a %hi had no later %lo against the same symbol, fatal.
The bare StopIteration escaped cle.Loader.__init__ with no message and is not a
CLEError; under PEP 479 a generator between the two turns it into an unrelated
RuntimeError. A corpus sweep hit this on 3523 real MIPS kernel modules, the
usual shape being a lui in a branch delay slot whose %lo sits at the branch
target, earlier in the relocation table.

Both relocations now read r_addend when the entry came from a RELA section and
only consult the pair for REL, where the ABI does require the R_MIPS_LO16 to
follow. That fixes the value as well: deriving from GenericAbsoluteReloc dropped
r_addend, so a RELA R_MIPS_LO16 wrote S & 0xffff where it owed (S + A) & 0xffff,
and R_MIPS_HI16 wrote %high(S) where it owed %high(S + A). A REL object with a
genuinely missing partner now logs a warning and leaves the instruction alone
instead of aborting the load.

The tests load three assembled objects added in angr/binaries#210, one per MIPS
ABI, so the same relocations are exercised as Elf64_Rela, Elf32_Rela and
Elf32_Rel. Only a real assembler can produce the arrangement this fix is about,
since the defect is that a shape no synthetic RELA reproduces reaches the loader.
zardus added a commit to angr/cle that referenced this pull request Aug 28, 2026
R_MIPS_HI16.relocate ran the REL algorithm unconditionally: it recovered the low
half of the addend from the immediate field of a matching R_MIPS_LO16, found
with a next() that had no default. A RELA object carries its whole addend in
r_addend and leaves those immediate fields zero, so the search was both
unnecessary and, whenever a %hi had no later %lo against the same symbol, fatal.
The bare StopIteration escaped cle.Loader.__init__ with no message and is not a
CLEError; under PEP 479 a generator between the two turns it into an unrelated
RuntimeError. A corpus sweep hit this on 3523 real MIPS kernel modules, the
usual shape being a lui in a branch delay slot whose %lo sits at the branch
target, earlier in the relocation table.

Both relocations now read r_addend when the entry came from a RELA section and
only consult the pair for REL, where the ABI does require the R_MIPS_LO16 to
follow. That fixes the value as well: deriving from GenericAbsoluteReloc dropped
r_addend, so a RELA R_MIPS_LO16 wrote S & 0xffff where it owed (S + A) & 0xffff,
and R_MIPS_HI16 wrote %high(S) where it owed %high(S + A). A REL object with a
genuinely missing partner now logs a warning and leaves the instruction alone
instead of aborting the load.

The tests load three assembled objects added in angr/binaries#210, one per MIPS
ABI, so the same relocations are exercised as Elf64_Rela, Elf32_Rela and
Elf32_Rel. Only a real assembler can produce the arrangement this fix is about,
since the defect is that a shape no synthetic RELA reproduces reaches the loader.
@zardus

zardus commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head 9b2b0338b78fbb02898bce1f4fcdfac6e3215ce4 against baseline 3bfd1de09dd357deafbe29b4075aa55a9379ce50. Three objects added, no existing file touched.

Built with GNU as 2.46 from nixpkgs pkgsCross.mips64el-linux-gnuabin32, which assembles all three ABIs from one binary, run from tests_src/relocs/mips:

mips64el-unknown-linux-gnuabin32-as -EB -64  -march=mips64r2 -mno-pdr -o ../../../tests/mips64/mips64-hilo-rela.o  mips-hilo-rela.s
mips64el-unknown-linux-gnuabin32-as -EB -n32 -march=mips64r2 -mno-pdr -o ../../../tests/mips64/mipsn32-hilo-rela.o mips-hilo-rela.s
mips64el-unknown-linux-gnuabin32-as -EB -32  -march=mips32r2 -mno-pdr -o ../../../tests/mips/mips-hilo-unpaired.o  mips-hilo-rela.s

mips-hilo-generate.sh runs exactly those. Re-running it over the committed objects leaves all three sha256 digests unchanged, so the artifact is reproducible from the source in this PR. The -32 build prints Warning: can't find matching low-part relocation for %hi operator; that is the assembler naming the property the fixture exists for, and it emits the object regardless.

Container shape read back out of the files with pyelftools rather than taken from the target triple:

path class endian e_flags reloc sections bytes sha256
tests/mips64/mips64-hilo-rela.o ELFCLASS64 big 0x80000001 .rela.text SHT_RELA 1,416 01168b8e67627a9716032b45f589ce05c9001c9c1b1a0a21568b7541b9ca6149
tests/mips64/mipsn32-hilo-rela.o ELFCLASS32 big 0x80000021 .rela.text SHT_RELA 972 855f85303e3d3ec51d6529454cd8fff81c2e4db5bc7a516bd1dab520995267cd
tests/mips/mips-hilo-unpaired.o ELFCLASS32 big 0x70001001 .rel.text SHT_REL 948 9b486889ae99f633d4f9e02fb441bb7270dfb220811bafc6d443b94e02e1b204
  • Neither RELA object carries an SHT_REL section at all, and the n32 one has EF_MIPS_ABI2 (0x20) set in e_flags, which is what distinguishes n32 from o32 inside an ELFCLASS32 container
  • Each object's .rela.text/.rel.text holds five entries: R_MIPS_HI16, R_MIPS_LO16 against .data + 0, R_MIPS_HI16, R_MIPS_LO16 against .bss + 0x8000, and a final R_MIPS_HI16 against .bss + 0xc004 with no R_MIPS_LO16 after it. .bss is NOBITS, so the 0xc010-byte gap costs no file space
  • Loaded at 0x21000 those relocations build 0x21020, 0x29030 and 0x2d034. The middle one has bit 15 set, so its low halfword sign-extends negative and the lui has to load one more than the plain high half

Consumer check, with cle's tests/test_mips_relocations.py from angr/cle#722 and angr/binaries at this branch: all three objects raise a bare StopIteration from find_matching_lo16_relocation on cle master a4fb8003198229d33c84df6a82f749729232fd31, and all three load with the expected bytes on that pull request's head. A sweep of every ELF under tests/mips, tests/mipsel and tests/mips64 — 55 files with these three added — loads to a byte-identical memory image on both revisions except these three, which go from StopIteration to loading.

This repository has no executable suite of its own; the fixtures are exercised by angr/cle#722.

session: mega-corpus

@zardus

zardus commented Aug 28, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Searching every EM_MIPS object under tests/ for R_MIPS_HI16 in an SHT_RELA section, before and after this change.

Before — every HI16/LO16 object here is o32, and therefore SHT_REL; the RELA spelling has no coverage:

angr/binaries at the merge base
angr/binaries at baseline 3bfd1de
searched 736 ELF objects under tests/ for: EM_MIPS object carrying R_MIPS_HI16 in an SHT_RELA section
0 match

After — the same five relocations in Elf64_Rela and Elf32_Rela form:

with this change
angr/binaries at head 9b2b033
searched 739 ELF objects under tests/ for: EM_MIPS object carrying R_MIPS_HI16 in an SHT_RELA section
2 match
    tests/mips64/mips64-hilo-rela.o  --  .rela.text is SHT_RELA with 3 R_MIPS_HI16 and 2 R_MIPS_LO16
    tests/mips64/mipsn32-hilo-rela.o  --  .rela.text is SHT_RELA with 3 R_MIPS_HI16 and 2 R_MIPS_LO16

cle's tests for R_MIPS_HI16 and R_MIPS_LO16 had one object to work from,
tests/mips/mips-hilo.o, which is o32 and therefore SHT_REL. Nothing here
covered the SHT_RELA spelling of the same pair, so the tests that wanted
it were rewriting mips-hilo.o's section headers in memory.

Three objects assembled from tests_src/relocs/mips/mips-hilo-rela.s, one
per ABI, so the same code appears as Elf64_Rela, Elf32_Rela and Elf32_Rel:

  tests/mips64/mips64-hilo-rela.o   ELFCLASS64, n64, SHT_RELA
  tests/mips64/mipsn32-hilo-rela.o  ELFCLASS32, n32, SHT_RELA
  tests/mips/mips-hilo-unpaired.o   ELFCLASS32, o32, SHT_REL

Each holds two matched %hi/%lo pairs and a trailing %hi whose %lo never
follows it, the arrangement a consumer that insists on pairing cannot
handle. One pair points 0x8000 past its section symbol, so the low half
of the address is negative once sign-extended and the high half has to
compensate.

Built with GNU as 2.46 from nixpkgs pkgsCross.mips64el-linux-gnuabin32;
tests_src/relocs/mips/mips-hilo-generate.sh has the exact invocations and
reproduces all three byte for byte.
@zardus
zardus force-pushed the feature/mips-rela-hilo-fixtures branch from 9b2b033 to 1d6e87b Compare August 29, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant