From 42e8fb903d2b9fb74c0edfcd8946177745ee0504 Mon Sep 17 00:00:00 2001 From: kiwidoggie Date: Tue, 23 Dec 2025 15:39:42 -0500 Subject: [PATCH 01/11] Add Elf dynamic support (cherry picked from commit bf73964b3acf6c9df302389ccda18264985324cf) --- .../Elf/ElfSectionHeaderTable.cs | 1 + src/LibObjectFile/Elf/Sections/ElfDynamic.cs | 32 +++ .../Elf/Sections/ElfDynamicLinkingTable.cs | 158 ++++++++++++++ .../Elf/Sections/ElfDynamicTag.cs | 202 ++++++++++++++++++ 4 files changed, 393 insertions(+) create mode 100644 src/LibObjectFile/Elf/Sections/ElfDynamic.cs create mode 100644 src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs create mode 100644 src/LibObjectFile/Elf/Sections/ElfDynamicTag.cs diff --git a/src/LibObjectFile/Elf/ElfSectionHeaderTable.cs b/src/LibObjectFile/Elf/ElfSectionHeaderTable.cs index dd17f04..0185874 100644 --- a/src/LibObjectFile/Elf/ElfSectionHeaderTable.cs +++ b/src/LibObjectFile/Elf/ElfSectionHeaderTable.cs @@ -133,6 +133,7 @@ private static ElfSection CreateElfSection(ElfReader reader, uint sectionIndex, ElfSectionType.Note => new ElfNoteTable(), ElfSectionType.SymbolTableSectionHeaderIndices => new ElfSymbolTableSectionHeaderIndices(), ElfSectionType.NoBits => new ElfNoBitsSection(), + ElfSectionType.DynamicLinking => new ElfDynamicLinkingTable(), _ => new ElfStreamSection(sectionType) }; } diff --git a/src/LibObjectFile/Elf/Sections/ElfDynamic.cs b/src/LibObjectFile/Elf/Sections/ElfDynamic.cs new file mode 100644 index 0000000..554e435 --- /dev/null +++ b/src/LibObjectFile/Elf/Sections/ElfDynamic.cs @@ -0,0 +1,32 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +namespace LibObjectFile.Elf +{ + /// + /// Dynamic table entry for ELF. + /// + public record struct ElfDynamic + { + /// + /// Type of dynamic table entry. + /// + public long Tag { get; set; } + + /// + /// Integer value of entry. + /// + public ulong Value { get; set; } + + /// + /// Pointer value of entry. + /// + public ulong Pointer => Value; + + /// + /// Dynamic Tag + /// + public ElfDynamicTag TagType => (ElfDynamicTag)Tag; + } +} \ No newline at end of file diff --git a/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs b/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs new file mode 100644 index 0000000..1f05c10 --- /dev/null +++ b/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs @@ -0,0 +1,158 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using LibObjectFile.Diagnostics; +using LibObjectFile.IO; + +namespace LibObjectFile.Elf +{ + public class ElfDynamicLinkingTable : ElfSection + { + private Stream _stream; + private bool _is32; + + public Stream Stream + { + get => _stream; + set + { + ArgumentNullException.ThrowIfNull(value); + _stream = value; + Size = (ulong)_stream.Length; + } + } + + public List Entries { get; } + + public ElfDynamicLinkingTable() : base(ElfSectionType.DynamicLinking) + { + Name = ElfSectionSpecialType.Dynamic.GetDefaultName(); + Flags = ElfSectionSpecialType.Dynamic.GetSectionFlags(); + _stream = new MemoryStream(); + + Entries = []; + } + + public override void Read(ElfReader reader) + { + reader.Position = Position; + Entries.Clear(); + + var numberOfEntries = (int)(base.Size / base.TableEntrySize); + var entries = Entries; + CollectionsMarshal.SetCount(entries, numberOfEntries); + + if (_is32) + Read32(reader, numberOfEntries); + else + Read64(reader, numberOfEntries); + } + + private void Read32(ElfReader reader, int numberOfEntries) + { + using var batch = new BatchDataReader(reader.Stream, numberOfEntries); + var span = CollectionsMarshal.AsSpan(Entries); + ref var entry = ref MemoryMarshal.GetReference(span); + while (batch.HasNext()) + { + ref var dyn = ref batch.Read(); + + entry.Tag = reader.Decode(dyn.d_tag); + entry.Value = reader.Decode(dyn.d_un.d_val); + + entry = ref Unsafe.Add(ref entry, 1); + } + } + + private void Read64(ElfReader reader, int numberOfEntries) + { + using var batch = new BatchDataReader(reader.Stream, numberOfEntries); + var span = CollectionsMarshal.AsSpan(Entries); + ref var entry = ref MemoryMarshal.GetReference(span); + while (batch.HasNext()) + { + ref var dyn = ref batch.Read(); + + entry.Tag = reader.Decode(dyn.d_tag); + entry.Value = reader.Decode(dyn.d_un.d_val); + + entry = ref Unsafe.Add(ref entry, 1); + } + } + + public override void Write(ElfWriter writer) + { + if (_is32) + Write32(writer); + else + Write64(writer); + } + + private void Write32(ElfWriter writer) + { + var entries = CollectionsMarshal.AsSpan(Entries); + using var batch = new BatchDataWriter(writer.Stream, entries.Length); + var dyn = new ElfNative.Elf32_Dyn(); + for (int i = 0; i < entries.Length; i++) + { + ref var entry = ref entries[i]; + + writer.Encode(out dyn.d_tag, (int)entry.Tag); + writer.Encode(out dyn.d_un.d_val, (uint)entry.Value); + + batch.Write(dyn); + } + } + + private void Write64(ElfWriter writer) + { + var entries = CollectionsMarshal.AsSpan(Entries); + using var batch = new BatchDataWriter(writer.Stream, entries.Length); + var dyn = new ElfNative.Elf64_Dyn(); + for (int i = 0; i < entries.Length; i++) + { + ref var entry = ref entries[i]; + + writer.Encode(out dyn.d_tag, entry.Tag); + writer.Encode(out dyn.d_un.d_val, entry.Value); + + batch.Write(dyn); + } + } + + protected override unsafe void ValidateParent(ObjectElement parent) + { + base.ValidateParent(parent); + + var elf = (ElfFile)parent; + _is32 = elf.FileClass == ElfFileClass.Is32; + + BaseTableEntrySize = (uint)(_is32 ? sizeof(ElfNative.Elf32_Dyn) : sizeof(ElfNative.Elf64_Dyn)); + AdditionalTableEntrySize = 0; + } + + internal override unsafe void InitializeEntrySizeFromRead(DiagnosticBag diagnostics, ulong entrySize, bool is32) + { + _is32 = is32; + + if (is32) + throw new NotImplementedException(); + else + { + if (entrySize != (ulong)sizeof(ElfNative.Elf64_Dyn)) + diagnostics.Error(DiagnosticId.ELF_ERR_InvalidSectionEntrySize, $"Invalid size [{entrySize}] for dynamic entry. Expecting to be equal to [{sizeof(ElfNative.Elf64_Dyn)}] bytes."); + else + { + BaseTableEntrySize = (uint)sizeof(ElfNative.Elf64_Dyn); + AdditionalTableEntrySize = (uint)(entrySize - AdditionalTableEntrySize); + } + } + } + } +} \ No newline at end of file diff --git a/src/LibObjectFile/Elf/Sections/ElfDynamicTag.cs b/src/LibObjectFile/Elf/Sections/ElfDynamicTag.cs new file mode 100644 index 0000000..17c7eef --- /dev/null +++ b/src/LibObjectFile/Elf/Sections/ElfDynamicTag.cs @@ -0,0 +1,202 @@ +// Copyright (c) Alexandre Mutel. All rights reserved. +// This file is licensed under the BSD-Clause 2 license. +// See the license.txt file in the project root for more information. + +namespace LibObjectFile.Elf +{ + /// + /// Dynamic table entry tags. + /// + public enum ElfDynamicTag : uint + { + /// + /// Null + /// + Null = ElfNative.DT_NULL, + + /// + /// String table offset of needed library. + /// + Needed = ElfNative.DT_NEEDED, + + /// + /// Size of relocation entries in PLT. + /// + PltRelSz = ElfNative.DT_PLTRELSZ, + + /// + /// Address associated with linkage table. + /// + PltGot = ElfNative.DT_PLTGOT, + + /// + /// Address of symbolic hash table. + /// + Hash = ElfNative.DT_HASH, + + /// + /// Address of dynamic string table. + /// + StrTab = ElfNative.DT_STRTAB, + + /// + /// Address of dynamic symbol table. + /// + SymTab = ElfNative.DT_SYMTAB, + + /// + /// Address of relocation table (Rela entries). + /// + Rela = ElfNative.DT_RELA, + + /// + /// Size of Rela relocation table. + /// + RelaSz = ElfNative.DT_RELASZ, + + /// + /// Size of a Rela relocation entry. + /// + RelaEnt = ElfNative.DT_RELAENT, + + /// + /// Total size of the string table. + /// + StrSz = ElfNative.DT_STRSZ, + + /// + /// Size of a symbol table entry. + /// + SymEnt = ElfNative.DT_SYMENT, + + /// + /// Address of initialization function. + /// + Init = ElfNative.DT_INIT, + + /// + /// Address of termination function. + /// + Fini = ElfNative.DT_FINI, + + /// + /// String table offset of a shared object's name. + /// + SoName = ElfNative.DT_SONAME, + + /// + /// String table offset of library search path. + /// + RPath = ElfNative.DT_RPATH, + + /// + /// Changes symbol resolution algorithm. + /// + Symbolic = ElfNative.DT_SYMBOLIC, + + /// + /// Address of relocation table (Rel entries). + /// + Rel = ElfNative.DT_REL, + + /// + /// Size of Rel relocation table. + /// + RelSz = ElfNative.DT_RELSZ, + + /// + /// Size of a Rel relocation entry. + /// + RelEnt = ElfNative.DT_RELENT, + + /// + /// Type of relocation entry used for linking. + /// + PltRel = ElfNative.DT_PLTREL, + + /// + /// Reserved for debugger. + /// + Debug = ElfNative.DT_DEBUG, + + /// + /// Relocations exist for non-writable segments. + /// + TextRel = ElfNative.DT_TEXTREL, + + /// + /// Address of relocations associated with PLT. + /// + JmpRel = ElfNative.DT_JMPREL, + + /// + /// Process all relocations before execution. + /// + BindNow = ElfNative.DT_BIND_NOW, + + /// + /// Pointer to array of initialization functions. + /// + InitArray = ElfNative.DT_INIT_ARRAY, + + /// + /// Pointer to array of termination functions. + /// + FiniArray = ElfNative.DT_FINI_ARRAY, + + /// + /// Size of . + /// + InitArraySz = ElfNative.DT_INIT_ARRAYSZ, + + /// + /// Size of . + /// + FiniArraySz = ElfNative.DT_FINI_ARRAYSZ, + + /// + /// String table offset of library search path. + /// + RunPath = ElfNative.DT_RUNPATH, + + /// + /// Flags. + /// + Flags = ElfNative.DT_FLAGS, + + /// + /// Values from here to DT_LOOS follow the rules for the interpretation of the d_un union. + /// + Encoding = ElfNative.DT_ENCODING, + + /// + /// Pointer to array of preinit functions. + /// + PreInitArray = ElfNative.DT_PREINIT_ARRAY, + + /// + /// Size of the DT_PREINIT_ARRAY array. + /// + PreInitArraySz = ElfNative.DT_PREINIT_ARRAYSZ, + + /// + /// Start of environment specific tags. + /// + LoOs = ElfNative.DT_LOOS, + + /// + /// End of environment specific tags. + /// + HiOS = ElfNative.DT_HIOS, + + /// + /// Start of processor specific tags. + /// + LoProc = ElfNative.DT_LOPROC, + + /// + /// End of processor specific tags. + /// + HiProc = ElfNative.DT_HIPROC + } +} \ No newline at end of file From 1651a2e1cc2c45a1e8e67fb4bde63edaac1c0852 Mon Sep 17 00:00:00 2001 From: kiwidoggie Date: Tue, 23 Dec 2025 15:44:32 -0500 Subject: [PATCH 02/11] Fix NotImplementedException for 32b elfs (cherry picked from commit c70e1a7d9731774432e1e5b2b197f60950de3653) --- .../Elf/Sections/ElfDynamicLinkingTable.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs b/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs index 1f05c10..ef19012 100644 --- a/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs +++ b/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs @@ -142,7 +142,15 @@ internal override unsafe void InitializeEntrySizeFromRead(DiagnosticBag diagnost _is32 = is32; if (is32) - throw new NotImplementedException(); + { + if (entrySize != (ulong)sizeof(ElfNative.Elf32_Dyn)) + diagnostics.Error(DiagnosticId.ELF_ERR_InvalidSectionEntrySize, $"Invalid size [{entrySize}] for dynamic entry. Expecting to be equal to [{sizeof(ElfNative.Elf32_Dyn)}] bytes."); + else + { + BaseTableEntrySize = (uint)sizeof(ElfNative.Elf32_Dyn); + AdditionalTableEntrySize = (uint)(entrySize - AdditionalTableEntrySize); + } + } else { if (entrySize != (ulong)sizeof(ElfNative.Elf64_Dyn)) From 41bd619f8d7648908fd8390100710896698477ad Mon Sep 17 00:00:00 2001 From: kiwidoggie Date: Tue, 23 Dec 2025 16:13:38 -0500 Subject: [PATCH 03/11] Make ElfStringTable.TryGetString public for use outside of the library (cherry picked from commit 1e985b284a4c039b92210e25fc51c5c8055e5b00) --- src/LibObjectFile/Elf/Sections/ElfStringTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LibObjectFile/Elf/Sections/ElfStringTable.cs b/src/LibObjectFile/Elf/Sections/ElfStringTable.cs index a54d9e0..abe0081 100644 --- a/src/LibObjectFile/Elf/Sections/ElfStringTable.cs +++ b/src/LibObjectFile/Elf/Sections/ElfStringTable.cs @@ -127,7 +127,7 @@ public ElfString Resolve(ElfString name) return newName; } - internal bool TryGetString(uint index, out string text) + public bool TryGetString(uint index, out string text) { if (index == 0) { From f0bdd52336b5e0f4bc418a638dbde81e4097ccd4 Mon Sep 17 00:00:00 2001 From: kiwidoggie Date: Sun, 25 Jan 2026 09:14:26 -0500 Subject: [PATCH 04/11] Fix ElfDynamicLinkingTable not updating size correctly (cherry picked from commit e1fca6377ef3b83e376ae145f67c0cfbc4b4b0ed) --- src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs b/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs index ef19012..268cd92 100644 --- a/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs +++ b/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs @@ -39,6 +39,15 @@ public ElfDynamicLinkingTable() : base(ElfSectionType.DynamicLinking) Entries = []; } + protected override void UpdateLayoutCore(ElfVisitorContext context) + { + base.UpdateLayoutCore(context); + + var numberOfEntries = Entries.Count; + + base.Size = (ulong)numberOfEntries * base.TableEntrySize; + } + public override void Read(ElfReader reader) { reader.Position = Position; From 25449dc7d07ccafd7c2ef959760beee937f20722 Mon Sep 17 00:00:00 2001 From: Jack Greiner Date: Sat, 18 Jul 2026 18:11:45 -0400 Subject: [PATCH 05/11] feat(elf): expose ElfStringTable.GetOrCreateString for appending strings PR #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. --- .../Elf/Sections/ElfStringTable.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/LibObjectFile/Elf/Sections/ElfStringTable.cs b/src/LibObjectFile/Elf/Sections/ElfStringTable.cs index abe0081..0b9a181 100644 --- a/src/LibObjectFile/Elf/Sections/ElfStringTable.cs +++ b/src/LibObjectFile/Elf/Sections/ElfStringTable.cs @@ -92,6 +92,23 @@ private ElfString Create(string? text) return new(index); } + /// + /// Adds a string to the table (or returns the offset of an existing copy) and + /// returns its byte offset. Lets callers append entries such as a new + /// DT_NEEDED library name whose dynamic entry stores a raw string offset. + /// + public uint GetOrCreateString(string text) + { + if (string.IsNullOrEmpty(text)) return 0; + + if (!_mapStringToIndex.TryGetValue(text, out uint index)) + { + index = CreateIndex(text); + } + + return index; + } + public bool TryResolve(ElfString name, out ElfString resolvedName) { string text = name.Value; From 4dddfea08f5403047c47d601054bb1b2b02f8eee Mon Sep 17 00:00:00 2001 From: Jack Greiner Date: Sat, 18 Jul 2026 20:09:38 -0400 Subject: [PATCH 06/11] Add missing dynamic tags to ElfDynamicTag 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. --- .../Elf/Sections/ElfDynamicTag.cs | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) diff --git a/src/LibObjectFile/Elf/Sections/ElfDynamicTag.cs b/src/LibObjectFile/Elf/Sections/ElfDynamicTag.cs index 17c7eef..822d317 100644 --- a/src/LibObjectFile/Elf/Sections/ElfDynamicTag.cs +++ b/src/LibObjectFile/Elf/Sections/ElfDynamicTag.cs @@ -179,6 +179,176 @@ public enum ElfDynamicTag : uint /// PreInitArraySz = ElfNative.DT_PREINIT_ARRAYSZ, + /// + /// Address of the section header index table for the dynamic symbol table. + /// + SymtabShndx = ElfNative.DT_SYMTAB_SHNDX, + + /// + /// Total size of the RELR relative relocation table. + /// + RelrSz = ElfNative.DT_RELRSZ, + + /// + /// Address of the RELR relative relocation table. + /// + Relr = ElfNative.DT_RELR, + + /// + /// Size of a RELR relative relocation entry. + /// + RelrEnt = ElfNative.DT_RELRENT, + + /// + /// Prelinking timestamp (GNU extension). + /// + GnuPrelinked = ElfNative.DT_GNU_PRELINKED, + + /// + /// Size of the conflict section (GNU extension). + /// + GnuConflictSz = ElfNative.DT_GNU_CONFLICTSZ, + + /// + /// Size of the library list (GNU extension). + /// + GnuLibListSz = ElfNative.DT_GNU_LIBLISTSZ, + + /// + /// Checksum of the shared object (GNU extension). + /// + Checksum = ElfNative.DT_CHECKSUM, + + /// + /// Size of the PLT padding. + /// + PltPadSz = ElfNative.DT_PLTPADSZ, + + /// + /// Size of a move table entry. + /// + MoveEnt = ElfNative.DT_MOVEENT, + + /// + /// Total size of the move table. + /// + MoveSz = ElfNative.DT_MOVESZ, + + /// + /// Feature selection flags (DTF_*). + /// + Feature1 = ElfNative.DT_FEATURE_1, + + /// + /// Flags for the entry immediately following this one. + /// + PosFlag1 = ElfNative.DT_POSFLAG_1, + + /// + /// Size of the syminfo table. + /// + SymInSz = ElfNative.DT_SYMINSZ, + + /// + /// Size of a syminfo table entry. + /// + SymInEnt = ElfNative.DT_SYMINENT, + + /// + /// Address of the GNU-style symbol hash table. + /// + GnuHash = ElfNative.DT_GNU_HASH, + + /// + /// Address of the PLT stub for TLS descriptors. + /// + TlsDescPlt = ElfNative.DT_TLSDESC_PLT, + + /// + /// Address of the GOT entry for TLS descriptors. + /// + TlsDescGot = ElfNative.DT_TLSDESC_GOT, + + /// + /// Address of the conflict section (GNU extension). + /// + GnuConflict = ElfNative.DT_GNU_CONFLICT, + + /// + /// Address of the library list (GNU extension). + /// + GnuLibList = ElfNative.DT_GNU_LIBLIST, + + /// + /// String table offset of a configuration file. + /// + Config = ElfNative.DT_CONFIG, + + /// + /// String table offset of the dependency audit library list. + /// + DepAudit = ElfNative.DT_DEPAUDIT, + + /// + /// String table offset of the audit library list. + /// + Audit = ElfNative.DT_AUDIT, + + /// + /// Address of the PLT padding. + /// + PltPad = ElfNative.DT_PLTPAD, + + /// + /// Address of the move table. + /// + MoveTab = ElfNative.DT_MOVETAB, + + /// + /// Address of the syminfo table. + /// + SymInfo = ElfNative.DT_SYMINFO, + + /// + /// Address of the symbol version table (.gnu.version). + /// + VerSym = ElfNative.DT_VERSYM, + + /// + /// Number of RELATIVE entries at the start of the Rela relocation table. + /// + RelaCount = ElfNative.DT_RELACOUNT, + + /// + /// Number of RELATIVE entries at the start of the Rel relocation table. + /// + RelCount = ElfNative.DT_RELCOUNT, + + /// + /// State flags (DF_1_*). + /// + Flags1 = ElfNative.DT_FLAGS_1, + + /// + /// Address of the version definition table (.gnu.version_d). + /// + VerDef = ElfNative.DT_VERDEF, + + /// + /// Number of entries in the version definition table. + /// + VerDefNum = ElfNative.DT_VERDEFNUM, + + /// + /// Address of the version needs table (.gnu.version_r). + /// + VerNeed = ElfNative.DT_VERNEED, + + /// + /// Number of entries in the version needs table. + /// + VerNeedNum = ElfNative.DT_VERNEEDNUM, + /// /// Start of environment specific tags. /// From add94d0e024d1fd7879afb000950744ec5450a51 Mon Sep 17 00:00:00 2001 From: Jack Greiner Date: Sat, 18 Jul 2026 20:18:46 -0400 Subject: [PATCH 07/11] Add string resolution and stream IO to the dynamic table 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. --- .../Elf/Sections/ElfDynamicLinkingTable.cs | 135 ++++++++++++++++-- 1 file changed, 122 insertions(+), 13 deletions(-) diff --git a/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs b/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs index 268cd92..81ea52f 100644 --- a/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs +++ b/src/LibObjectFile/Elf/Sections/ElfDynamicLinkingTable.cs @@ -14,31 +14,140 @@ namespace LibObjectFile.Elf { public class ElfDynamicLinkingTable : ElfSection { - private Stream _stream; private bool _is32; - public Stream Stream - { - get => _stream; - set - { - ArgumentNullException.ThrowIfNull(value); - _stream = value; - Size = (ulong)_stream.Length; - } - } - public List Entries { get; } public ElfDynamicLinkingTable() : base(ElfSectionType.DynamicLinking) { Name = ElfSectionSpecialType.Dynamic.GetDefaultName(); Flags = ElfSectionSpecialType.Dynamic.GetSectionFlags(); - _stream = new MemoryStream(); Entries = []; } + /// + /// Gets the dynamic string table (.dynstr) referenced by , + /// or null when the link does not point to an . Tags such as + /// store an offset into this table rather than the string itself. + /// + public ElfStringTable? StringTable => Link.Section as ElfStringTable; + + /// + /// Returns true when the value of an entry with the given is an offset + /// into the linked string table () rather than an address, size, or flag set. + /// + public static bool IsStringValueTag(ElfDynamicTag tag) => tag switch + { + ElfDynamicTag.Needed + or ElfDynamicTag.SoName + or ElfDynamicTag.RPath + or ElfDynamicTag.RunPath + or ElfDynamicTag.Config + or ElfDynamicTag.DepAudit + or ElfDynamicTag.Audit => true, + _ => false, + }; + + /// + /// Resolves the string referenced by a string-valued entry (e.g. ) + /// through the linked string table. Returns false when the entry is not string-valued or the + /// linked string table is missing. + /// + public bool TryGetString(ElfDynamic entry, out string value) + { + if (IsStringValueTag(entry.TagType) && StringTable is { } stringTable) + { + return stringTable.TryGetString((uint)entry.Value, out value); + } + + value = string.Empty; + return false; + } + + /// + /// Enumerates the shared library names referenced by entries, + /// resolved through the linked string table. Yields nothing when the string table is missing. + /// + public IEnumerable GetNeededLibraries() + { + if (StringTable is not { } stringTable) + { + yield break; + } + + foreach (var entry in Entries) + { + if (entry.TagType == ElfDynamicTag.Needed && stringTable.TryGetString((uint)entry.Value, out var name)) + { + yield return name; + } + } + } + + /// + /// Appends a entry for , adding the + /// name to the linked string table. The entry is inserted before any trailing + /// terminator so the table stays valid. + /// + /// is not an . + public void AddNeededLibrary(string libraryName) + { + ArgumentNullException.ThrowIfNull(libraryName); + var stringTable = StringTable ?? throw new InvalidOperationException($"The {nameof(Link)} of this dynamic section must be set to an {nameof(ElfStringTable)} before adding a needed library"); + + var offset = stringTable.GetOrCreateString(libraryName); + + int insertIndex = Entries.Count; + while (insertIndex > 0 && Entries[insertIndex - 1].TagType == ElfDynamicTag.Null) + { + insertIndex--; + } + + Entries.Insert(insertIndex, new ElfDynamic { Tag = (long)ElfDynamicTag.Needed, Value = offset }); + } + + /// + /// Serializes as raw Elf32_Dyn/Elf64_Dyn records to , + /// using the file class and byte order of the parent . Standalone counterpart to the + /// full-file write path, mirroring at the section level. + /// + /// The section is not attached to an . + public void Write(Stream stream) + { + ArgumentNullException.ThrowIfNull(stream); + var elf = Parent ?? throw new InvalidOperationException($"The {nameof(ElfDynamicLinkingTable)} must be attached to an {nameof(ElfFile)} to determine the file class and byte order"); + + var writer = ElfWriter.Create(elf, stream); + Write(writer); + } + + /// + /// Replaces by reading raw Elf32_Dyn/Elf64_Dyn records from the whole of + /// , using the file class and byte order of the parent . + /// Standalone counterpart to the full-file read path, mirroring . + /// + /// The section is not attached to an . + public void Read(Stream stream) + { + ArgumentNullException.ThrowIfNull(stream); + var elf = Parent ?? throw new InvalidOperationException($"The {nameof(ElfDynamicLinkingTable)} must be attached to an {nameof(ElfFile)} to determine the file class and byte order"); + + var reader = ElfReader.Create(elf, stream, new ElfReaderOptions()); + reader.Position = 0; + + var entrySize = _is32 ? Unsafe.SizeOf() : Unsafe.SizeOf(); + var numberOfEntries = (int)((ulong)stream.Length / (ulong)entrySize); + + Entries.Clear(); + CollectionsMarshal.SetCount(Entries, numberOfEntries); + + if (_is32) + Read32(reader, numberOfEntries); + else + Read64(reader, numberOfEntries); + } + protected override void UpdateLayoutCore(ElfVisitorContext context) { base.UpdateLayoutCore(context); From 5d7a1c20ccdedecd776981368c5f656989c813e9 Mon Sep 17 00:00:00 2001 From: Jack Greiner Date: Sat, 18 Jul 2026 20:18:59 -0400 Subject: [PATCH 08/11] Implement readelf -d style dynamic section printer 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 --- ...Tests.TestElf_name=helloworld.verified.txt | 30 ++- ...TestElf_name=helloworld_debug.verified.txt | 30 ++- ...sts.TestElf_name=lib_debug.so.verified.txt | 20 +- ...sts.TestElf_name=libstdc++.so.verified.txt | 33 ++- src/LibObjectFile/Elf/ElfPrinter.cs | 250 +++++++++++++++++- 5 files changed, 356 insertions(+), 7 deletions(-) diff --git a/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=helloworld.verified.txt b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=helloworld.verified.txt index e66a256..86e6fed 100644 --- a/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=helloworld.verified.txt +++ b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=helloworld.verified.txt @@ -92,7 +92,35 @@ Program Headers: 11 12 .init_array .fini_array .dynamic .got -There is no dynamic section in this file. +Dynamic section at offset 0x2dc8 contains 27 entries: + Tag Type Name/Value + 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] + 0x000000000000000c (INIT) 0x1000 + 0x000000000000000d (FINI) 0x1174 + 0x0000000000000019 (INIT_ARRAY) 0x3db8 + 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) + 0x000000000000001a (FINI_ARRAY) 0x3dc0 + 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) + 0x000000006ffffef5 (GNU_HASH) 0x3b0 + 0x0000000000000005 (STRTAB) 0x480 + 0x0000000000000006 (SYMTAB) 0x3d8 + 0x000000000000000a (STRSZ) 141 (bytes) + 0x000000000000000b (SYMENT) 24 (bytes) + 0x0000000000000015 (DEBUG) 0x0 + 0x0000000000000003 (PLTGOT) 0x3fb8 + 0x0000000000000002 (PLTRELSZ) 24 (bytes) + 0x0000000000000014 (PLTREL) RELA + 0x0000000000000017 (JMPREL) 0x610 + 0x0000000000000007 (RELA) 0x550 + 0x0000000000000008 (RELASZ) 192 (bytes) + 0x0000000000000009 (RELAENT) 24 (bytes) + 0x000000000000001e (FLAGS) BIND_NOW + 0x000000006ffffffb (FLAGS_1) Flags: NOW PIE + 0x000000006ffffffe (VERNEED) 0x520 + 0x000000006fffffff (VERNEEDNUM) 1 + 0x000000006ffffff0 (VERSYM) 0x50e + 0x000000006ffffff9 (RELACOUNT) 3 + 0x0000000000000000 (NULL) 0x0 Relocation section '.rela.dyn' at offset 0x550 contains 8 entries: Offset Info Type Symbol's Value Symbol's Name + Addend diff --git a/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=helloworld_debug.verified.txt b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=helloworld_debug.verified.txt index 600ecf5..98b13bd 100644 --- a/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=helloworld_debug.verified.txt +++ b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=helloworld_debug.verified.txt @@ -97,7 +97,35 @@ Program Headers: 11 12 .init_array .fini_array .dynamic .got -There is no dynamic section in this file. +Dynamic section at offset 0x2dc8 contains 27 entries: + Tag Type Name/Value + 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] + 0x000000000000000c (INIT) 0x1000 + 0x000000000000000d (FINI) 0x1174 + 0x0000000000000019 (INIT_ARRAY) 0x3db8 + 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) + 0x000000000000001a (FINI_ARRAY) 0x3dc0 + 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) + 0x000000006ffffef5 (GNU_HASH) 0x3b0 + 0x0000000000000005 (STRTAB) 0x480 + 0x0000000000000006 (SYMTAB) 0x3d8 + 0x000000000000000a (STRSZ) 141 (bytes) + 0x000000000000000b (SYMENT) 24 (bytes) + 0x0000000000000015 (DEBUG) 0x0 + 0x0000000000000003 (PLTGOT) 0x3fb8 + 0x0000000000000002 (PLTRELSZ) 24 (bytes) + 0x0000000000000014 (PLTREL) RELA + 0x0000000000000017 (JMPREL) 0x610 + 0x0000000000000007 (RELA) 0x550 + 0x0000000000000008 (RELASZ) 192 (bytes) + 0x0000000000000009 (RELAENT) 24 (bytes) + 0x000000000000001e (FLAGS) BIND_NOW + 0x000000006ffffffb (FLAGS_1) Flags: NOW PIE + 0x000000006ffffffe (VERNEED) 0x520 + 0x000000006fffffff (VERNEEDNUM) 1 + 0x000000006ffffff0 (VERSYM) 0x50e + 0x000000006ffffff9 (RELACOUNT) 3 + 0x0000000000000000 (NULL) 0x0 Relocation section '.rela.dyn' at offset 0x550 contains 8 entries: Offset Info Type Symbol's Value Symbol's Name + Addend diff --git a/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=lib_debug.so.verified.txt b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=lib_debug.so.verified.txt index e8419d5..2dd86df 100644 --- a/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=lib_debug.so.verified.txt +++ b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=lib_debug.so.verified.txt @@ -87,7 +87,25 @@ Program Headers: 09 10 .init_array .fini_array .dynamic .got -There is no dynamic section in this file. +Dynamic section at offset 0x2e90 contains 17 entries: + Tag Type Name/Value + 0x000000000000000c (INIT) 0x1000 + 0x000000000000000d (FINI) 0x1130 + 0x0000000000000019 (INIT_ARRAY) 0x3e80 + 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) + 0x000000000000001a (FINI_ARRAY) 0x3e88 + 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) + 0x000000006ffffef5 (GNU_HASH) 0x2f0 + 0x0000000000000005 (STRTAB) 0x3c0 + 0x0000000000000006 (SYMTAB) 0x318 + 0x000000000000000a (STRSZ) 122 (bytes) + 0x000000000000000b (SYMENT) 24 (bytes) + 0x0000000000000003 (PLTGOT) 0x4000 + 0x0000000000000007 (RELA) 0x440 + 0x0000000000000008 (RELASZ) 168 (bytes) + 0x0000000000000009 (RELAENT) 24 (bytes) + 0x000000006ffffff9 (RELACOUNT) 3 + 0x0000000000000000 (NULL) 0x0 Relocation section '.rela.dyn' at offset 0x440 contains 7 entries: Offset Info Type Symbol's Value Symbol's Name + Addend diff --git a/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=libstdc++.so.verified.txt b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=libstdc++.so.verified.txt index 09b8027..aee262b 100644 --- a/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=libstdc++.so.verified.txt +++ b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.TestElf_name=libstdc++.so.verified.txt @@ -93,7 +93,38 @@ Program Headers: 10 11 .init_array .fini_array .data.rel.ro .dynamic .got -There is no dynamic section in this file. +Dynamic section at offset 0x223c90 contains 30 entries: + Tag Type Name/Value + 0x0000000000000001 (NEEDED) Shared library: [libm.so.6] + 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] + 0x0000000000000001 (NEEDED) Shared library: [ld-linux-x86-64.so.2] + 0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1] + 0x000000000000000e (SONAME) Library soname: [libstdc++.so.6] + 0x000000000000000c (INIT) 0x9a000 + 0x000000000000000d (FINI) 0x1aafc4 + 0x0000000000000019 (INIT_ARRAY) 0x21b880 + 0x000000000000001b (INIT_ARRAYSZ) 120 (bytes) + 0x000000000000001a (FINI_ARRAY) 0x21b8f8 + 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) + 0x000000006ffffef5 (GNU_HASH) 0x328 + 0x0000000000000005 (STRTAB) 0x2d310 + 0x0000000000000006 (SYMTAB) 0x90a0 + 0x000000000000000a (STRSZ) 302456 (bytes) + 0x000000000000000b (SYMENT) 24 (bytes) + 0x0000000000000003 (PLTGOT) 0x226000 + 0x0000000000000002 (PLTRELSZ) 25032 (bytes) + 0x0000000000000014 (PLTREL) RELA + 0x0000000000000017 (JMPREL) 0x92f78 + 0x0000000000000007 (RELA) 0x7a8e8 + 0x0000000000000008 (RELASZ) 99984 (bytes) + 0x0000000000000009 (RELAENT) 24 (bytes) + 0x000000006ffffffc (VERDEF) 0x7a0c0 + 0x000000006ffffffd (VERDEFNUM) 48 + 0x000000006ffffffe (VERNEED) 0x7a758 + 0x000000006fffffff (VERNEEDNUM) 4 + 0x000000006ffffff0 (VERSYM) 0x77088 + 0x000000006ffffff9 (RELACOUNT) 899 + 0x0000000000000000 (NULL) 0x0 Relocation section '.rela.dyn' at offset 0x7a8e8 contains 4166 entries: Offset Info Type Symbol's Value Symbol's Name + Addend diff --git a/src/LibObjectFile/Elf/ElfPrinter.cs b/src/LibObjectFile/Elf/ElfPrinter.cs index 27b4650..069a1f6 100644 --- a/src/LibObjectFile/Elf/ElfPrinter.cs +++ b/src/LibObjectFile/Elf/ElfPrinter.cs @@ -538,9 +538,253 @@ offsets within the segment. */ public static void PrintDynamicSections(ElfFile elf, TextWriter writer) { - writer.WriteLine(); - writer.WriteLine("There is no dynamic section in this file."); - // TODO + if (elf == null) throw new ArgumentNullException(nameof(elf)); + if (writer == null) throw new ArgumentNullException(nameof(writer)); + + bool is32 = elf.FileClass == ElfFileClass.Is32; + bool foundDynamic = false; + + foreach (var section in elf.Sections) + { + if (section is not ElfDynamicLinkingTable dynamic) continue; + foundDynamic = true; + + // readelf lists entries up to and including the first DT_NULL terminator, ignoring any + // trailing padding entries the section may reserve for later editing. + int count = dynamic.Entries.Count; + for (int i = 0; i < dynamic.Entries.Count; i++) + { + if (dynamic.Entries[i].TagType == ElfDynamicTag.Null) + { + count = i + 1; + break; + } + } + + writer.WriteLine(); + writer.WriteLine(count == 1 + ? $"Dynamic section at offset 0x{dynamic.Position:x} contains {count} entry:" + : $"Dynamic section at offset 0x{dynamic.Position:x} contains {count} entries:"); + writer.WriteLine(" Tag Type Name/Value"); + + for (int i = 0; i < count; i++) + { + var entry = dynamic.Entries[i]; + var tagHex = is32 ? $"0x{(uint)entry.Tag:x8}" : $"0x{(ulong)entry.Tag:x16}"; + var typeField = $" ({GetElfDynamicTagName(entry.Tag)})"; + writer.WriteLine($" {tagHex}{typeField,-22}{GetElfDynamicValue(dynamic, entry)}"); + } + } + + if (!foundDynamic) + { + writer.WriteLine(); + writer.WriteLine("There is no dynamic section in this file."); + } + } + + private static string GetElfDynamicValue(ElfDynamicLinkingTable dynamic, ElfDynamic entry) + { + var tag = entry.TagType; + var value = entry.Value; + + if (ElfDynamicLinkingTable.IsStringValueTag(tag) && dynamic.TryGetString(entry, out var name)) + { + return tag switch + { + ElfDynamicTag.Needed => $"Shared library: [{name}]", + ElfDynamicTag.SoName => $"Library soname: [{name}]", + ElfDynamicTag.RPath => $"Library rpath: [{name}]", + ElfDynamicTag.RunPath => $"Library runpath: [{name}]", + ElfDynamicTag.Config => $"Configuration file: [{name}]", + ElfDynamicTag.DepAudit => $"Dependency audit library: [{name}]", + ElfDynamicTag.Audit => $"Audit library: [{name}]", + _ => $"0x{value:x}", + }; + } + + switch (tag) + { + case ElfDynamicTag.PltRelSz: + case ElfDynamicTag.RelaSz: + case ElfDynamicTag.RelaEnt: + case ElfDynamicTag.StrSz: + case ElfDynamicTag.SymEnt: + case ElfDynamicTag.RelSz: + case ElfDynamicTag.RelEnt: + case ElfDynamicTag.InitArraySz: + case ElfDynamicTag.FiniArraySz: + case ElfDynamicTag.PreInitArraySz: + case ElfDynamicTag.RelrSz: + case ElfDynamicTag.RelrEnt: + case ElfDynamicTag.GnuConflictSz: + case ElfDynamicTag.GnuLibListSz: + case ElfDynamicTag.PltPadSz: + case ElfDynamicTag.MoveSz: + case ElfDynamicTag.MoveEnt: + case ElfDynamicTag.SymInSz: + case ElfDynamicTag.SymInEnt: + return $"{value} (bytes)"; + + case ElfDynamicTag.RelaCount: + case ElfDynamicTag.RelCount: + case ElfDynamicTag.VerDefNum: + case ElfDynamicTag.VerNeedNum: + return value.ToString(); + + case ElfDynamicTag.PltRel: + return value == (ulong)ElfNative.DT_RELA ? "RELA" : value == (ulong)ElfNative.DT_REL ? "REL" : $"0x{value:x}"; + + case ElfDynamicTag.Flags: + return GetElfDynamicFlags(value); + + case ElfDynamicTag.Flags1: + return $"Flags:{GetElfDynamicFlags1(value)}"; + + default: + return $"0x{value:x}"; + } + } + + private static string GetElfDynamicFlags(ulong flags) + { + // DF_* flags. Not exposed by ElfNative, so the bit values are inlined here. + (ulong Bit, string Name)[] known = + [ + (0x1, "ORIGIN"), + (0x2, "SYMBOLIC"), + (0x4, "TEXTREL"), + (0x8, "BIND_NOW"), + (0x10, "STATIC_TLS"), + ]; + + var builder = new StringBuilder(); + foreach (var (bit, flagName) in known) + { + if ((flags & bit) == 0) continue; + if (builder.Length > 0) builder.Append(' '); + builder.Append(flagName); + flags &= ~bit; + } + + if (flags != 0) + { + if (builder.Length > 0) builder.Append(' '); + builder.Append($"0x{flags:x}"); + } + + return builder.ToString(); + } + + private static string GetElfDynamicFlags1(ulong flags) + { + // DF_1_* flags, in readelf order. Each match is emitted as " NAME" so the caller can prefix "Flags:". + (ulong Bit, string Name)[] known = + [ + (0x00000001, "NOW"), (0x00000002, "GLOBAL"), (0x00000004, "GROUP"), (0x00000008, "NODELETE"), + (0x00000010, "LOADFLTR"), (0x00000020, "INITFIRST"), (0x00000040, "NOOPEN"), (0x00000080, "ORIGIN"), + (0x00000100, "DIRECT"), (0x00000200, "TRANS"), (0x00000400, "INTERPOSE"), (0x00000800, "NODEFLIB"), + (0x00001000, "NODUMP"), (0x00002000, "CONFALT"), (0x00004000, "ENDFILTEE"), (0x00008000, "DISPRELDNE"), + (0x00010000, "DISPRELPND"), (0x00020000, "NODIRECT"), (0x00040000, "IGNMULDEF"), (0x00080000, "NOKSYMS"), + (0x00100000, "NOHDR"), (0x00200000, "EDITED"), (0x00400000, "NORELOC"), (0x00800000, "SYMINTPOSE"), + (0x01000000, "GLOBAUDIT"), (0x02000000, "SINGLETON"), (0x04000000, "STUB"), (0x08000000, "PIE"), + (0x10000000, "KMOD"), (0x20000000, "WEAKFILTER"), (0x40000000, "NOCOMMON"), + ]; + + var builder = new StringBuilder(); + foreach (var (bit, flagName) in known) + { + if ((flags & bit) == 0) continue; + builder.Append(' '); + builder.Append(flagName); + flags &= ~bit; + } + + if (flags != 0) + { + builder.Append($" 0x{flags:x}"); + } + + return builder.ToString(); + } + + private static string GetElfDynamicTagName(long tag) + { + switch ((ElfDynamicTag)tag) + { + case ElfDynamicTag.Null: return "NULL"; + case ElfDynamicTag.Needed: return "NEEDED"; + case ElfDynamicTag.PltRelSz: return "PLTRELSZ"; + case ElfDynamicTag.PltGot: return "PLTGOT"; + case ElfDynamicTag.Hash: return "HASH"; + case ElfDynamicTag.StrTab: return "STRTAB"; + case ElfDynamicTag.SymTab: return "SYMTAB"; + case ElfDynamicTag.Rela: return "RELA"; + case ElfDynamicTag.RelaSz: return "RELASZ"; + case ElfDynamicTag.RelaEnt: return "RELAENT"; + case ElfDynamicTag.StrSz: return "STRSZ"; + case ElfDynamicTag.SymEnt: return "SYMENT"; + case ElfDynamicTag.Init: return "INIT"; + case ElfDynamicTag.Fini: return "FINI"; + case ElfDynamicTag.SoName: return "SONAME"; + case ElfDynamicTag.RPath: return "RPATH"; + case ElfDynamicTag.Symbolic: return "SYMBOLIC"; + case ElfDynamicTag.Rel: return "REL"; + case ElfDynamicTag.RelSz: return "RELSZ"; + case ElfDynamicTag.RelEnt: return "RELENT"; + case ElfDynamicTag.PltRel: return "PLTREL"; + case ElfDynamicTag.Debug: return "DEBUG"; + case ElfDynamicTag.TextRel: return "TEXTREL"; + case ElfDynamicTag.JmpRel: return "JMPREL"; + case ElfDynamicTag.BindNow: return "BIND_NOW"; + case ElfDynamicTag.InitArray: return "INIT_ARRAY"; + case ElfDynamicTag.FiniArray: return "FINI_ARRAY"; + case ElfDynamicTag.InitArraySz: return "INIT_ARRAYSZ"; + case ElfDynamicTag.FiniArraySz: return "FINI_ARRAYSZ"; + case ElfDynamicTag.RunPath: return "RUNPATH"; + case ElfDynamicTag.Flags: return "FLAGS"; + // DT_ENCODING (32) aliases DT_PREINIT_ARRAY; readelf reports 32 as PREINIT_ARRAY. + case ElfDynamicTag.PreInitArray: return "PREINIT_ARRAY"; + case ElfDynamicTag.PreInitArraySz: return "PREINIT_ARRAYSZ"; + case ElfDynamicTag.SymtabShndx: return "SYMTAB_SHNDX"; + case ElfDynamicTag.RelrSz: return "RELRSZ"; + case ElfDynamicTag.Relr: return "RELR"; + case ElfDynamicTag.RelrEnt: return "RELRENT"; + case ElfDynamicTag.GnuPrelinked: return "GNU_PRELINKED"; + case ElfDynamicTag.GnuConflictSz: return "GNU_CONFLICTSZ"; + case ElfDynamicTag.GnuLibListSz: return "GNU_LIBLISTSZ"; + case ElfDynamicTag.Checksum: return "CHECKSUM"; + case ElfDynamicTag.PltPadSz: return "PLTPADSZ"; + case ElfDynamicTag.MoveEnt: return "MOVEENT"; + case ElfDynamicTag.MoveSz: return "MOVESZ"; + case ElfDynamicTag.Feature1: return "FEATURE_1"; + case ElfDynamicTag.PosFlag1: return "POSFLAG_1"; + case ElfDynamicTag.SymInSz: return "SYMINSZ"; + case ElfDynamicTag.SymInEnt: return "SYMINENT"; + case ElfDynamicTag.GnuHash: return "GNU_HASH"; + case ElfDynamicTag.TlsDescPlt: return "TLSDESC_PLT"; + case ElfDynamicTag.TlsDescGot: return "TLSDESC_GOT"; + case ElfDynamicTag.GnuConflict: return "GNU_CONFLICT"; + case ElfDynamicTag.GnuLibList: return "GNU_LIBLIST"; + case ElfDynamicTag.Config: return "CONFIG"; + case ElfDynamicTag.DepAudit: return "DEPAUDIT"; + case ElfDynamicTag.Audit: return "AUDIT"; + case ElfDynamicTag.PltPad: return "PLTPAD"; + case ElfDynamicTag.MoveTab: return "MOVETAB"; + case ElfDynamicTag.SymInfo: return "SYMINFO"; + case ElfDynamicTag.VerSym: return "VERSYM"; + case ElfDynamicTag.RelaCount: return "RELACOUNT"; + case ElfDynamicTag.RelCount: return "RELCOUNT"; + case ElfDynamicTag.Flags1: return "FLAGS_1"; + case ElfDynamicTag.VerDef: return "VERDEF"; + case ElfDynamicTag.VerDefNum: return "VERDEFNUM"; + case ElfDynamicTag.VerNeed: return "VERNEED"; + case ElfDynamicTag.VerNeedNum: return "VERNEEDNUM"; + default: + if (tag >= ElfNative.DT_LOOS && tag <= ElfNative.DT_HIOS) return $""; + if (tag >= ElfNative.DT_LOPROC && tag <= ElfNative.DT_HIPROC) return $""; + return $": 0x{tag:x}"; + } } private static string GetElfSegmentFlags(ElfSegmentFlags flags) From 941b012b29a17318ce2d7893ed591e5ad9c4531e Mon Sep 17 00:00:00 2001 From: Jack Greiner Date: Sat, 18 Jul 2026 20:19:09 -0400 Subject: [PATCH 09/11] Add tests for dynamic table build, string API and stream IO 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). --- src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs | 97 +++++++++++++++++++ ...pleTests.SimpleDynamicSection.verified.txt | 48 +++++++++ 2 files changed, 145 insertions(+) create mode 100644 src/LibObjectFile.Tests/Verified/ElfSimpleTests.SimpleDynamicSection.verified.txt diff --git a/src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs b/src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs index fe22ac4..4f44376 100644 --- a/src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs +++ b/src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs @@ -438,6 +438,103 @@ public async Task SimpleProgramHeaderAndCodeSectionAndSymbolSectionAndRelocation } + [TestMethod] + public async Task SimpleDynamicSection() + { + var elf = new ElfFile(ElfArch.X86_64); + + var dynstr = new ElfStringTable() { Name = ".dynstr" }; + elf.Add(dynstr); + + var dynamic = new ElfDynamicLinkingTable() { Link = dynstr }; + dynamic.AddNeededLibrary("libc.so.6"); + dynamic.AddNeededLibrary("libm.so.6"); + dynamic.Entries.Add(new ElfDynamic { Tag = (long)ElfDynamicTag.Flags1, Value = 0x08000001 }); + dynamic.Entries.Add(new ElfDynamic { Tag = (long)ElfDynamicTag.Null }); + elf.Add(dynamic); + + elf.Add(new ElfSectionHeaderStringTable()); + elf.Add(new ElfSectionHeaderTable()); + + // The read helpers resolve DT_NEEDED offsets through the linked .dynstr. + CollectionAssert.AreEqual(new[] { "libc.so.6", "libm.so.6" }, dynamic.GetNeededLibraries().ToList()); + + await AssertReadElf(elf, "test_dynamic.elf"); + + // Round-trip the entries and confirm the resolved library names survive write -> read. + var memoryStream = new MemoryStream(); + elf.Write(memoryStream); + memoryStream.Position = 0; + var roundTrip = ElfFile.Read(memoryStream); + var roundTripDynamic = roundTrip.Sections.OfType().Single(); + Assert.AreEqual(dynamic.Entries.Count, roundTripDynamic.Entries.Count); + CollectionAssert.AreEqual(new[] { "libc.so.6", "libm.so.6" }, roundTripDynamic.GetNeededLibraries().ToList()); + } + + [TestMethod] + public void DynamicSectionStreamRoundTrip() + { + var elf = new ElfFile(ElfArch.X86_64); + var dynstr = new ElfStringTable() { Name = ".dynstr" }; + elf.Add(dynstr); + + var dynamic = new ElfDynamicLinkingTable() { Link = dynstr }; + dynamic.AddNeededLibrary("libc.so.6"); + dynamic.Entries.Add(new ElfDynamic { Tag = (long)ElfDynamicTag.Flags1, Value = 0x08000001 }); + dynamic.Entries.Add(new ElfDynamic { Tag = (long)ElfDynamicTag.Null }); + elf.Add(dynamic); + + // Serialize just the .dynamic entries to a standalone stream (64-bit -> 16 bytes each). + var stream = new MemoryStream(); + dynamic.Write(stream); + Assert.AreEqual(dynamic.Entries.Count * 16, stream.Length); + + // Read them back into a fresh table attached to another file and confirm value equality. + var elf2 = new ElfFile(ElfArch.X86_64); + var dynamic2 = new ElfDynamicLinkingTable(); + elf2.Add(dynamic2); + stream.Position = 0; + dynamic2.Read(stream); + + CollectionAssert.AreEqual(dynamic.Entries, dynamic2.Entries); + } + + [TestMethod] + public void DynamicSection32Bit() + { + var elf = new ElfFile(ElfArch.I386); + var dynstr = new ElfStringTable() { Name = ".dynstr" }; + elf.Add(dynstr); + + var dynamic = new ElfDynamicLinkingTable() { Link = dynstr }; + dynamic.AddNeededLibrary("libc.so.6"); + dynamic.Entries.Add(new ElfDynamic { Tag = (long)ElfDynamicTag.Flags, Value = 0x8 }); // DF_BIND_NOW + dynamic.Entries.Add(new ElfDynamic { Tag = (long)ElfDynamicTag.Null }); + elf.Add(dynamic); + + // The printer uses 8-digit tag hex for 32-bit files. + var writer = new StringWriter(); + ElfPrinter.PrintDynamicSections(elf, writer); + var text = writer.ToString(); + StringAssert.Contains(text, " 0x00000001 (NEEDED)"); + StringAssert.Contains(text, "Shared library: [libc.so.6]"); + StringAssert.Contains(text, " 0x0000001e (FLAGS)"); + StringAssert.Contains(text, "BIND_NOW"); + + // The Read32/Write32 path uses 8-byte Elf32_Dyn records. + var stream = new MemoryStream(); + dynamic.Write(stream); + Assert.AreEqual(dynamic.Entries.Count * 8, stream.Length); + + var elf2 = new ElfFile(ElfArch.I386); + var dynamic2 = new ElfDynamicLinkingTable(); + elf2.Add(dynamic2); + stream.Position = 0; + dynamic2.Read(stream); + + CollectionAssert.AreEqual(dynamic.Entries, dynamic2.Entries); + } + [TestMethod] public async Task TestAlignedSection() { diff --git a/src/LibObjectFile.Tests/Verified/ElfSimpleTests.SimpleDynamicSection.verified.txt b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.SimpleDynamicSection.verified.txt new file mode 100644 index 0000000..64b427d --- /dev/null +++ b/src/LibObjectFile.Tests/Verified/ElfSimpleTests.SimpleDynamicSection.verified.txt @@ -0,0 +1,48 @@ +ELF Header: + Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 + Class: ELF64 + Data: 2's complement, little endian + Version: 1 (current) + OS/ABI: UNIX - System V + ABI Version: 0 + Type: REL (Relocatable file) + Machine: Advanced Micro Devices X86-64 + Version: 0x1 + Entry point address: 0x0 + Start of program headers: 0 (bytes into file) + Start of section headers: 0 (bytes into file) + Flags: 0x0 + Size of this header: 0 (bytes) + Size of program headers: 0 (bytes) + Number of program headers: 0 + Size of section headers: 0 (bytes) + Number of section headers: 4 + Section header string table index: 3 + +Section Headers: + [Nr] Name Type Address Off Size ES Flg Lk Inf Al + [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 + [ 1] .dynstr STRTAB 0000000000000000 000000 000000 00 0 0 0 + [ 2] .dynamic DYNAMIC 0000000000000000 000000 000000 10 A 1 0 0 + [ 3] .shstrtab STRTAB 0000000000000000 000000 000000 00 0 0 0 +Key to Flags: + W (write), A (alloc), X (execute), M (merge), S (strings), I (info), + L (link order), O (extra OS processing required), G (group), T (TLS), + C (compressed), x (unknown), o (OS specific), E (exclude), + D (mbind), l (large), p (processor specific) + +There are no section groups in this file. + +There are no program headers in this file. + +Dynamic section at offset 0x0 contains 4 entries: + Tag Type Name/Value + 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] + 0x0000000000000001 (NEEDED) Shared library: [libm.so.6] + 0x000000006ffffffb (FLAGS_1) Flags: NOW PIE + 0x0000000000000000 (NULL) 0x0 + +There are no relocations in this file. +No processor specific unwind information to decode + +No version information found in this file. From 92825617bf4df9035bea9f78f2fe37657a09499c Mon Sep 17 00:00:00 2001 From: Jack Greiner Date: Sat, 18 Jul 2026 20:57:11 -0400 Subject: [PATCH 10/11] Fix truncation of 64-bit section header fields on write 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 --- src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs | 28 +++++++++++++++++++ .../Elf/ElfSectionHeaderTable.cs | 14 ++++++---- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs b/src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs index 4f44376..9b3511a 100644 --- a/src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs +++ b/src/LibObjectFile.Tests/Elf/ElfSimpleTests.cs @@ -535,6 +535,34 @@ public void DynamicSection32Bit() CollectionAssert.AreEqual(dynamic.Entries, dynamic2.Entries); } + [TestMethod] + public void SectionHeader64BitFieldsNotTruncated() + { + // Regression: the 64-bit section header writer cast sh_addr/sh_offset/sh_size/etc. to uint, + // truncating any value >= 4 GiB. A NOBITS section declares a huge address/size with no file bytes. + var elf = new ElfFile(ElfArch.X86_64); + + var bss = new ElfNoBitsSection + { + Name = ".hugebss", + VirtualAddress = 0x1_0000_2000UL, // > 4 GiB + Size = 0x2_0000_1000UL, // > 4 GiB + VirtualAddressAlignment = 0x1000, + }; + elf.Add(bss); + elf.Add(new ElfSectionHeaderStringTable()); + elf.Add(new ElfSectionHeaderTable()); + + var stream = new MemoryStream(); + elf.Write(stream); + stream.Position = 0; + var roundTrip = ElfFile.Read(stream); + + var bss2 = roundTrip.Sections.OfType().Single(); + Assert.AreEqual(0x1_0000_2000UL, bss2.VirtualAddress); + Assert.AreEqual(0x2_0000_1000UL, bss2.Size); + } + [TestMethod] public async Task TestAlignedSection() { diff --git a/src/LibObjectFile/Elf/ElfSectionHeaderTable.cs b/src/LibObjectFile/Elf/ElfSectionHeaderTable.cs index 0185874..8dc0ae6 100644 --- a/src/LibObjectFile/Elf/ElfSectionHeaderTable.cs +++ b/src/LibObjectFile/Elf/ElfSectionHeaderTable.cs @@ -216,21 +216,23 @@ private static unsafe void EncodeSectionTableEntry64(ElfWriter writer, ElfSectio writer.Encode(out rawSection.sh_name, section.Name.Index); writer.Encode(out rawSection.sh_type, (uint)section.Type); writer.Encode(out rawSection.sh_flags, (uint)section.Flags); - writer.Encode(out rawSection.sh_addr, (uint)section.VirtualAddress); - writer.Encode(out rawSection.sh_offset, (uint)section.Position); + // sh_addr/sh_offset/sh_size/sh_addralign/sh_entsize are 64-bit in Elf64_Shdr. Casting to + // uint here truncated any value >= 4 GiB (high load addresses, large NOBITS sections). + writer.Encode(out rawSection.sh_addr, section.VirtualAddress); + writer.Encode(out rawSection.sh_offset, section.Position); if (section.SectionIndex == 0 && writer.File.Sections.Count >= ElfNative.SHN_LORESERVE) { - writer.Encode(out rawSection.sh_size, (uint)writer.File.Sections.Count); + writer.Encode(out rawSection.sh_size, (ulong)writer.File.Sections.Count); var shstrSectionIndex = (uint)(writer.File.SectionHeaderStringTable?.SectionIndex ?? 0); writer.Encode(out rawSection.sh_link, shstrSectionIndex >= ElfNative.SHN_LORESERVE ? shstrSectionIndex : 0); } else { - writer.Encode(out rawSection.sh_size, (uint)section.Size); + writer.Encode(out rawSection.sh_size, section.Size); writer.Encode(out rawSection.sh_link, (uint)section.Link.GetIndex()); } writer.Encode(out rawSection.sh_info, (uint)section.Info.GetIndex()); - writer.Encode(out rawSection.sh_addralign, (uint)section.VirtualAddressAlignment); - writer.Encode(out rawSection.sh_entsize, (uint)section.TableEntrySize); + writer.Encode(out rawSection.sh_addralign, section.VirtualAddressAlignment); + writer.Encode(out rawSection.sh_entsize, section.TableEntrySize); } } \ No newline at end of file From fa50da05bcf0ea0dcab84dab9e7d0dff35966b0e Mon Sep 17 00:00:00 2001 From: Jack Greiner Date: Tue, 21 Jul 2026 04:33:01 -0400 Subject: [PATCH 11/11] Document dynamic linking table support in readme The ELF section list did not mention the dynamic linking table. Adds it to the supported sections. --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 50c09fc..c491ae8 100644 --- a/readme.md +++ b/readme.md @@ -49,6 +49,7 @@ elf.Write(outStream); - Symbol Table - Relocation Table: supported I386, X86_64, ARM and AARCH64 relocations (others can be exposed by adding some mappings) - Note Table + - Dynamic Linking Table (`SHT_DYNAMIC`) - Other sections fallback to `ElfCustomSection` - Program headers with or without sections - Print with `readelf` similar output