From 93d88fdbfb9f9a6fd3a3ee0644a6473d6aaea13d Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 24 Jan 2026 21:51:26 +0900 Subject: [PATCH 1/3] emit .dynstr for shared output --- lib/caotral/linker/builder.rb | 21 ++++++++++++++++++++- lib/caotral/linker/writer.rb | 11 +++++++++++ test/caotral/linker/shared-object_test.rb | 2 ++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index d803912..ecfbafa 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -50,6 +50,7 @@ def build section_name: ".shstrtab", header: Caotral::Binary::ELF::SectionHeader.new ) + start_bytes = [0xe8, *[0] * 4, 0x48, 0x89, 0xc7, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05] exec_text_offset = 0x1000 base_addr = 0x400000 @@ -150,6 +151,7 @@ def build entsize: 24 ) + sections += build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, text_section:) if @shared sections << symtab_section rel_sections.each { |s| sections << s.dup } @@ -161,7 +163,7 @@ def build entsize: 0 ) - @elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./]).each do |section| + @elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr"]).each do |section| sections << section.dup end @@ -254,6 +256,23 @@ def resolve_symbols end private + def build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, text_section:) + dynstr_section = Caotral::Binary::ELF::Section.new( + body: Caotral::Binary::ELF::Section::Strtab.new("\0".b), + section_name: ".dynstr", + header: Caotral::Binary::ELF::SectionHeader.new + ) + + dynstr_section.header.set!( + type: Caotral::Binary::ELF::SectionHeader::SHT[:strtab], + flags: 0, + addralign: 1, + entsize: 0 + ) + + [dynstr_section,] + end + def ref_index(sections, section_name) raise Caotral::Binary::ELF::Error, "invalid section name: #{section_name}" if section_name.nil? ref_names = "." + section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.join(".") diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 29cd6e4..bd7937e 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -41,6 +41,7 @@ def write gap = [text_offset - f.pos, 0].max f.write("\0" * gap) f.write(text_section.body) + write_shared_dynamic_sections(file: f) if @shared symtab_offset = f.pos symtab_section.body.each { |sym| f.write(sym.build) } symtab_entsize = symtab_section.body.first&.build&.bytesize.to_i @@ -100,6 +101,7 @@ def write_order_sections write_order = [] write_order << @elf_obj.sections.find { |s| s.section_name.nil? } write_order << @elf_obj.find_by_name(".text") + write_order << @elf_obj.find_by_name(".dynstr") write_order << @elf_obj.find_by_name(".symtab") write_order << @elf_obj.find_by_name(".strtab") write_order.concat(@elf_obj.select_by_names(RELOCATION_SECTION_NAMES)) @@ -107,6 +109,14 @@ def write_order_sections write_order.compact end def write_section_index(section_name) = @write_sections.index { it.section_name == section_name } + + def write_shared_dynamic_sections(file:) + dynstr_offset = file.pos + file.write(dynstr_section.body.build) + size = file.pos - dynstr_offset + dynstr_section.header.set!(offset: dynstr_offset, size:) + end + def ref_index(section_name) ref_name = section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" } ref_name = "." + ref_name.join(".") @@ -120,6 +130,7 @@ def rel_sections = @rel_sections ||= @write_sections.select { RELOCATION_SECTION def symtab_section = @symtab_section ||= @write_sections.find { |s| ".symtab" === s.section_name.to_s } def strtab_section = @strtab_section ||= @write_sections.find { |s| ".strtab" === s.section_name.to_s } def shstrtab_section = @shstrtab_section ||= @write_sections.find { |s| ".shstrtab" === s.section_name.to_s } + def dynstr_section = @dynstr_section ||= @write_sections.find { |s| ".dynstr" === s.section_name.to_s } end end end diff --git a/test/caotral/linker/shared-object_test.rb b/test/caotral/linker/shared-object_test.rb index 5356dbf..dbc1884 100644 --- a/test/caotral/linker/shared-object_test.rb +++ b/test/caotral/linker/shared-object_test.rb @@ -17,6 +17,8 @@ def teardown def test_link_shared_object Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", shared: true, executable: false) elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) + section_names = elf.sections.map(&:section_name) + assert_include(section_names, ".dynstr") assert_equal(:DYN, elf.header.type) assert_equal(:AMD64, elf.header.arch) end From a323bd83abe293c88b1d5e006186c2e0bd4dd8e7 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 24 Jan 2026 22:30:12 +0900 Subject: [PATCH 2/3] emit minimal .dynsym for shared output --- lib/caotral/linker/builder.rb | 18 ++++++++++++++++-- lib/caotral/linker/writer.rb | 23 ++++++++++++++++++++--- test/caotral/linker/shared-object_test.rb | 1 + 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index ecfbafa..263f461 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -163,7 +163,7 @@ def build entsize: 0 ) - @elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr"]).each do |section| + @elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym"]).each do |section| sections << section.dup end @@ -270,7 +270,21 @@ def build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, t entsize: 0 ) - [dynstr_section,] + dynsym_section = Caotral::Binary::ELF::Section.new( + body: [Caotral::Binary::ELF::Section::Symtab.new], + section_name: ".dynsym", + header: Caotral::Binary::ELF::SectionHeader.new + ) + + dynsym_section.header.set!( + type: Caotral::Binary::ELF::SectionHeader::SHT[:dynsym], + flags: 0, + addralign: 8, + entsize: 24 + ) + + + [dynstr_section, dynsym_section] end def ref_index(sections, section_name) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index bd7937e..f5b535c 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -68,7 +68,6 @@ def write f.write(shstrtab_section.body.names) shoffset = f.pos shstrndx = write_section_index(".shstrtab") - strtabndx = write_section_index(".strtab") symtabndx = write_section_index(".symtab") shnum = @write_sections.size @elf_obj.header.set!(shoffset:, shnum:, shstrndx:) @@ -79,8 +78,8 @@ def write lookup_name = section.section_name name_offset = names.offset_of(lookup_name) name, info, entsize = (name_offset.nil? ? 0 : name_offset), header.info, header.entsize - link = header.link - link = strtabndx if section.section_name == ".symtab" + link = link_index(section.section_name) + link = header.link if link.nil? if [:rel, :rela].include?(header.type) link = symtabndx info = ref_index(section.section_name) @@ -102,6 +101,7 @@ def write_order_sections write_order << @elf_obj.sections.find { |s| s.section_name.nil? } write_order << @elf_obj.find_by_name(".text") write_order << @elf_obj.find_by_name(".dynstr") + write_order << @elf_obj.find_by_name(".dynsym") write_order << @elf_obj.find_by_name(".symtab") write_order << @elf_obj.find_by_name(".strtab") write_order.concat(@elf_obj.select_by_names(RELOCATION_SECTION_NAMES)) @@ -115,6 +115,11 @@ def write_shared_dynamic_sections(file:) file.write(dynstr_section.body.build) size = file.pos - dynstr_offset dynstr_section.header.set!(offset: dynstr_offset, size:) + + dynsym_offset = file.pos + dynsym_section.body.each { |dynsym| file.write(dynsym.build) } + size = file.pos - dynsym_offset + dynsym_section.header.set!(offset: dynsym_offset, size:) end def ref_index(section_name) @@ -125,12 +130,24 @@ def ref_index(section_name) write_section_index(ref.section_name) end + def link_index(section_name) + case section_name + when ".symtab" + write_section_index(".strtab") + when ".dynsym" + write_section_index(".dynstr") + else + nil + end + end + def text_section = @text_section ||= @write_sections.find { |s| ".text" === s.section_name.to_s } def rel_sections = @rel_sections ||= @write_sections.select { RELOCATION_SECTION_NAMES.include?(it.section_name) } def symtab_section = @symtab_section ||= @write_sections.find { |s| ".symtab" === s.section_name.to_s } def strtab_section = @strtab_section ||= @write_sections.find { |s| ".strtab" === s.section_name.to_s } def shstrtab_section = @shstrtab_section ||= @write_sections.find { |s| ".shstrtab" === s.section_name.to_s } def dynstr_section = @dynstr_section ||= @write_sections.find { |s| ".dynstr" === s.section_name.to_s } + def dynsym_section = @dynsym_section ||= @write_sections.find { |s| ".dynsym" === s.section_name.to_s } end end end diff --git a/test/caotral/linker/shared-object_test.rb b/test/caotral/linker/shared-object_test.rb index dbc1884..2c45cad 100644 --- a/test/caotral/linker/shared-object_test.rb +++ b/test/caotral/linker/shared-object_test.rb @@ -19,6 +19,7 @@ def test_link_shared_object elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) section_names = elf.sections.map(&:section_name) assert_include(section_names, ".dynstr") + assert_include(section_names, ".dynsym") assert_equal(:DYN, elf.header.type) assert_equal(:AMD64, elf.header.arch) end From 93e6f5c3ca0c70e42c371a7a9cc953cb0e0e8cb9 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 27 Jan 2026 21:31:25 +0900 Subject: [PATCH 3/3] emit minimal .dynamic for shared output --- lib/caotral/binary/elf.rb | 1 + lib/caotral/binary/elf/section/dynamic.rb | 31 +++++++++++++++++++++++ lib/caotral/linker/builder.rb | 25 +++++++++--------- lib/caotral/linker/writer.rb | 9 ++++++- test/caotral/linker/shared-object_test.rb | 1 + 5 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 lib/caotral/binary/elf/section/dynamic.rb diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index e859004..fad0a02 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -3,6 +3,7 @@ require_relative "elf/header" require_relative "elf/program_header" require_relative "elf/section" +require_relative "elf/section/dynamic" require_relative "elf/section/rel" require_relative "elf/section/strtab" require_relative "elf/section/symtab" diff --git a/lib/caotral/binary/elf/section/dynamic.rb b/lib/caotral/binary/elf/section/dynamic.rb new file mode 100644 index 0000000..c531d20 --- /dev/null +++ b/lib/caotral/binary/elf/section/dynamic.rb @@ -0,0 +1,31 @@ +require "caotral/binary/elf/utils" +module Caotral + module Binary + class ELF + class Section + class Dynamic + include Caotral::Binary::ELF::Utils + TAG_TYPES = { NULL: 0 }.freeze + + def initialize + @tag = num2bytes(0, 8) + # d_un is a C union. Depending on d_tag, interpret this 8-byte field as either + # a pointer (d_ptr) or an integer value (d_val). + @un = num2bytes(0, 8) + end + + def set!(tag: nil, un: nil) + @tag = num2bytes(tag, 8) if check(tag, 8) + @un = num2bytes(un, 8) if check(un, 8) + self + end + + def tag = @tag.pack("C*").unpack1("Q<") + def null? = tag == TAG_TYPES[:NULL] + + private def bytes = [@tag, @un] + end + end + end + end +end diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 263f461..e6017b0 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -11,6 +11,7 @@ class Builder BIND_BY_VALUE = SYMTAB_BIND.invert.freeze RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze + GENERATED_SECTION_NAMES = [".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic"].freeze attr_reader :symbols @@ -163,7 +164,7 @@ def build entsize: 0 ) - @elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym"]).each do |section| + @elf_objs.first.without_sections(GENERATED_SECTION_NAMES).each do |section| sections << section.dup end @@ -257,18 +258,14 @@ def resolve_symbols private def build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, text_section:) + sht = Caotral::Binary::ELF::SectionHeader::SHT dynstr_section = Caotral::Binary::ELF::Section.new( body: Caotral::Binary::ELF::Section::Strtab.new("\0".b), section_name: ".dynstr", header: Caotral::Binary::ELF::SectionHeader.new ) - dynstr_section.header.set!( - type: Caotral::Binary::ELF::SectionHeader::SHT[:strtab], - flags: 0, - addralign: 1, - entsize: 0 - ) + dynstr_section.header.set!(type: sht[:strtab], flags: 0, addralign: 1, entsize: 0) dynsym_section = Caotral::Binary::ELF::Section.new( body: [Caotral::Binary::ELF::Section::Symtab.new], @@ -276,15 +273,17 @@ def build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, t header: Caotral::Binary::ELF::SectionHeader.new ) - dynsym_section.header.set!( - type: Caotral::Binary::ELF::SectionHeader::SHT[:dynsym], - flags: 0, - addralign: 8, - entsize: 24 + dynsym_section.header.set!(type: sht[:dynsym], flags: 0, addralign: 8, entsize: 24) + + dynamic_section = Caotral::Binary::ELF::Section.new( + body: [Caotral::Binary::ELF::Section::Dynamic.new], + section_name: ".dynamic", + header: Caotral::Binary::ELF::SectionHeader.new ) + dynamic_section.header.set!(type: sht[:dynamic], flags: 0, addralign: 8, entsize: 16) - [dynstr_section, dynsym_section] + [dynstr_section, dynsym_section, dynamic_section] end def ref_index(sections, section_name) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index f5b535c..c10a062 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -102,6 +102,7 @@ def write_order_sections write_order << @elf_obj.find_by_name(".text") write_order << @elf_obj.find_by_name(".dynstr") write_order << @elf_obj.find_by_name(".dynsym") + write_order << @elf_obj.find_by_name(".dynamic") write_order << @elf_obj.find_by_name(".symtab") write_order << @elf_obj.find_by_name(".strtab") write_order.concat(@elf_obj.select_by_names(RELOCATION_SECTION_NAMES)) @@ -120,6 +121,11 @@ def write_shared_dynamic_sections(file:) dynsym_section.body.each { |dynsym| file.write(dynsym.build) } size = file.pos - dynsym_offset dynsym_section.header.set!(offset: dynsym_offset, size:) + + dynamic_offset = file.pos + dynamic_section.body.each { |dynamic| file.write(dynamic.build) } + size = file.pos - dynamic_offset + dynamic_section.header.set!(offset: dynamic_offset, size:) end def ref_index(section_name) @@ -134,7 +140,7 @@ def link_index(section_name) case section_name when ".symtab" write_section_index(".strtab") - when ".dynsym" + when ".dynsym", ".dynamic" write_section_index(".dynstr") else nil @@ -148,6 +154,7 @@ def strtab_section = @strtab_section ||= @write_sections.find { |s| ".strtab" == def shstrtab_section = @shstrtab_section ||= @write_sections.find { |s| ".shstrtab" === s.section_name.to_s } def dynstr_section = @dynstr_section ||= @write_sections.find { |s| ".dynstr" === s.section_name.to_s } def dynsym_section = @dynsym_section ||= @write_sections.find { |s| ".dynsym" === s.section_name.to_s } + def dynamic_section = @dynamic_section ||= @write_sections.find { |s| ".dynamic" === s.section_name.to_s } end end end diff --git a/test/caotral/linker/shared-object_test.rb b/test/caotral/linker/shared-object_test.rb index 2c45cad..390c215 100644 --- a/test/caotral/linker/shared-object_test.rb +++ b/test/caotral/linker/shared-object_test.rb @@ -20,6 +20,7 @@ def test_link_shared_object section_names = elf.sections.map(&:section_name) assert_include(section_names, ".dynstr") assert_include(section_names, ".dynsym") + assert_include(section_names, ".dynamic") assert_equal(:DYN, elf.header.type) assert_equal(:AMD64, elf.header.arch) end