From 6e8f9c99dd8e62baf1e713ebb14db3317ba5330b Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 6 Aug 2026 11:29:40 +0900 Subject: [PATCH 1/8] use finalized ELF layout in writer --- lib/caotral/linker/writer.rb | 228 ++++------------------------------- 1 file changed, 26 insertions(+), 202 deletions(-) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index baa324a1..181eade4 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -24,43 +24,13 @@ def initialize(elf_obj:, output:, metadata: nil, entry: nil, debug: false, execu def write f = File.open(@output, "wb") - phoffset, phsize, ehsize = 64, 56, 64 - e_type = elf_type - phs = program_headers - - lph = load_program_header - iph = interp_program_header - pph = pie_program_header - dph = dynamic_program_header - - phnum = phs.size - header = @elf_obj.header.set!(type: e_type, phoffset:, phnum:, phsize:, ehsize:) - text_offset = text_section.header.offset - align = 0x1000 vaddr = text_section.header.addr - paddr = vaddr - type, flags = 1, program_header_flags(:RWX) - filesz = text_section.body.bytesize - memsz = filesz entry = non_executable? ? 0 : (@entry || vaddr) - header.set!(entry:) - lph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:) + @elf_obj.header.set!(entry:) f.write(@elf_obj.header.build) - phs.each { |ph| f.write(ph.build) } - pph.set!( - type: 6, - offset: phoffset, - vaddr: phoffset, - paddr: phoffset, - filesz: phsize * phnum, - memsz: phsize * phnum, - flags: program_header_flags(:R), - align: 8 - ) if pph - gap = [text_offset - f.pos, 0].max - f.write("\0" * gap) + program_headers.each { |ph| f.write(ph.build) } write_elf_sections(file: f) @@ -78,29 +48,17 @@ def write # relocation rel_sections.each do |rel| - rel_offset = f.pos - f.write(rel.build) - rel_size = f.pos - rel_offset - entsize = rel.body.respond_to?(:first) ? rel.body.first&.build&.bytesize.to_i : rel.header.entsize.to_i - rel.header.set!(offset: rel_offset, size: rel_size, entsize:) + write_section(file: f, section: rel) end rewrite_text_section(file: f) unless rel_text_sections.empty? patch_dynamic_sections(file: f) if dynamic? - patch_program_headers(file: f) - write_program_headers(file: f) - offset = f.pos - names = @write_sections.map { |s| s.section_name.to_s } - if names.last != ".shstrtab" - raise Caotral::Binary::ELF::Error, "section header string table must be the last section" - end - shstrtab_section.body.names = names.uniq.join("\0") + "\0" - shstrtab_section.header.set!(offset:, size: shstrtab_section.body.names.bytesize) - f.write(shstrtab_section.body.names) - shoffset = f.pos - write_section_headers(file: f, shoffset:) + write_section(file: f, section: shstrtab_section) + shoffset = @elf_obj.header.shoffset + f.seek(shoffset) + write_section_headers(file: f) output ensure f.close if f @@ -183,153 +141,30 @@ def patch_dynamic_sections(file:) end end - def patch_program_headers(file:) - if interp_program_header - ish = interp_section.header - interp_program_header.set!( - offset: ish.offset, - vaddr: 0, - paddr: 0, - filesz: ish.size, - memsz: ish.size, - flags: program_header_flags(:R), - align: 1 - ) - end - - if dynamic_program_header - dsh = dynamic_section.header - dynamic_program_header.set!( - offset: dsh.offset, - filesz: dsh.size, - memsz: dsh.size, - vaddr: dsh.addr || 0, - paddr: dsh.addr || 0, - flags: program_header_flags(:R), - align: dsh.addralign - ) - end - - segment_start = text_section.header.offset - segment_start = 0 if @pie - segment_end = [text_section, rodata_section, data_section,].concat(dynamic_sections).compact.map { |s| s.header.offset + s.header.size }.max - - dynamic_filesz = segment_end - segment_start - load_program_header.set!(filesz: dynamic_filesz, memsz: dynamic_filesz) - load_program_header.set!(offset: 0, vaddr: 0, paddr: 0) if @pie - end - - def write_program_headers(file:) - phoffset = @elf_obj.header.phoffset - phsize = @elf_obj.header.phsize - cur = file.pos - program_headers.each_with_index do |ph, idx| - file.seek(phoffset + (idx * phsize)) - file.write(ph.build) - end - file.seek(cur) - end - def write_elf_sections(file:) - text_offset = file.pos - file.write(text_section.build) - text_section.header.set!( - offset: text_offset, - size: text_section.body.bytesize, - addr: text_section.header.addr - ) - - if rodata_section - ordata_offset = file.pos - file.write(rodata_section.build) - rodata_section.header.set!( - offset: ordata_offset, - size: rodata_section.body.bytesize, - addr: text_section.header.addr + (ordata_offset - text_section.header.offset) - ) - end + write_section(file:, section: text_section) - if data_section - data_offset = file.pos - file.write(data_section.build) - data_section.header.set!( - offset: data_offset, - size: data_section.body.bytesize, - addr: text_section.header.addr + (data_offset - text_section.header.offset) - ) - end + write_section(file:, section: rodata_section) if rodata_section + write_section(file:, section: data_section) if data_section if plt_section - plt_offset = file.pos - file.write(plt_section.body.flatten.pack("C*")) - size = file.pos - plt_offset - plt_section.header.set!( - offset: plt_offset, - size:, - addr: text_section.header.addr + (plt_offset - text_section.header.offset) - ) - + write_section(file:, section: plt_section) raise Caotral::Binary::ELF::Error, "missing .got.plt for .plt" if got_plt_section.nil? - - got_plt_offset = file.pos - file.write(got_plt_section.body.flatten.pack("C*")) - size = file.pos - got_plt_offset - got_plt_section.header.set!( - offset: got_plt_offset, - size:, - addr: text_section.header.addr + (got_plt_offset - text_section.header.offset) - ) + write_section(file:, section: got_plt_section) end write_shared_dynamic_sections(file:) if dynamic? - - # section write - symtab_offset = file.pos - file.write(symtab_section.build) - symtab_entsize = symtab_section.body.first&.build&.bytesize.to_i - symtab_size = file.pos - symtab_offset - symtab_section.header.set!(offset: symtab_offset, size: symtab_size, entsize: symtab_entsize) - strtab_offset = file.pos - file.write(strtab_section.build) - strtab_section.header.set!(offset: strtab_offset, size: strtab_section.body.names.bytesize) + write_section(file:, section: symtab_section) + write_section(file:, section: strtab_section) end def write_section_index(section_name) = @write_sections.index { it.section_name == section_name } def write_shared_dynamic_sections(file:) - tsh = text_section&.header - text_addr = tsh&.addr || 0 - if interp_section - interp_offset = file.pos - file.write(interp_section.body) - size = file.pos - interp_offset - interp_section.header.set!(offset: interp_offset, size:, addr: text_addr + (interp_offset - tsh.offset)) - end - - pad_to_align(file:, align: dynstr_section.header.addralign) - dynstr_offset = file.pos - file.write(dynstr_section.body.build) - size = file.pos - dynstr_offset - dynstr_section.header.set!(offset: dynstr_offset, size:, addr: text_addr + (dynstr_offset - tsh.offset)) - - pad_to_align(file:, align: dynsym_section.header.addralign) - 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:, addr: text_addr + (dynsym_offset - tsh.offset)) - - if dynamic? - pad_to_align(file:, align: hash_section.header.addralign) - hash_offset = file.pos - file.write(hash_section.body.build) - size = file.pos - hash_offset - hash_section.header.set!(offset: hash_offset, size:, addr: text_addr + (hash_offset - tsh.offset)) - end - - pad_to_align(file:, align: dynamic_section.header.addralign) - 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:, addr: text_addr + (dynamic_offset - tsh.offset)) + write_section(file:, section: interp_section) if interp_section + write_section(file:, section: dynstr_section) if dynstr_section + write_section(file:, section: dynsym_section) if dynsym_section + write_section(file:, section: hash_section) if dynamic? + write_section(file:, section: dynamic_section) if dynamic_section if plt_section current_offset = file.pos @@ -364,7 +199,7 @@ def write_shared_dynamic_sections(file:) file.seek(current_offset) end end - + def ref_index(section_name) return 0 if section_name == ".rela.dyn" ref_name = section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" } @@ -387,12 +222,9 @@ def link_index(section_name) end end - def write_section_headers(file:, shoffset:) - shnum = @write_sections.size - shstrndx = write_section_index(".shstrtab") + def write_section_headers(file:) symtabndx = write_section_index(".symtab") - @elf_obj.header.set!(shoffset:, shnum:, shstrndx:) names = shstrtab_section.body @write_sections.each do |section| header = section.header @@ -416,27 +248,19 @@ def write_section_headers(file:, shoffset:) header.set!(name:, info:, link:, entsize:) file.write(section.header.build) end - file.seek(0) - file.write(@elf_obj.header.build) end - def pad_to_align(file:, align:) - pos = file.pos - padding = (align - (pos % align)) % align - file.write("\0" * padding) - end + def write_section(file:, section:) + return unless section - def program_header_flags(flag) = Caotral::Binary::ELF::ProgramHeader::PF[flag.to_sym] - def elf_type = Caotral::Binary::ELF::Header::TYPE[dynamic? ? :DYN : :EXEC] + file.seek(section.header.offset) + file.write(section.build) + end def non_executable? = (@shared || !@executable) def dynamic? = (@shared || @pie) def dynamic_tables = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES - def pie_program_header = @pie_program_header ||= program_headers.find { |ph| ph.type == :PHDR } - def load_program_header = @load_program_header ||= program_headers.find { |ph| ph.type == :LOAD } - def interp_program_header = @interp_program_header ||= program_headers.find { |ph| ph.type == :INTERP } - def dynamic_program_header = @dynamic_program_header ||= program_headers.find { |ph| ph.type == :DYNAMIC } def text_section = @text_section ||= @write_sections.find { |s| ".text" === s.section_name.to_s } def rel_sections = @rel_sections ||= @write_sections.select { |s| RELOCATION_SECTION_NAMES.include?(s.section_name.to_s) } From 7f70615b941b3290599921ebb28f7fb41c1348f6 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 6 Aug 2026 15:03:22 +0900 Subject: [PATCH 2/8] add linker finalizer --- lib/caotral/linker.rb | 1 + lib/caotral/linker/builder.rb | 1 + lib/caotral/linker/finalizer.rb | 17 +++++++++++++++++ lib/caotral/linker/writer.rb | 16 +++++----------- sig/caotral/linker/finalizer.rbs | 10 ++++++++++ sig/caotral/linker/writer.rbs | 4 ++-- 6 files changed, 36 insertions(+), 13 deletions(-) create mode 100644 lib/caotral/linker/finalizer.rb create mode 100644 sig/caotral/linker/finalizer.rbs diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 6b33bebc..8a75cce0 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -2,6 +2,7 @@ require_relative "binary/elf/reader" require_relative "linker/builder" require_relative "linker/layout" +require_relative "linker/finalizer" require_relative "linker/writer" module Caotral diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 61e8971a..5686d44b 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -426,6 +426,7 @@ def build @linker_metadata[:pending_text_relocations] = rel_texts Caotral::Linker::Layout.new(elf:, shared: @shared, executable: @executable, pie: @pie).apply! + Caotral::Linker::Finalizer.new(elf:, metadata: @linker_metadata, shared: @shared, executable: @executable, pie: @pie, debug: @debug).apply! elf end diff --git a/lib/caotral/linker/finalizer.rb b/lib/caotral/linker/finalizer.rb new file mode 100644 index 00000000..b28babb9 --- /dev/null +++ b/lib/caotral/linker/finalizer.rb @@ -0,0 +1,17 @@ +module Caotral + class Linker + class Finalizer + def initialize(elf:, metadata:, shared:, executable:, pie:, debug:) + @elf, @metadata = elf, metadata + @shared, @executable, @pie = shared, executable, pie + end + + def apply! + text = @elf.find_by_name(".text") + entry = @executable ? text&.header&.addr : 0 + @elf.header.set!(entry:) + @elf + end + end + end +end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 181eade4..0db100a9 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -7,13 +7,13 @@ class Writer REL_TYPES = Caotral::Binary::ELF::Section::Rel::TYPES.freeze ALLOW_RELOCATION_TYPES = [REL_TYPES[:AMD64_PC32], REL_TYPES[:AMD64_PLT32]].freeze RELOCATION_SECTION_NAMES = [".rela.text", ".rela.dyn", ".rela.data", ".rela.plt"].freeze - attr_reader :elf_obj, :output, :entry, :debug, :got_plt_offsets, :pending_text_relocations, :program_headers - def self.write!(elf_obj:, output:, metadata: nil, entry: nil, debug: false, executable: true, shared: false) - new(elf_obj:, output:, metadata:, entry:, debug:, shared:, executable:).write + attr_reader :elf_obj, :output, :debug, :got_plt_offsets, :pending_text_relocations, :program_headers + def self.write!(elf_obj:, output:, metadata: nil, debug: false, executable: true, shared: false) + new(elf_obj:, output:, metadata:, debug:, shared:, executable:).write end - def initialize(elf_obj:, output:, metadata: nil, entry: nil, debug: false, executable: true, shared: false, pie: false) - @elf_obj, @output, @entry, @debug, @executable, @shared, @pie = elf_obj, output, entry, debug, executable, shared, pie + def initialize(elf_obj:, output:, metadata: nil, debug: false, executable: true, shared: false, pie: false) + @elf_obj, @output, @debug, @executable, @shared, @pie = elf_obj, output, debug, executable, shared, pie @program_headers = elf_obj.program_headers @write_sections = elf_obj.sections @got_plt_offsets = {} @@ -25,10 +25,6 @@ def initialize(elf_obj:, output:, metadata: nil, entry: nil, debug: false, execu def write f = File.open(@output, "wb") - vaddr = text_section.header.addr - entry = non_executable? ? 0 : (@entry || vaddr) - - @elf_obj.header.set!(entry:) f.write(@elf_obj.header.build) program_headers.each { |ph| f.write(ph.build) } @@ -257,9 +253,7 @@ def write_section(file:, section:) file.write(section.build) end - def non_executable? = (@shared || !@executable) def dynamic? = (@shared || @pie) - def dynamic_tables = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES def text_section = @text_section ||= @write_sections.find { |s| ".text" === s.section_name.to_s } diff --git a/sig/caotral/linker/finalizer.rbs b/sig/caotral/linker/finalizer.rbs new file mode 100644 index 00000000..8ce93120 --- /dev/null +++ b/sig/caotral/linker/finalizer.rbs @@ -0,0 +1,10 @@ +class Caotral::Linker::Finalizer + @elf: Caotral::Binary::ELF + @metadata: Hash[Symbol, untyped] + @shared: bool + @executable: bool + @pie: bool + + def initialize: (elf: Caotral::Binary::ELF, metadata: Hash[Symbol, untyped], shared: bool, executable: bool, pie: bool, debug: bool) -> void + def apply!: () -> Caotral::Binary::ELF +end diff --git a/sig/caotral/linker/writer.rbs b/sig/caotral/linker/writer.rbs index 87b6091a..0ee0dc81 100644 --- a/sig/caotral/linker/writer.rbs +++ b/sig/caotral/linker/writer.rbs @@ -1,5 +1,5 @@ class Caotral::Linker::Writer - def self.write!: (elf_obj: Caotral::Binary::ELF, output: String, ?entry: Integer, ?debug: bool, ?executable: bool, ?shared: bool) -> String - def initialize: (elf_obj: Caotral::Binary::ELF, output: String, ?entry: Integer, ?debug: bool, ?executable: bool, ?shared: bool) -> void + def self.write!: (elf_obj: Caotral::Binary::ELF, output: String, ?metadata: Hash[Symbol, untyped]?, ?debug: bool, ?executable: bool, ?shared: bool) -> String + def initialize: (elf_obj: Caotral::Binary::ELF, output: String, ?metadata: Hash[Symbol, untyped]?, ?debug: bool, ?executable: bool, ?shared: bool, ?pie: bool) -> void def write: () -> String end From af542969bdb6702fc0cc28cdf58880aad9887181 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 6 Aug 2026 19:35:39 +0900 Subject: [PATCH 3/8] finalize PLT relocations --- lib/caotral/linker/finalizer.rb | 31 ++++++++++++++++++++++++++++++- lib/caotral/linker/writer.rb | 12 ------------ 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/lib/caotral/linker/finalizer.rb b/lib/caotral/linker/finalizer.rb index b28babb9..33624b2e 100644 --- a/lib/caotral/linker/finalizer.rb +++ b/lib/caotral/linker/finalizer.rb @@ -1,16 +1,45 @@ +require "caotral/binary/elf" + +require_relative "error" + module Caotral class Linker class Finalizer + REL_TYPES = Caotral::Binary::ELF::Section::Rel::TYPES.freeze + def initialize(elf:, metadata:, shared:, executable:, pie:, debug:) @elf, @metadata = elf, metadata @shared, @executable, @pie = shared, executable, pie end def apply! + finalize_entry! + finalize_plt_relocations! + @elf + end + + def finalize_entry! text = @elf.find_by_name(".text") entry = @executable ? text&.header&.addr : 0 @elf.header.set!(entry:) - @elf + end + + def finalize_plt_relocations! + rela_plt = @elf.find_by_name(".rela.plt") + got_plt = @elf.find_by_name(".got.plt") + dynsym = @elf.find_by_name(".dynsym") + symtab = @elf.find_by_name(".symtab") + dynstr = @elf.find_by_name(".dynstr") + return unless rela_plt && got_plt + rela_plt&.body&.each do |rel| + sym = symtab.body[rel.sym] + dynsymndx = dynsym.body.index { |ds| ds.name_offset == dynstr.body.offset_of(sym.name_string) } + raise Caotral::Linker::Error, "cannot find symbol #{sym.name_string} in .dynsym for relocation in .rela.plt" if dynsymndx.nil? + rel.set!( + info: (dynsymndx << 32) | REL_TYPES[:AMD64_JUMP_SLOT], + offset: rel.offset + got_plt.header.addr + ) + end end end end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 0db100a9..ff81b651 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -30,18 +30,6 @@ def write write_elf_sections(file: f) - if rela_plt_section && got_plt_section - rela_plt_section&.body&.each do |rel| - sym = symtab_section.body[rel.sym] - dynsymndx = dynsym_section.body.index { |ds| ds.name_offset == dynstr_section.body.offset_of(sym.name_string) } - raise Caotral::Binary::ELF::Error, "cannot find symbol #{sym.name_string} in .dynsym for relocation in .rela.plt" if dynsymndx.nil? - rel.set!( - info: (dynsymndx << 32) | REL_TYPES[:AMD64_JUMP_SLOT], - offset: rel.offset + got_plt_section.header.addr - ) - end - end - # relocation rel_sections.each do |rel| write_section(file: f, section: rel) From 78b26bdd998a10bb7669b69ac643a8d602819960 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 6 Aug 2026 22:03:12 +0900 Subject: [PATCH 4/8] finalize text relocations --- lib/caotral/linker.rb | 2 +- lib/caotral/linker/finalizer.rb | 50 ++++++++++++++++++++++++++---- lib/caotral/linker/writer.rb | 49 +++-------------------------- sig/caotral/linker/writer.rbs | 4 +-- test/caotral/linker/writer_test.rb | 2 +- 5 files changed, 52 insertions(+), 55 deletions(-) diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 8a75cce0..d45f2635 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -61,7 +61,7 @@ def to_elf(inputs: @inputs, output: @output, debug: @debug, shared: @shared, exe builder.resolve_symbols elf_obj = builder.build metadata = builder.linker_metadata - Caotral::Linker::Writer.new(elf_obj:, output:, metadata:, debug:, shared:, executable:, pie:).write + Caotral::Linker::Writer.new(elf_obj:, output:, debug:, shared:, executable:, pie:).write File.chmod(0755, output) if executable output end diff --git a/lib/caotral/linker/finalizer.rb b/lib/caotral/linker/finalizer.rb index 33624b2e..7f90c59c 100644 --- a/lib/caotral/linker/finalizer.rb +++ b/lib/caotral/linker/finalizer.rb @@ -6,6 +6,7 @@ module Caotral class Linker class Finalizer REL_TYPES = Caotral::Binary::ELF::Section::Rel::TYPES.freeze + ALLOW_RELOCATION_TYPES = [REL_TYPES[:AMD64_PC32], REL_TYPES[:AMD64_PLT32]].freeze def initialize(elf:, metadata:, shared:, executable:, pie:, debug:) @elf, @metadata = elf, metadata @@ -15,21 +16,17 @@ def initialize(elf:, metadata:, shared:, executable:, pie:, debug:) def apply! finalize_entry! finalize_plt_relocations! + finalize_text_relocations! @elf end + private def finalize_entry! - text = @elf.find_by_name(".text") entry = @executable ? text&.header&.addr : 0 @elf.header.set!(entry:) end def finalize_plt_relocations! - rela_plt = @elf.find_by_name(".rela.plt") - got_plt = @elf.find_by_name(".got.plt") - dynsym = @elf.find_by_name(".dynsym") - symtab = @elf.find_by_name(".symtab") - dynstr = @elf.find_by_name(".dynstr") return unless rela_plt && got_plt rela_plt&.body&.each do |rel| sym = symtab.body[rel.sym] @@ -41,6 +38,47 @@ def finalize_plt_relocations! ) end end + + def finalize_text_relocations! + return unless pending_text_relocations && got_plt_offsets + grouped_rels = pending_text_relocations.group_by { |rel| rel.header.info } + grouped_rels.each do |target_index, rels| + target = @elf.sections[target_index] + bytes = target.body.dup + symtab_body = symtab.body + vaddr = target.header.addr + rels.each do |rel| + rel.body.each do |entry| + next unless ALLOW_RELOCATION_TYPES.include?(entry.type) + sym = symtab_body[entry.sym] + next if sym.nil? + target_addr = target == text ? vaddr : target.header.addr + sym_offset = entry.offset + sym_addend = entry.addend? ? entry.addend : bytes[sym_offset, 4].unpack1("l<") + sym_addr = if sym.shndx == 0 + plt.header.addr + 16 * (((got_plt_offsets[entry.sym] - 24) / 8) + 1) + elsif sym.shndx >= 0xff00 + sym.value + else + @elf.sections[sym.shndx].header.addr + sym.value + end + value = sym_addr + sym_addend - (target_addr + sym_offset) + bytes[sym_offset, 4] = [value].pack("l<") + end + end + target.body = bytes + end + end + + def text = @elf.find_by_name(".text") + def plt = @elf.find_by_name(".plt") + def rela_plt = @elf.find_by_name(".rela.plt") + def got_plt = @elf.find_by_name(".got.plt") + def dynsym = @elf.find_by_name(".dynsym") + def symtab = @elf.find_by_name(".symtab") + def dynstr = @elf.find_by_name(".dynstr") + def pending_text_relocations = @metadata.fetch(:pending_text_relocations, []) + def got_plt_offsets = @metadata.fetch(:got_plt_offsets, {}) end end end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index ff81b651..2960a8bc 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -5,21 +5,16 @@ class Linker class Writer include Caotral::Binary::ELF::Utils REL_TYPES = Caotral::Binary::ELF::Section::Rel::TYPES.freeze - ALLOW_RELOCATION_TYPES = [REL_TYPES[:AMD64_PC32], REL_TYPES[:AMD64_PLT32]].freeze RELOCATION_SECTION_NAMES = [".rela.text", ".rela.dyn", ".rela.data", ".rela.plt"].freeze - attr_reader :elf_obj, :output, :debug, :got_plt_offsets, :pending_text_relocations, :program_headers - def self.write!(elf_obj:, output:, metadata: nil, debug: false, executable: true, shared: false) - new(elf_obj:, output:, metadata:, debug:, shared:, executable:).write + attr_reader :elf_obj, :output, :debug, :program_headers + def self.write!(elf_obj:, output:, debug: false, executable: true, shared: false) + new(elf_obj:, output:, debug:, shared:, executable:).write end - def initialize(elf_obj:, output:, metadata: nil, debug: false, executable: true, shared: false, pie: false) + def initialize(elf_obj:, output:, debug: false, executable: true, shared: false, pie: false) @elf_obj, @output, @debug, @executable, @shared, @pie = elf_obj, output, debug, executable, shared, pie @program_headers = elf_obj.program_headers @write_sections = elf_obj.sections - @got_plt_offsets = {} - @got_plt_offsets = metadata.fetch(:got_plt_offsets, {}) if metadata - @pending_text_relocations = [] - @pending_text_relocations = metadata.fetch(:pending_text_relocations, []) if metadata end def write @@ -35,8 +30,6 @@ def write write_section(file: f, section: rel) end - rewrite_text_section(file: f) unless rel_text_sections.empty? - patch_dynamic_sections(file: f) if dynamic? write_section(file: f, section: shstrtab_section) @@ -49,39 +42,6 @@ def write end private - def rewrite_text_section(file:) - cur = file.pos - grouped_rels = rel_text_sections.group_by { |rel| rel.header.info } - grouped_rels.each do |target_index, rels| - target = @elf_obj.sections[target_index] - bytes = target.body.dup - symtab_body = symtab_section.body - vaddr = target.header.addr - file.seek(target.header.offset) - rels.each do |rel| - rel.body.each do |entry| - next unless ALLOW_RELOCATION_TYPES.include?(entry.type) - sym = symtab_body[entry.sym] - next if sym.nil? - target_addr = target == text_section ? vaddr : target.header.addr - sym_offset = entry.offset - sym_addend = entry.addend? ? entry.addend : bytes[sym_offset, 4].unpack1("l<") - sym_addr = if sym.shndx == 0 - plt_section.header.addr + 16 * (((got_plt_offsets[entry.sym] - 24) / 8) + 1) - elsif sym.shndx >= 0xff00 - sym.value - else - @elf_obj.sections[sym.shndx].header.addr + sym.value - end - value = sym_addr + sym_addend - (target_addr + sym_offset) - bytes[sym_offset, 4] = [value].pack("l<") - end - end - file.write(bytes) - end - file.seek(cur) - end - def patch_dynamic_sections(file:) dynamic_sections.each do |dyn| addr = text_section.header.addr + (dyn.header.offset - text_section.header.offset) @@ -246,7 +206,6 @@ def dynamic_tables = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES def text_section = @text_section ||= @write_sections.find { |s| ".text" === s.section_name.to_s } def rel_sections = @rel_sections ||= @write_sections.select { |s| RELOCATION_SECTION_NAMES.include?(s.section_name.to_s) } - def rel_text_sections = @rel_text_sections ||= @pending_text_relocations 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 } diff --git a/sig/caotral/linker/writer.rbs b/sig/caotral/linker/writer.rbs index 0ee0dc81..b20ccd88 100644 --- a/sig/caotral/linker/writer.rbs +++ b/sig/caotral/linker/writer.rbs @@ -1,5 +1,5 @@ class Caotral::Linker::Writer - def self.write!: (elf_obj: Caotral::Binary::ELF, output: String, ?metadata: Hash[Symbol, untyped]?, ?debug: bool, ?executable: bool, ?shared: bool) -> String - def initialize: (elf_obj: Caotral::Binary::ELF, output: String, ?metadata: Hash[Symbol, untyped]?, ?debug: bool, ?executable: bool, ?shared: bool, ?pie: bool) -> void + def self.write!: (elf_obj: Caotral::Binary::ELF, output: String, ?debug: bool, ?executable: bool, ?shared: bool) -> String + def initialize: (elf_obj: Caotral::Binary::ELF, output: String, ?debug: bool, ?executable: bool, ?shared: bool, ?pie: bool) -> void def write: () -> String end diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index 53c5c4df..a749e37f 100644 --- a/test/caotral/linker/writer_test.rb +++ b/test/caotral/linker/writer_test.rb @@ -36,7 +36,7 @@ def test_relocation_write_and_execute builder = Caotral::Linker::Builder.new(elf_objs: [input_elf], debug: false) builder.resolve_symbols elf_obj = builder.build - Caotral::Linker::Writer.write!(elf_obj:, output: "relocated_exec", debug: false, metadata: builder.linker_metadata) + Caotral::Linker::Writer.write!(elf_obj:, output: "relocated_exec", debug: false) File.chmod(0755, "./relocated_exec") IO.popen("./relocated_exec").close exit_code, _handle_code = check_process($?.to_i) From 0440deeadd9819b1335fc0c32b756f94ebfef3b9 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 6 Aug 2026 23:09:23 +0900 Subject: [PATCH 5/8] finalize dynamic sections --- lib/caotral/linker/builder.rb | 7 +++-- lib/caotral/linker/finalizer.rb | 53 ++++++++++++++++++++++++++------ lib/caotral/linker/writer.rb | 54 +-------------------------------- 3 files changed, 49 insertions(+), 65 deletions(-) diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 5686d44b..ce3834f7 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -336,7 +336,8 @@ def build hash.chain[i] = num2bytes(0, 4) end hash_section.body = hash - dynamic_section = build_dynamic_section(dynstr:) + textrel = rela_dyn_section.body.any? { |rel| rel.type == REL_TYPES[:AMD64_RELATIVE] } + dynamic_section = build_dynamic_section(dynstr:, textrel:) if rela_plt_section.body.size == 0 && dynamic? bodies = dynamic_section.body.reject { |ent| REJECT_DYNAMIC_TAGS.include?(ent.tag) } dynamic_section.body = bodies @@ -500,7 +501,7 @@ def build_hash_section hash_section end - def build_dynamic_section(dynstr:) + def build_dynamic_section(dynstr:, textrel:) tag_types = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES dynamic_section = Caotral::Binary::ELF::Section.new( body: [ @@ -516,7 +517,7 @@ def build_dynamic_section(dynstr:) SectionDynamic.new.set!(tag: tag_types[:SYMTAB]), SectionDynamic.new.set!(tag: tag_types[:SYMENT], un: 24), SectionDynamic.new.set!(tag: tag_types[:PLTREL]), - SectionDynamic.new.set!(tag: tag_types[:TEXTREL]), + textrel ? SectionDynamic.new.set!(tag: tag_types[:TEXTREL]) : nil, SectionDynamic.new.set!(tag: tag_types[:JMPREL]), SectionDynamic.new ].compact, diff --git a/lib/caotral/linker/finalizer.rb b/lib/caotral/linker/finalizer.rb index 7f90c59c..703e9570 100644 --- a/lib/caotral/linker/finalizer.rb +++ b/lib/caotral/linker/finalizer.rb @@ -7,6 +7,7 @@ class Linker class Finalizer REL_TYPES = Caotral::Binary::ELF::Section::Rel::TYPES.freeze ALLOW_RELOCATION_TYPES = [REL_TYPES[:AMD64_PC32], REL_TYPES[:AMD64_PLT32]].freeze + DYNAMIC_TABLES = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES def initialize(elf:, metadata:, shared:, executable:, pie:, debug:) @elf, @metadata = elf, metadata @@ -17,6 +18,7 @@ def apply! finalize_entry! finalize_plt_relocations! finalize_text_relocations! + finalize_dynamic_sections! if dynamic? @elf end @@ -70,15 +72,48 @@ def finalize_text_relocations! end end - def text = @elf.find_by_name(".text") - def plt = @elf.find_by_name(".plt") - def rela_plt = @elf.find_by_name(".rela.plt") - def got_plt = @elf.find_by_name(".got.plt") - def dynsym = @elf.find_by_name(".dynsym") - def symtab = @elf.find_by_name(".symtab") - def dynstr = @elf.find_by_name(".dynstr") - def pending_text_relocations = @metadata.fetch(:pending_text_relocations, []) - def got_plt_offsets = @metadata.fetch(:got_plt_offsets, {}) + def finalize_dynamic_sections! + dynsym.body.each do |dynsym_body| + if dynsym_body.shndx != 0 + value = dynsym_body.value + secndx = @elf.sections[dynsym_body.shndx]&.header&.addr + unless secndx.nil? + value += secndx + dynsym_body.set!(value:) + end + end + end + if dynamic? && dynamic && rela_dyn + rdsh = rela_dyn&.header + bodies = dynamic.body + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:RELA] }.set!(un: rdsh&.addr.to_i) + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:RELASZ] }.set!(un: rdsh&.size.to_i) + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:STRSZ] }&.set!(un: dynstr.header.size.to_i) + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:SYMENT] }&.set!(un: dynsym.header.entsize.to_i) + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:STRTAB] }&.set!(un: dynstr.header.addr.to_i) + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:SYMTAB] }&.set!(un: dynsym.header.addr.to_i) + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:HASH] }&.set!(un: hash_section.header.addr.to_i) if hash_section + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:PLTRELSZ] }&.set!(un: rela_plt.header.size.to_i) + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:JMPREL] }&.set!(un: rela_plt.header.addr.to_i) + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:PLTREL] }&.set!(un: DYNAMIC_TABLES[:RELA]) + bodies.find { |dyn| dyn.tag == DYNAMIC_TABLES[:PLTGOT] }&.set!(un: got_plt.header.addr.to_i) + end + end + + def text = @text ||= @elf.find_by_name(".text") + def plt = @plt ||= @elf.find_by_name(".plt") + def rela_plt = @rela_plt ||= @elf.find_by_name(".rela.plt") + def got_plt = @got_plt ||= @elf.find_by_name(".got.plt") + def dynsym = @dynsym ||= @elf.find_by_name(".dynsym") + def symtab = @symtab ||= @elf.find_by_name(".symtab") + def dynstr = @dynstr ||= @elf.find_by_name(".dynstr") + def interp = @interp ||= @elf.find_by_name(".interp") + def hash_section = @hash_section ||= @elf.find_by_name(".hash") + def dynamic = @dynamic ||= @elf.find_by_name(".dynamic") + def rela_dyn = @rela_dyn ||= @elf.find_by_name(".rela.dyn") + def pending_text_relocations = @pending_text_relocations ||= @metadata.fetch(:pending_text_relocations, []) + def got_plt_offsets = @got_plt_offsets ||= @metadata.fetch(:got_plt_offsets, {}) + def dynamic? = (@shared || @pie) end end end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 2960a8bc..fd9a8766 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -4,7 +4,6 @@ module Caotral class Linker class Writer include Caotral::Binary::ELF::Utils - REL_TYPES = Caotral::Binary::ELF::Section::Rel::TYPES.freeze RELOCATION_SECTION_NAMES = [".rela.text", ".rela.dyn", ".rela.data", ".rela.plt"].freeze attr_reader :elf_obj, :output, :debug, :program_headers def self.write!(elf_obj:, output:, debug: false, executable: true, shared: false) @@ -26,12 +25,7 @@ def write write_elf_sections(file: f) # relocation - rel_sections.each do |rel| - write_section(file: f, section: rel) - end - - patch_dynamic_sections(file: f) if dynamic? - + rel_sections.each { |rel| write_section(file: f, section: rel) } write_section(file: f, section: shstrtab_section) shoffset = @elf_obj.header.shoffset f.seek(shoffset) @@ -42,49 +36,6 @@ def write end private - def patch_dynamic_sections(file:) - dynamic_sections.each do |dyn| - addr = text_section.header.addr + (dyn.header.offset - text_section.header.offset) - dyn.header.set!(addr:) - end - - cur = file.pos - file.seek(dynsym_section.header.offset) - dynsym_section.body.each do |dynsym_body| - if dynsym_body.shndx != 0 - value = dynsym_body.value - secndx = @write_sections[dynsym_body.shndx]&.header&.addr - unless secndx.nil? - value += secndx - dynsym_body.set!(value:) - end - end - file.write(dynsym_body.build) - end - file.seek(cur) - - if dynamic? && dynamic_section && rela_dyn_section - rdsh = rela_dyn_section&.header - bodies = dynamic_section.body - bodies.delete_if { |dyn| dyn.tag == dynamic_tables[:TEXTREL] } unless rela_dyn_section.body.any? { |rel| rel.type == REL_TYPES[:AMD64_RELATIVE] } - bodies.find { |dyn| dyn.tag == dynamic_tables[:RELA] }.set!(un: rdsh&.addr.to_i) - bodies.find { |dyn| dyn.tag == dynamic_tables[:RELASZ] }.set!(un: rdsh&.size.to_i) - bodies.find { |dyn| dyn.tag == dynamic_tables[:STRSZ] }&.set!(un: dynstr_section.header.size.to_i) - bodies.find { |dyn| dyn.tag == dynamic_tables[:SYMENT] }&.set!(un: dynsym_section.header.entsize.to_i) - bodies.find { |dyn| dyn.tag == dynamic_tables[:STRTAB] }&.set!(un: dynstr_section.header.addr.to_i) - bodies.find { |dyn| dyn.tag == dynamic_tables[:SYMTAB] }&.set!(un: dynsym_section.header.addr.to_i) - bodies.find { |dyn| dyn.tag == dynamic_tables[:HASH] }&.set!(un: hash_section.header.addr.to_i) if hash_section - bodies.find { |dyn| dyn.tag == dynamic_tables[:PLTRELSZ] }&.set!(un: rela_plt_section.header.size.to_i) - bodies.find { |dyn| dyn.tag == dynamic_tables[:JMPREL] }&.set!(un: rela_plt_section.header.addr.to_i) - bodies.find { |dyn| dyn.tag == dynamic_tables[:PLTREL] }&.set!(un: dynamic_tables[:RELA]) - bodies.find { |dyn| dyn.tag == dynamic_tables[:PLTGOT] }&.set!(un: got_plt_section.header.addr.to_i) - cur = file.pos - file.seek(dynamic_section.header.offset) - file.write(dynamic_section.build) - file.seek(cur) - end - end - def write_elf_sections(file:) write_section(file:, section: text_section) @@ -202,7 +153,6 @@ def write_section(file:, section:) end def dynamic? = (@shared || @pie) - def dynamic_tables = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES def text_section = @text_section ||= @write_sections.find { |s| ".text" === s.section_name.to_s } def rel_sections = @rel_sections ||= @write_sections.select { |s| RELOCATION_SECTION_NAMES.include?(s.section_name.to_s) } @@ -220,8 +170,6 @@ def hash_section = @hash_section ||= @write_sections.find { |s| ".hash" === s.se def plt_section = @plt_section ||= @write_sections.find { |s| ".plt" === s.section_name.to_s } def got_plt_section = @got_plt_section ||= @write_sections.find { |s| ".got.plt" === s.section_name.to_s } def rela_plt_section = @rela_plt_section ||= @write_sections.find { |s| ".rela.plt" === s.section_name.to_s } - - def dynamic_sections = @dynamic_sections ||= [interp_section, dynstr_section, dynsym_section, hash_section, dynamic_section, rela_dyn_section, rela_plt_section].compact end end end From cca7dfdc850a0d3f51d9877deaaf91d24175eb9d Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 6 Aug 2026 23:41:19 +0900 Subject: [PATCH 6/8] finalize PLT and GOT sections --- lib/caotral/linker/finalizer.rb | 22 ++++++++++++++++++++++ lib/caotral/linker/writer.rb | 33 --------------------------------- 2 files changed, 22 insertions(+), 33 deletions(-) diff --git a/lib/caotral/linker/finalizer.rb b/lib/caotral/linker/finalizer.rb index 703e9570..2c865ae8 100644 --- a/lib/caotral/linker/finalizer.rb +++ b/lib/caotral/linker/finalizer.rb @@ -19,6 +19,7 @@ def apply! finalize_plt_relocations! finalize_text_relocations! finalize_dynamic_sections! if dynamic? + finalize_shared_sections! if dynamic? && plt @elf end @@ -100,6 +101,27 @@ def finalize_dynamic_sections! end end + def finalize_shared_sections! + primary, *rest = plt.body + plt_addr = plt.header.addr + got_plt_addr = got_plt.header.addr + # only support x86-64 binaries with PLT + primary[2..5] = [(got_plt_addr + 8) - (plt_addr + 6)].pack("l<").bytes + primary[8..11] = [(got_plt_addr + 16) - (plt_addr + 12)].pack("l<").bytes + slot_offset = 24 + rest.each_with_index do |entry, i| + entry_addr = plt_addr + 16 + 16 * i + slot_addr = got_plt_addr + slot_offset + 8 * i + entry[2..5] = [slot_addr - (entry_addr + 6)].pack("l<").bytes + entry[7..10] = [i].pack("l<").bytes + entry[12..15] = [plt_addr - (entry_addr + 16)].pack("l<").bytes + end + primary = got_plt.body.first + rest = got_plt.body.drop(3) + primary.replace([dynamic.header.addr].pack("Q<").bytes) + rest.each_with_index { |entry, i| entry.replace([plt_addr + 22 + 16 * i].pack("Q<").bytes) } + end + def text = @text ||= @elf.find_by_name(".text") def plt = @plt ||= @elf.find_by_name(".plt") def rela_plt = @rela_plt ||= @elf.find_by_name(".rela.plt") diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index fd9a8766..47f51d85 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -60,39 +60,6 @@ def write_shared_dynamic_sections(file:) write_section(file:, section: dynsym_section) if dynsym_section write_section(file:, section: hash_section) if dynamic? write_section(file:, section: dynamic_section) if dynamic_section - - if plt_section - current_offset = file.pos - primary, *rest = plt_section.body - plt_offset = plt_section.header.offset - got_plt_offset = got_plt_section.header.offset - file.seek(plt_offset) - plt_addr = plt_section.header.addr - got_plt_addr = got_plt_section.header.addr - # only support x86-64 binaries with PLT - primary[2..5] = [(got_plt_addr + 8) - (plt_addr + 6)].pack("l<").bytes - primary[8..11] = [(got_plt_addr + 16) - (plt_addr + 12)].pack("l<").bytes - slot_offset = 24 - rest.each_with_index do |entry, i| - entry_addr = plt_addr + 16 + 16 * i - slot_addr = got_plt_addr + slot_offset + 8 * i - entry[2..5] = [slot_addr - (entry_addr + 6)].pack("l<").bytes - entry[7..10] = [i].pack("l<").bytes - entry[12..15] = [plt_addr - (entry_addr + 16)].pack("l<").bytes - end - file.write(primary.flatten.pack("C*")) - file.write(rest.flatten.pack("C*")) - - file.seek(got_plt_offset) - primary, secondary, third, *rest = got_plt_section.body - primary = [dynamic_section.header.addr].pack("Q<").bytes - rest.each_with_index { |_entry, i| rest[i] = [plt_addr + 22 + 16 * i].pack("Q<").bytes } - file.write(primary.flatten.pack("C*")) - file.write(secondary.flatten.pack("C*")) - file.write(third.flatten.pack("C*")) - file.write(rest.flatten.pack("C*")) - file.seek(current_offset) - end end def ref_index(section_name) From b4b2826051511b901353808108ecc04ec8aaf75b Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Fri, 7 Aug 2026 00:42:07 +0900 Subject: [PATCH 7/8] finalize section headers --- lib/caotral/linker/finalizer.rb | 49 +++++++++++++++++++++++++++++++++ lib/caotral/linker/writer.rb | 49 +-------------------------------- 2 files changed, 50 insertions(+), 48 deletions(-) diff --git a/lib/caotral/linker/finalizer.rb b/lib/caotral/linker/finalizer.rb index 2c865ae8..6ba58341 100644 --- a/lib/caotral/linker/finalizer.rb +++ b/lib/caotral/linker/finalizer.rb @@ -20,6 +20,7 @@ def apply! finalize_text_relocations! finalize_dynamic_sections! if dynamic? finalize_shared_sections! if dynamic? && plt + finalize_section_headers! @elf end @@ -122,6 +123,53 @@ def finalize_shared_sections! rest.each_with_index { |entry, i| entry.replace([plt_addr + 22 + 16 * i].pack("Q<").bytes) } end + def finalize_section_headers! + symtabndx = @elf.sections.index { |s| s.section_name == ".symtab" } + names = shstrtab.body + @elf.sections.each do |section| + header = section.header + lookup_name = section.section_name + name = names.offset_of(lookup_name) || 0 + info, entsize = header.info, header.entsize + link = link_index(section.section_name) + link = header.link if link.nil? + if [:rela, :rel].include?(header.type) + if [".rela.dyn", ".rela.plt"].include?(section.section_name.to_s) + entsize = 24 + elsif ".rela.text" == section.section_name.to_s + info = @elf.index(".text") + entsize = 24 + link = symtabndx + else + link = symtabndx + end + info = ref_index(section.section_name) unless ".rela.plt" == section.section_name.to_s + end + header.set!(name:, info:, link:, entsize:) + end + end + + def ref_index(section_name) + return 0 if section_name == ".rela.dyn" + ref_name = section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.join(".") + index = @elf.index(".#{ref_name}") + raise Caotral::Linker::Error, "cannot find reference section for #{section_name}" unless index + index + end + + def link_index(section_name) + case section_name + when ".symtab" + @elf.index(".strtab") + when ".dynsym", ".dynamic" + @elf.index(".dynstr") + when ".hash" + @elf.index(".dynsym") + else + nil + end + end + def text = @text ||= @elf.find_by_name(".text") def plt = @plt ||= @elf.find_by_name(".plt") def rela_plt = @rela_plt ||= @elf.find_by_name(".rela.plt") @@ -133,6 +181,7 @@ def interp = @interp ||= @elf.find_by_name(".interp") def hash_section = @hash_section ||= @elf.find_by_name(".hash") def dynamic = @dynamic ||= @elf.find_by_name(".dynamic") def rela_dyn = @rela_dyn ||= @elf.find_by_name(".rela.dyn") + def shstrtab = @shstrtab ||= @elf.find_by_name(".shstrtab") def pending_text_relocations = @pending_text_relocations ||= @metadata.fetch(:pending_text_relocations, []) def got_plt_offsets = @got_plt_offsets ||= @metadata.fetch(:got_plt_offsets, {}) def dynamic? = (@shared || @pie) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 47f51d85..136965f2 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -52,7 +52,6 @@ def write_elf_sections(file:) write_section(file:, section: symtab_section) write_section(file:, section: strtab_section) end - def write_section_index(section_name) = @write_sections.index { it.section_name == section_name } def write_shared_dynamic_sections(file:) write_section(file:, section: interp_section) if interp_section @@ -62,54 +61,8 @@ def write_shared_dynamic_sections(file:) write_section(file:, section: dynamic_section) if dynamic_section end - def ref_index(section_name) - return 0 if section_name == ".rela.dyn" - ref_name = section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" } - ref_name = "." + ref_name.join(".") - ref = @write_sections.find { |s| s.section_name == ref_name } - raise Caotral::Binary::ELF::Error, "cannot find reference section for #{section_name}" if ref.nil? - write_section_index(ref.section_name) - end - - def link_index(section_name) - case section_name - when ".symtab" - write_section_index(".strtab") - when ".dynsym", ".dynamic" - write_section_index(".dynstr") - when ".hash" - write_section_index(".dynsym") - else - nil - end - end - def write_section_headers(file:) - symtabndx = write_section_index(".symtab") - - names = shstrtab_section.body - @write_sections.each do |section| - header = section.header - lookup_name = section.section_name - name = names.offset_of(lookup_name) || 0 - info, entsize = header.info, header.entsize - link = link_index(section.section_name) - link = header.link if link.nil? - if [:rela, :rel].include?(header.type) - if [".rela.dyn", ".rela.plt"].include?(section.section_name.to_s) - entsize = 24 - elsif ".rela.text" == section.section_name.to_s - info = write_section_index(".text") - entsize = 24 - link = symtabndx - else - link = symtabndx - end - info = ref_index(section.section_name) unless ".rela.plt" == section.section_name.to_s - end - header.set!(name:, info:, link:, entsize:) - file.write(section.header.build) - end + @write_sections.each { |section| file.write(section.header.build) } end def write_section(file:, section:) From 48b0f3e975cbdab3692910e21d085f48f4c2490a Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Fri, 7 Aug 2026 01:01:51 +0900 Subject: [PATCH 8/8] orchestrate linker stages --- lib/caotral/linker.rb | 5 ++++- lib/caotral/linker/builder.rb | 3 --- test/caotral/linker/writer_test.rb | 14 +++++++++++--- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index d45f2635..c27e6f5d 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -59,8 +59,11 @@ def to_elf(inputs: @inputs, output: @output, debug: @debug, shared: @shared, exe elf_objs = inputs.map { |input| Caotral::Binary::ELF::Reader.new(input:, debug:).read } builder = Caotral::Linker::Builder.new(elf_objs:, debug:, shared:, executable:, pie:, needed:) builder.resolve_symbols - elf_obj = builder.build + elf = builder.build metadata = builder.linker_metadata + Caotral::Linker::Layout.new(elf:, shared:, executable:, pie:).apply! + elf_obj = Caotral::Linker::Finalizer.new(elf:, metadata:, shared:, executable:, pie:, debug:).apply! + Caotral::Linker::Writer.new(elf_obj:, output:, debug:, shared:, executable:, pie:).write File.chmod(0755, output) if executable output diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index ce3834f7..644d23c1 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -426,9 +426,6 @@ def build @linker_metadata[:got_plt_offsets] = got_plt_offsets @linker_metadata[:pending_text_relocations] = rel_texts - Caotral::Linker::Layout.new(elf:, shared: @shared, executable: @executable, pie: @pie).apply! - Caotral::Linker::Finalizer.new(elf:, metadata: @linker_metadata, shared: @shared, executable: @executable, pie: @pie, debug: @debug).apply! - elf end diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index a749e37f..734dd6ce 100644 --- a/test/caotral/linker/writer_test.rb +++ b/test/caotral/linker/writer_test.rb @@ -4,7 +4,12 @@ class Caotral::Linker::WriterTest < Test::Unit::TestCase def setup Caotral.assemble(input: "sample/assembler/plus.s", assembler: "self", output: "plus.o") elf_obj = Caotral::Binary::ELF::Reader.read!(input: "plus.o", debug: false) - @elf_obj = Caotral::Linker::Builder.new(elf_objs: [elf_obj], debug: false).build + builder = Caotral::Linker::Builder.new(elf_objs: [elf_obj], debug: false) + builder.resolve_symbols + @elf_obj = builder.build + metadata = builder.linker_metadata + Caotral::Linker::Layout.new(elf: @elf_obj, shared: false, pie: false, executable: true).apply! + Caotral::Linker::Finalizer.new(elf: @elf_obj, metadata:, shared: false, pie: false, executable: true, debug: false).apply! end def teardown File.delete("plus.o") if File.exist?("plus.o") @@ -35,8 +40,11 @@ def test_relocation_write_and_execute input_elf = Caotral::Binary::ELF::Reader.read!(input: "relocatable.o", debug: false) builder = Caotral::Linker::Builder.new(elf_objs: [input_elf], debug: false) builder.resolve_symbols - elf_obj = builder.build - Caotral::Linker::Writer.write!(elf_obj:, output: "relocated_exec", debug: false) + elf = builder.build + metadata = builder.linker_metadata + Caotral::Linker::Layout.new(elf:, shared: false, pie: false, executable: true).apply! + Caotral::Linker::Finalizer.new(elf:, metadata:, shared: false, pie: false, executable: true, debug: false).apply! + Caotral::Linker::Writer.write!(elf_obj: elf, output: "relocated_exec", debug: false) File.chmod(0755, "./relocated_exec") IO.popen("./relocated_exec").close exit_code, _handle_code = check_process($?.to_i)