Skip to content

Add ELF dynamic linking table (SHT_DYNAMIC) support#48

Merged
xoofx merged 11 commits into
xoofx:masterfrom
ProjectSynchro:elf-dynamic-support
Jul 23, 2026
Merged

Add ELF dynamic linking table (SHT_DYNAMIC) support#48
xoofx merged 11 commits into
xoofx:masterfrom
ProjectSynchro:elf-dynamic-support

Conversation

@ProjectSynchro

Copy link
Copy Markdown
Contributor

Supersedes #45 and builds on that initial work. Implements the SHT_DYNAMIC item of #36, and a separate follow-up PR adds DT_NEEDED dependency injection on top of this. I'll update a comment linking to that PR.

This gives the .dynamic section its own ElfDynamicLinkingTable type instead of loading it as a generic section, and fills out the dynamic-linking API and tooling around it.

What's added/finished:

  • ElfDynamicLinkingTable: a dedicated section for SHT_DYNAMIC, wired into the section factory. Reads and writes the dynamic array for 32 and 64-bit and LSB/MSB, with full round-trips.
  • ElfDynamicTag: completed with the tags that show up in binaries (GNU_HASH, VERSYM, VERDEF/VERNEED and their counts, RELACOUNT, FLAGS_1,the RELR group of tags, and the GNU value/address-range tags).
  • String resolution over the linked .dynstr: StringTable, IsStringValueTag, TryGetString and GetNeededLibraries() turn DT_NEEDED/DT_SONAME/DT_RPATH/DT_RUNPATH offsets into names.
  • Standalone Read(Stream)/Write(Stream) on the dynamic table, following the ElfFile/PEFile convention (happy to drop it if you'd prefer sections stay pipeline-only).
  • readelf -d-style printer (ElfPrinter.PrintDynamicSections); output matches readelf -d byte for byte on the test binaries.

Also fixed an issue with section truncation I noticed:

  • 64-bit section-header field truncation. The Elf64_Shdr writer cast sh_addr/sh_offset/sh_size/sh_addralign/sh_entsize to uint, silently truncating any value >= 4 GiB. It now writes the full 64-bit values (output for sub-4 GiB values is unchanged).

Happy to split this into its own PR if you'd prefer.

Test coverage (tests the earlier draft didn't have yet):

  • Build and round-trip of a dynamic table from entries, 32 and 64-bit.
  • String-resolution API and standalone stream I/O.
  • Printer snapshots for the real test binaries, verified against readelf -d.
  • Regression for the 64-bit section-header truncation.

kiwidoggie and others added 11 commits July 18, 2026 18:07
(cherry picked from commit bf73964)
PR xoofx#45 added editable dynamic-section entries but left no public way to append
a string to a string table. A new DT_NEEDED entry stores a raw .dynstr offset,
so referencing a library name that is not already in the table was impossible.
Exposes GetOrCreateString, which appends the string (or reuses an existing copy)
and returns its byte offset.
The enum only defined tags through DT_PREINIT_ARRAYSZ, so tags that
appear in ordinary shared objects and executables (GNU_HASH, VERSYM,
VERDEF/VERNEED and their counts, RELACOUNT, FLAGS_1, the RELR family,
and the GNU value/address range tags) had no named members and could
only be handled as raw numbers. Adds those tags, mapped to the existing
ElfNative constants, so callers and the printer can name them.
The Stream property on ElfDynamicLinkingTable was never wired into
Read/Write (both go through Entries), so it was dead: it held an empty
MemoryStream that was neither populated on load nor written on save.
There was also no way to resolve a DT_NEEDED/DT_SONAME offset back to a
library name, nor to serialize the entries on their own.

Removes the dead Stream property and adds a resolution API over the
linked .dynstr (StringTable, IsStringValueTag, TryGetString,
GetNeededLibraries, AddNeededLibrary) that keeps entries as raw offsets.
Adds standalone Read(Stream)/Write(Stream) that (de)serialize Entries
through the ElfReader/ElfWriter factories, inferring the file class and
byte order from the parent ElfFile.
PrintDynamicSections was a stub that always wrote "There is no dynamic
section in this file", even for files that had one, so ElfPrinter output
diverged from readelf for any dynamic binary. Implements the dump: one
line per entry up to the DT_NULL terminator, with the tag name and a
value rendered per tag (library names via the linked .dynstr, sizes as
bytes, PLTREL as REL/RELA, DF_/DF_1_ flag names, and addresses as hex).
Output matches readelf -d byte for byte on the test binaries.

- src/LibObjectFile/Elf/ElfPrinter.cs: dump plus tag-name and value formatters
- src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=*.verified.txt: regenerated snapshots now show the dynamic section
Nothing exercised the dynamic table outside the real-binary snapshots:
building one from entries, resolving needed libraries, the standalone
stream round-trip, and the 32-bit Read32/Write32 path were all untested.
Adds SimpleDynamicSection (build, resolve, full-file round-trip),
DynamicSectionStreamRoundTrip, and DynamicSection32Bit (Elf32_Dyn
records plus the printer's 8-digit tag hex).
The 64-bit section header writer cast sh_addr, sh_offset, sh_size,
sh_addralign and sh_entsize to uint, so any value >= 4 GiB (high load
addresses, large NOBITS sections) was silently truncated on write.
Passes the full ulong values to the matching Elf64 encode overloads.
Output for sub-4 GiB values is unchanged.

- src/LibObjectFile/Elf/ElfSectionHeaderTable.cs: drop the uint casts on the 64-bit Elf64_Shdr fields
- src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs: regression round-tripping a NOBITS section with a >4 GiB address and size
The ELF section list did not mention the dynamic linking table. Adds it
to the supported sections.
@xoofx
xoofx merged commit 0b89af9 into xoofx:master Jul 23, 2026
1 check failed
@xoofx

xoofx commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Thanks! I will fix the build in a separate commit.

@xoofx xoofx mentioned this pull request Jul 23, 2026
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.

3 participants