From cd7aa028f4fb77be46b8efb7fac13a996cff592f Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Sat, 30 May 2026 20:19:04 +1000 Subject: [PATCH] write/elf: fix need_dynstr handling in Writer .dynsym needs to link to .dynstr. This matches the existing logic for .symtab and .strtab. Also some minor dynstr_len cleanup. Writer originally required this, but was changed in f3432e98. This change was because there are files in the wild that are missing .dynstr, and Builder needed to be able to handle them. Now that Builder is no longer using Writer, I don't see any downside to writing the empty .dynstr, so I think it's better to change Writer back again. It's still possible to omit .dynstr if you use Builder (which uses Encoder directly, not Writer), and there is a test for this. --- src/write/elf/writer.rs | 10 ++++++++- tests/round_trip/elf.rs | 46 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/write/elf/writer.rs b/src/write/elf/writer.rs index 8086f05..8c3ff85 100644 --- a/src/write/elf/writer.rs +++ b/src/write/elf/writer.rs @@ -1789,7 +1789,11 @@ impl<'a> Writer<'a, TwoPhase> { /// Return the size of the dynamic string table. /// /// Must be called after [`Self::reserve_dynstr`]. - pub fn dynstr_len(&mut self) -> u32 { + /// Returns 0 if the dynamic string table is not needed. + pub fn dynstr_len(&self) -> u32 { + if !self.dynstr_needed() { + return 0; + } debug_assert_ne!(self.dynstr_offset, 0); self.dynstr_size } @@ -1836,6 +1840,8 @@ impl<'a> Writer<'a, TwoPhase> { debug_assert_eq!(self.dynsym_offset, 0); debug_assert_eq!(self.dynsym_num, 0); self.dynsym_num = 1; + // The symbol table must link to a string table. + self.need_dynstr = true; SymbolIndex(0) } @@ -1852,6 +1858,8 @@ impl<'a> Writer<'a, TwoPhase> { debug_assert_eq!(self.dynsym_offset, 0); if self.dynsym_num == 0 { self.dynsym_num = 1; + // The symbol table must link to a string table. + self.need_dynstr = true; } let index = self.dynsym_num; self.dynsym_num += 1; diff --git a/tests/round_trip/elf.rs b/tests/round_trip/elf.rs index f98132e..86e7e25 100644 --- a/tests/round_trip/elf.rs +++ b/tests/round_trip/elf.rs @@ -109,6 +109,52 @@ fn empty_symtab() { assert_eq!(strtab.size(), 1); } +// .dynsym must link to .dynstr even when only the null dynamic symbol is present. +#[test] +fn empty_dynsym() { + let file_header = write::elf::FileHeader { + os_abi: elf::ELFOSABI_SYSV, + abi_version: 0, + e_type: elf::ET_DYN, + e_machine: elf::EM_X86_64, + e_entry: 0, + e_flags: elf::FileFlags(0), + }; + + let mut bytes = Vec::new(); + let mut writer = write::elf::Writer::new(Endianness::Little, true, &mut bytes); + + writer.reserve_file_header(); + writer.reserve_null_dynamic_symbol_index(); + writer.reserve_dynsym(); + writer.reserve_dynstr().unwrap(); + writer.reserve_null_section_index(); + writer.reserve_dynsym_section_index(); + let dynstr_index = writer.reserve_dynstr_section_index(); + writer.reserve_shstrtab_section_index(); + writer.reserve_shstrtab().unwrap(); + writer.reserve_section_headers(); + + writer.write_file_header(&file_header).unwrap(); + writer.write_null_dynamic_symbol(); + writer.write_dynstr(); + writer.write_shstrtab(); + writer.write_null_section_header(); + writer.write_dynsym_section_header(0, 1); + writer.write_dynstr_section_header(0); + writer.write_shstrtab_section_header(); + + let object = read::elf::ElfFile64::::parse(&*bytes).unwrap(); + let dynsym = object.section_by_name(".dynsym").unwrap(); + assert_eq!(dynsym.size(), 24); + let dynstr = object.section_by_name(".dynstr").unwrap(); + assert_eq!(dynstr.size(), 1); + assert_eq!( + dynsym.elf_section_header().sh_link(Endianness::Little), + dynstr_index.0, + ); +} + #[test] fn aligned_sections() { let mut object =