From b803795561baaade0cad53bfe8c3ae0768c6c97a Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 2 Apr 2026 23:16:30 +0900 Subject: [PATCH 01/10] add needed option for linker --- lib/caotral/linker.rb | 7 ++++--- sample/C/needed.c | 6 ++++++ test/caotral/linker/needed_test.rb | 11 +++++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 sample/C/needed.c create mode 100644 test/caotral/linker/needed_test.rb diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 2663d63..4e0baa4 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -5,13 +5,14 @@ module Caotral class Linker - def self.link!(inputs:, output: "a.out", linker: "mold", debug: false, shared: false, executable: true, pie: false) - new(inputs:, output:, linker:, debug:, shared:, executable:, pie:).link + def self.link!(inputs:, output: "a.out", linker: "mold", debug: false, shared: false, executable: true, pie: false, needed: []) + new(inputs:, output:, linker:, debug:, shared:, executable:, pie:, needed:).link end - def initialize(inputs:, output: "a.out", linker: "mold", linker_options: [], executable: true, shared: false, pie: false, debug: false) + def initialize(inputs:, output: "a.out", linker: "mold", linker_options: [], executable: true, shared: false, pie: false, debug: false, needed: []) @inputs, @output, @linker = inputs, output, linker @options = linker_options + @needed = needed @executable, @debug, @shared, @pie = executable, debug, shared, pie end diff --git a/sample/C/needed.c b/sample/C/needed.c new file mode 100644 index 0000000..4509b17 --- /dev/null +++ b/sample/C/needed.c @@ -0,0 +1,6 @@ +extern int puts(const char *); + +int main(void) { + puts("hello, glibc"); + return 0; +} diff --git a/test/caotral/linker/needed_test.rb b/test/caotral/linker/needed_test.rb new file mode 100644 index 0000000..efd0703 --- /dev/null +++ b/test/caotral/linker/needed_test.rb @@ -0,0 +1,11 @@ +require_relative "../../test_suite" + +class LinkerNeededTest < Test::Unit::TestCase + def test_needed_options + compile_path = Pathname.new("sample/C/needed.c").to_s + IO.popen(["gcc", "-fPIE", "-c", "-o", "needed.o", "%s" % compile_path]).close + + linker = Caotral::Linker.new(inputs: ["needed.o"], linker: "self", pie: true, needed: ["libc.so.6"]) + assert_equal ["libc.so.6"], linker.instance_variable_get(:@needed) + end +end From 6bc6e715c4378290b0e46f2ccd11a67702603d93 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 4 Apr 2026 15:00:23 +0900 Subject: [PATCH 02/10] add needed in dynamic section --- lib/caotral/binary/elf/section/dynamic.rb | 3 ++ lib/caotral/linker.rb | 4 +-- lib/caotral/linker/builder.rb | 39 ++++++++++++----------- test/caotral/linker/needed_test.rb | 21 ++++++++++-- 4 files changed, 44 insertions(+), 23 deletions(-) diff --git a/lib/caotral/binary/elf/section/dynamic.rb b/lib/caotral/binary/elf/section/dynamic.rb index 0641e8a..8f2da23 100644 --- a/lib/caotral/binary/elf/section/dynamic.rb +++ b/lib/caotral/binary/elf/section/dynamic.rb @@ -1,4 +1,5 @@ require "caotral/binary/elf/utils" + module Caotral module Binary class ELF @@ -7,6 +8,7 @@ class Dynamic include Caotral::Binary::ELF::Utils TAG_TYPES = { NULL: 0, + NEEDED: 1, PLTRELSZ: 2, PLTGOT: 3, HASH: 4, @@ -46,6 +48,7 @@ def jmp_rel? = tag == TAG_TYPES[:JMPREL] def plt_rel? = tag == TAG_TYPES[:PLTREL] def plt_rel_size? = tag == TAG_TYPES[:PLTRELSZ] def plt_got? = tag == TAG_TYPES[:PLTGOT] + def needed? = tag == TAG_TYPES[:NEEDED] private def bytes = [@tag, @un] end diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 4e0baa4..973a14a 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -50,9 +50,9 @@ def link_command(inputs: @inputs, output: @output) def libpath = @libpath ||= File.dirname(Dir.glob("/usr/lib*/**/crti.o").last) def gcc_libpath = @gcc_libpath ||= File.dirname(Dir.glob("/usr/lib/gcc/x86_64-*/*/crtbegin.o").last) - def to_elf(inputs: @inputs, output: @output, debug: @debug, shared: @shared, executable: @executable, pie: @pie) + def to_elf(inputs: @inputs, output: @output, debug: @debug, shared: @shared, executable: @executable, pie: @pie, needed: @needed) elf_objs = inputs.map { |input| Caotral::Binary::ELF::Reader.new(input:, debug:).read } - builder = Caotral::Linker::Builder.new(elf_objs:, debug:, shared:, executable:, pie:) + builder = Caotral::Linker::Builder.new(elf_objs:, debug:, shared:, executable:, pie:, needed:) builder.resolve_symbols elf_obj = builder.build Caotral::Linker::Writer.new(elf_obj:, output:, debug:, shared:, executable:, pie:).write diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index f4f0a18..d866a59 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -14,6 +14,7 @@ class Builder GENERATED_SECTION_NAMES = [".text", ".data", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic", ".interp", ".rela.dyn", ".plt", ".got.plt"].freeze SHT = Caotral::Binary::ELF::SectionHeader::SHT SHF = Caotral::Binary::ELF::SectionHeader::SHF + SectionDynamic = Caotral::Binary::ELF::Section::Dynamic UNSUPPORTED_REL_TYPES = [ REL_TYPES[:AMD64_GOTPCREL], REL_TYPES[:AMD64_GOTPCRELX], @@ -27,9 +28,9 @@ class Builder attr_reader :symbols - def initialize(elf_objs:, executable: true, debug: false, shared: false, pie: false) + def initialize(elf_objs:, executable: true, debug: false, shared: false, pie: false, needed: []) @elf_objs = elf_objs - @executable, @debug, @shared, @pie = executable, debug, shared, pie + @executable, @debug, @shared, @pie, @needed = executable, debug, shared, pie, needed @symbols = { locals: Set.new, globals: Set.new, weaks: Set.new } _mode! end @@ -264,6 +265,7 @@ def build sections += build_pie_sections if @pie if dynamic? + @needed.each { |lib| dynstr.body.names += lib + "\0" if dynstr.body.offset_of(lib).nil? } sections << dynstr sections << dynsym hash_section = build_hash_section @@ -293,7 +295,7 @@ def build hash.chain[i] = num2bytes(0, 4) end hash_section.body = hash - dynamic_section = build_dynamic_section + dynamic_section = build_dynamic_section(dynstr:) if rela_plt_section.body.size == 0 && dynamic? bodies = dynamic_section.body.reject { |ent| REJECT_DYNAMIC_TAGS.include?(ent.tag) } dynamic_section.body = bodies @@ -444,24 +446,25 @@ def build_hash_section hash_section end - def build_dynamic_section + def build_dynamic_section(dynstr:) tag_types = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES dynamic_section = Caotral::Binary::ELF::Section.new( body: [ - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:PLTRELSZ]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:PLTGOT]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:HASH]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:RELA]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:RELASZ]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:RELAENT], un: 24), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:STRTAB]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:STRSZ]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:SYMTAB]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:SYMENT], un: 24), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:PLTREL]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:TEXTREL]), - Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:JMPREL]), - Caotral::Binary::ELF::Section::Dynamic.new + *@needed.map { |lib| SectionDynamic.new.set!(tag: tag_types[:NEEDED], un: dynstr.body.offset_of(lib)) }, + SectionDynamic.new.set!(tag: tag_types[:PLTRELSZ]), + SectionDynamic.new.set!(tag: tag_types[:PLTGOT]), + SectionDynamic.new.set!(tag: tag_types[:HASH]), + SectionDynamic.new.set!(tag: tag_types[:RELA]), + SectionDynamic.new.set!(tag: tag_types[:RELASZ]), + SectionDynamic.new.set!(tag: tag_types[:RELAENT], un: 24), + SectionDynamic.new.set!(tag: tag_types[:STRTAB]), + SectionDynamic.new.set!(tag: tag_types[:STRSZ]), + 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]), + SectionDynamic.new.set!(tag: tag_types[:JMPREL]), + SectionDynamic.new ].compact, section_name: ".dynamic", header: Caotral::Binary::ELF::SectionHeader.new diff --git a/test/caotral/linker/needed_test.rb b/test/caotral/linker/needed_test.rb index efd0703..c5f612f 100644 --- a/test/caotral/linker/needed_test.rb +++ b/test/caotral/linker/needed_test.rb @@ -1,11 +1,26 @@ require_relative "../../test_suite" class LinkerNeededTest < Test::Unit::TestCase + def setup + @input = "needed.o" + @output = "needed" + end + + def teardown + File.delete(@input) if File.exist?(@input) + File.delete(@output) if File.exist?(@output) + end + def test_needed_options compile_path = Pathname.new("sample/C/needed.c").to_s - IO.popen(["gcc", "-fPIE", "-c", "-o", "needed.o", "%s" % compile_path]).close + IO.popen(["gcc", "-fPIE", "-c", "-o", @input, "%s" % compile_path]).close - linker = Caotral::Linker.new(inputs: ["needed.o"], linker: "self", pie: true, needed: ["libc.so.6"]) - assert_equal ["libc.so.6"], linker.instance_variable_get(:@needed) + Caotral::Linker.link!(inputs: [@input], output: @output, linker: "self", pie: true, needed: ["libc.so.6"]) + + elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) + dynstr = elf.find_by_name(".dynstr") + needed = elf.find_by_name(".dynamic").body.select(&:needed?) + assert_equal(1, needed.size) + assert_equal(dynstr.body.lookup(needed.first.un), "libc.so.6") end end From 1849b94f30d9f42f319b9952578be8b6ba238d33 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 4 Apr 2026 19:27:34 +0900 Subject: [PATCH 03/10] exclude RELTEXT without .rel.text --- lib/caotral/binary/elf/reader.rb | 4 +++- lib/caotral/binary/elf/section/symtab.rb | 3 ++- lib/caotral/linker/writer.rb | 1 + test/caotral/linker/needed_test.rb | 17 ++++++++++++++++- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/caotral/binary/elf/reader.rb b/lib/caotral/binary/elf/reader.rb index 6f716ac..ffde1b3 100644 --- a/lib/caotral/binary/elf/reader.rb +++ b/lib/caotral/binary/elf/reader.rb @@ -97,7 +97,9 @@ def read shndx = sym_bin[6, 2].unpack1("S<") value = sym_bin[8, 8].unpack1("Q<") size = sym_bin[16, 8].unpack1("Q<") - Caotral::Binary::ELF::Section::Symtab.new.set!(name:, info:, other:, shndx:, value:, size:) + name_string = @context.sections[section.header.link]&.body&.lookup(name).to_s + + Caotral::Binary::ELF::Section::Symtab.new.set!(name:, info:, other:, shndx:, value:, size:, name_string:) end when :rel, :rela rela = type == :rela diff --git a/lib/caotral/binary/elf/section/symtab.rb b/lib/caotral/binary/elf/section/symtab.rb index c6f5676..e923f30 100644 --- a/lib/caotral/binary/elf/section/symtab.rb +++ b/lib/caotral/binary/elf/section/symtab.rb @@ -19,13 +19,14 @@ def initialize(**opts) end def build = bytes.flatten.pack("C*") - def set!(name: nil, info: nil, other: nil, shndx: nil, value: nil, size: nil) + def set!(name: nil, info: nil, other: nil, shndx: nil, value: nil, size: nil, name_string: nil) @name = num2bytes(name, 4) if check(name, 4) @info = num2bytes(info, 1) if check(info, 1) @other = num2bytes(other, 1) if check(other, 1) @shndx = num2bytes(shndx, 2) if check(shndx, 2) @value = num2bytes(value, 8) if check(value, 8) @size = num2bytes(size, 8) if check(size, 8) + @name_string = name_string if name_string self end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index cb3e886..f842b76 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -126,6 +126,7 @@ def patch_dynamic_sections(file:) 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) diff --git a/test/caotral/linker/needed_test.rb b/test/caotral/linker/needed_test.rb index c5f612f..fe0ac94 100644 --- a/test/caotral/linker/needed_test.rb +++ b/test/caotral/linker/needed_test.rb @@ -1,6 +1,8 @@ require_relative "../../test_suite" class LinkerNeededTest < Test::Unit::TestCase + include TestProcessHelper + def setup @input = "needed.o" @output = "needed" @@ -11,7 +13,7 @@ def teardown File.delete(@output) if File.exist?(@output) end - def test_needed_options + def test_needed_in_dynamic_section compile_path = Pathname.new("sample/C/needed.c").to_s IO.popen(["gcc", "-fPIE", "-c", "-o", @input, "%s" % compile_path]).close @@ -19,8 +21,21 @@ def test_needed_options elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) dynstr = elf.find_by_name(".dynstr") + dynsym = elf.find_by_name(".dynsym") needed = elf.find_by_name(".dynamic").body.select(&:needed?) assert_equal(1, needed.size) assert_equal(dynstr.body.lookup(needed.first.un), "libc.so.6") + assert_include(dynsym.body.map(&:name_string), "puts") + end + + def test_needed_in_executable + compile_path = Pathname.new("sample/C/needed.c").to_s + IO.popen(["gcc", "-fPIE", "-c", "-o", @input, "%s" % compile_path]).close + + Caotral::Linker.link!(inputs: [@input], output: @output, linker: "self", pie: true, needed: ["libc.so.6"], executable: true) + + IO.popen(["./#{@output}"]).close + pid, exit_code = check_process($?.to_i) + assert_equal(0, exit_code) end end From 7f6e98819582166f4aca18b4fd09cc1810d57ffd Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 4 Apr 2026 23:33:47 +0900 Subject: [PATCH 04/10] support DT_NEEDED and _start entry in self linker --- lib/caotral/linker/builder.rb | 35 +++++++++++++++++++++++------------ sample/C/start.c | 10 ++++++++++ 2 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 sample/C/start.c diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index d866a59..1509e92 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -31,7 +31,7 @@ class Builder def initialize(elf_objs:, executable: true, debug: false, shared: false, pie: false, needed: []) @elf_objs = elf_objs @executable, @debug, @shared, @pie, @needed = executable, debug, shared, pie, needed - @symbols = { locals: Set.new, globals: Set.new, weaks: Set.new } + @symbols = { locals: Set.new, globals: Set.new, weaks: Set.new, }.freeze _mode! end @@ -117,6 +117,9 @@ def build got_plt_offset = 8 sym_by_elf = Hash.new { |h, k| h[k] = [] } dynstr, dynsym = build_shared_dynamic_sections if dynamic? + + start_len = 0 if @elf_objs.any? { |elf| elf.find_by_name(".symtab").body.find { |sym| sym.name_string == "_start" } } + @elf_objs.each do |elf_obj| text = elf_obj.find_by_name(".text") unless text.nil? @@ -158,6 +161,7 @@ def build header: Caotral::Binary::ELF::SectionHeader.new ) section.body.each do |rel| + sym = base_index.nil? ? rel.sym : base_index + rel.sym if rel.type == REL_TYPES[:AMD64_64] base_offset = case section.section_name.to_s when /\.rela?\.text/ @@ -178,7 +182,7 @@ def build addend = sym_addr - base_addr rela_dyn_section.body << Caotral::Binary::ELF::Section::Rel.new.set!(offset:, info: (0 << 32) | REL_TYPES[:AMD64_RELATIVE], addend:) next - elsif (undefined = symtab.body[rel.sym]&.shndx.to_i == 0) && rel.type == REL_TYPES[:AMD64_PLT32] + elsif (undefined = symtab.body[rel.sym]&.shndx.to_i == 0) && ALLOW_RELOCATION_TYPES.include?(rel.type) sym = base_index.to_i + rel.sym first_insertion = got_plt_offsets[sym].nil? got_plt_offsets[sym] ||= got_plt_offset.tap { got_plt_offset += 8 } @@ -188,6 +192,7 @@ def build offset: got_plt_offsets[sym], info: ((sym) << 32) | REL_TYPES[:AMD64_JUMP_SLOT] ) + name = symtab_section.body[sym].name_string dynstr_index = dynstr.body.offset_of(name) if dynstr_index.nil? @@ -198,7 +203,6 @@ def build ) end rela_plt_section.body << rps - next end elsif UNSUPPORTED_REL_TYPES.include?(rel.type) raise Caotral::Binary::ELF::Error, "unsupported relocation type: #{rel.type_name}" @@ -206,7 +210,6 @@ def build offset = rel.offset + text_offsets.fetch(elf_obj.object_id, 0) addend = rel.addend? ? rel.addend : nil new_rel = Caotral::Binary::ELF::Section::Rel.new(addend: rel.addend?) - sym = base_index.nil? ? rel.sym : base_index + rel.sym info = (sym << 32) | rel.type new_rel.set!(offset:, info:, addend:) rel_section.body << new_rel @@ -215,15 +218,19 @@ def build end rel_sections += rels end - + strtab_section.body.names = strtab_names.to_a.sort.join("\0") + "\0" sections << null_section - main_sym = symtab_section.body.find { |sym| sym.name_string == "main" } - raise Caotral::Binary::ELF::Error, "main function not found" if @executable && main_sym.nil? - main_offset = main_sym.nil? ? 0 : main_sym.value + start_len - start_bytes[1, 4] = num2bytes((main_offset - 5), 4) if @executable - text_section.body.prepend(start_bytes.pack("C*")) + _start = symtab_section.body.find { |sym| sym.name_string == "_start" } + entry_sym = _start || symtab_section.body.find { |sym| sym.name_string == "main" } + + raise Caotral::Binary::ELF::Error, "main or _start function not found" if @executable && entry_sym.nil? + if _start.nil? + main_offset = entry_sym.nil? ? 0 : entry_sym.value + start_len + start_bytes[1, 4] = num2bytes((main_offset - 5), 4) if @executable + text_section.body.prepend(start_bytes.pack("C*")) + end text_section.header.set!( type: 1, @@ -369,11 +376,15 @@ def build rel.body.each do |entry| next unless ALLOW_RELOCATION_TYPES.include?(entry.type) sym = symtab_body[entry.sym] - next if sym.nil? || sym.shndx == 0 + next if sym.nil? target_addr = target == text_section ? vaddr : target.header.addr - sym_addr = sym.shndx >= 0xff00 ? sym.value : sections[sym.shndx].then { |st| st.header.addr + sym.value } sym_offset = entry.offset + start_len 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] / 8) + else + sym.shndx >= 0xff00 ? sym.value : sections[sym.shndx].then { |st| st.header.addr + sym.value } + end value = sym_addr + sym_addend - (target_addr + sym_offset) bytes[sym_offset, 4] = [value].pack("l<") end diff --git a/sample/C/start.c b/sample/C/start.c new file mode 100644 index 0000000..2b2fdba --- /dev/null +++ b/sample/C/start.c @@ -0,0 +1,10 @@ +typedef unsigned long size_t; + +extern long write(int fd, const void *buf, size_t count); +extern void _exit(int status); + +void _start(void) { + static const char msg [] = "hello, world!!!\n"; + write(1, msg, sizeof(msg) - 1); + _exit(0); +} From d2c0653e44ffb9f52e4c2fa31a4b8a8a7f494ed5 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sun, 5 Apr 2026 21:16:48 +0900 Subject: [PATCH 05/10] prepare plt entries for dynamic self linking --- lib/caotral/linker/builder.rb | 4 +++- lib/caotral/linker/writer.rb | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 1509e92..29f52f2 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -56,7 +56,7 @@ def build .set!(type: SHT[:progbits], flags: SHF[:ALLOC] | SHF[:WRITE], addralign: 8) ) plt_section = Caotral::Binary::ELF::Section.new( - body: [0, 0].pack("Q Date: Sun, 5 Apr 2026 22:42:49 +0900 Subject: [PATCH 06/10] prepare and patch plt entries for dynamic self linking --- lib/caotral/linker/writer.rb | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 18b857d..51a02bc 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -220,9 +220,9 @@ def write_elf_sections(file:) size:, addr: text_section.header.addr + (plt_offset - text_section.header.offset) ) - end - if got_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.build) got_plt_section.header.set!( @@ -230,6 +230,26 @@ def write_elf_sections(file:) size: got_plt_section.body.bytesize, addr: text_section.header.addr + (got_plt_offset - text_section.header.offset) ) + + current_offset = file.pos + primary, *rest = plt_section.body + 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(current_offset) end write_shared_dynamic_sections(file:) if dynamic? From 49ad62f0b88e052a7c4512abcb8bfe4a43cddf0b Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Mon, 6 Apr 2026 00:02:47 +0900 Subject: [PATCH 07/10] initialize plt and got.plt entries --- lib/caotral/linker/builder.rb | 7 +++-- lib/caotral/linker/writer.rb | 58 ++++++++++++++++++++++------------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 29f52f2..cc2c16d 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -62,7 +62,7 @@ def build .set!(type: SHT[:progbits], flags: SHF[:ALLOC] | SHF[:EXECINSTR], addralign: 16) ) got_plt_section = Caotral::Binary::ELF::Section.new( - body: [0].pack("Q<"), + body: [[0] * 8, [0] * 8, [0] * 8], section_name: ".got.plt", header: Caotral::Binary::ELF::SectionHeader.new .set!(type: SHT[:progbits], flags: SHF[:ALLOC] | SHF[:WRITE], addralign: 8) @@ -114,7 +114,7 @@ def build got_plt_offsets = {} text_offset = 0 data_offset = 0 - got_plt_offset = 8 + got_plt_offset = 24 sym_by_elf = Hash.new { |h, k| h[k] = [] } dynstr, dynsym = build_shared_dynamic_sections if dynamic? @@ -187,7 +187,8 @@ def build first_insertion = got_plt_offsets[sym].nil? got_plt_offsets[sym] ||= got_plt_offset.tap { got_plt_offset += 8 } if dynamic? && undefined && first_insertion - got_plt_section.body << [0].pack("Q<") + got_plt_body = [0]*8 + got_plt_section.body << got_plt_body rps = Caotral::Binary::ELF::Section::Rel.new.set!( offset: got_plt_offsets[sym], info: ((sym) << 32) | REL_TYPES[:AMD64_JUMP_SLOT] diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 51a02bc..63fa0a4 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -224,32 +224,13 @@ def write_elf_sections(file:) 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.build) + 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: got_plt_section.body.bytesize, + size:, addr: text_section.header.addr + (got_plt_offset - text_section.header.offset) ) - - current_offset = file.pos - primary, *rest = plt_section.body - 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(current_offset) end write_shared_dynamic_sections(file:) if dynamic? @@ -301,6 +282,39 @@ def write_shared_dynamic_sections(file:) 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)) + + 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 09e52e8d1e03bef7b9961c95a876de7d93a46f1a Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 11 Apr 2026 22:44:25 +0900 Subject: [PATCH 08/10] prepare writer-side text relocation rewriting --- lib/caotral/binary/elf.rb | 4 ++- lib/caotral/linker/builder.rb | 24 ++------------- lib/caotral/linker/writer.rb | 56 +++++++++++++++++++++++++++-------- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index 172b522..3888739 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -16,11 +16,13 @@ module Binary class ELF include Enumerable attr_reader :sections, :program_headers - attr_accessor :header + attr_accessor :header, :rel_texts, :got_plt_offsets def initialize @program_headers = [] @sections = [] @header = nil + @rel_texts = [] + @got_plt_offsets = {} end def each(&block) = @sections.each(&block) def [](idx) = @sections[idx] diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index cc2c16d..714190e 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -370,33 +370,13 @@ def build addralign: 8, entsize: rel_entsize(rel_section) ) - end - - rel_sections.each do |rel| - target = sections[rel.header.info] - bytes = target.body.dup - symtab_body = symtab_section.body - 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 + start_len - 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] / 8) - else - sym.shndx >= 0xff00 ? sym.value : sections[sym.shndx].then { |st| st.header.addr + sym.value } - end - value = sym_addr + sym_addend - (target_addr + sym_offset) - bytes[sym_offset, 4] = [value].pack("l<") - end - target.body = bytes + elf.rel_texts << rel_section if rel_section.section_name.to_s.start_with?(".rela.text", ".rel.text") end sections = sections.reject { |section| RELOCATION_SECTION_NAMES.any? { |name| name === section.section_name.to_s } } if @executable sections.each { |section| elf.sections << section } + elf.got_plt_offsets = got_plt_offsets elf end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 63fa0a4..09a68d8 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -11,6 +11,7 @@ class Writer def self.write!(elf_obj:, output:, entry: nil, debug: false, executable: true, shared: false) new(elf_obj:, output:, entry:, debug:, shared:, executable:).write end + def initialize(elf_obj:, output:, 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 @program_headers = [] @@ -44,18 +45,16 @@ def write lph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:) f.write(@elf_obj.header.build) phs.each { |ph| f.write(ph.build) } - if pph - pph.set!( - type: 6, - offset: phoffset, - vaddr: phoffset, - paddr: phoffset, - filesz: phsize * phnum, - memsz: phsize * phnum, - flags: program_header_flags(:R), - align: 8 - ) - end + 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) @@ -102,6 +101,37 @@ def write end private + def rewrite_text_section(file:) + cur = file.pos + got_plt_offsets = @elf_obj.got_plt_offsets + rel_text_sections.each do |rel| + target = @elf_obj.sections[rel.header.info] + bytes = target.body.dup + symtab_body = symtab_section.body + vaddr = target.header.addr + file.seek(target.header.offset) + 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] / 8) + elsif sym.shndx >= 0xff00 + sym.value + else + @elf_obj.sections[sym.shndx - 1].header.addr + sym.value + end + value = sym_addr + sym_addend - (target_addr + sym_offset) + bytes[sym_offset, 4] = [value].pack("l<") + file.write(bytes) + end + 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) @@ -315,6 +345,7 @@ def write_shared_dynamic_sections(file:) file.write(rest.flatten.pack("C*")) file.seek(current_offset) end + rewrite_text_section(file:) unless rel_text_sections.empty? end def ref_index(section_name) @@ -419,6 +450,7 @@ def dynamic_program_header = @dynamic_program_header ||= program_headers.find { 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 ||= @elf_obj.rel_texts 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 } From ead9a8830509beb80635fdf804a8b2c6d860dae9 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sun, 12 Apr 2026 10:46:47 +0900 Subject: [PATCH 09/10] support rodata and rewrite text relocations in writer --- lib/caotral/linker/builder.rb | 17 +++++++++++++++++ lib/caotral/linker/writer.rb | 19 +++++++++++++++---- test/caotral/linker/writer_test.rb | 2 +- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 714190e..308954b 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -49,6 +49,12 @@ def build section_name: ".text", header: Caotral::Binary::ELF::SectionHeader.new ) + rodata_section = Caotral::Binary::ELF::Section.new( + body: String.new, + section_name: ".rodata", + header: Caotral::Binary::ELF::SectionHeader.new + .set!(type: SHT[:progbits], flags: SHF[:ALLOC], addralign: 16) + ) data_section = Caotral::Binary::ELF::Section.new( body: String.new, section_name: ".data", @@ -110,9 +116,11 @@ def build elf.header = elf_obj.header.dup strtab_names = [] text_offsets = {} + rodata_offsets = {} data_offsets = {} got_plt_offsets = {} text_offset = 0 + rodata_offset = 0 data_offset = 0 got_plt_offset = 24 sym_by_elf = Hash.new { |h, k| h[k] = [] } @@ -128,6 +136,12 @@ def build size = text.body.bytesize text_offset += size end + rodata = elf_obj.find_by_name(".rodata") + unless rodata.nil? + rodata_section.body << rodata.body + rodata_offsets[elf_obj.object_id] = rodata_offset + rodata_offset += rodata.body.bytesize + end data = elf_obj.find_by_name(".data") unless data.nil? data_section.body << data.body @@ -142,6 +156,7 @@ def build base_index = symtab_section.body.size text_index = elf_obj.sections.index(text) unless text.nil? data_index = elf_obj.sections.index(data) unless data.nil? + rodata_index = elf_obj.sections.index(rodata) unless rodata.nil? symtab.body.each_with_index do |st, index| sym = Caotral::Binary::ELF::Section::Symtab.new @@ -149,6 +164,7 @@ def build sym_by_elf[elf_obj] << sym value += text_offsets.fetch(elf_obj.object_id, 0) if shndx == text_index value += data_offsets.fetch(elf_obj.object_id, 0) if shndx == data_index + value += rodata_offsets.fetch(elf_obj.object_id, 0) if shndx == rodata_index sym.set!(name:, info:, other:, shndx:, value:, size:) sym.name_string = strtab.body.lookup(name) unless strtab.nil? symtab_section.body << sym @@ -246,6 +262,7 @@ def build sections << text_section strtab_section.header.set!(type: 3, flags: 0, addralign: 1, entsize: 0) + sections << rodata_section sections << data_section if dynamic? sections << plt_section diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 09a68d8..a7f4f42 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -118,16 +118,16 @@ def rewrite_text_section(file:) 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] / 8) + 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 - 1].header.addr + sym.value + @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<") - file.write(bytes) end + file.write(bytes) end file.seek(cur) end @@ -204,7 +204,7 @@ def patch_program_headers(file:) segment_start = text_section.header.offset segment_start = 0 if @pie - segment_end = [text_section, data_section,].concat(dynamic_sections).compact.map { |s| s.header.offset + s.header.size }.max + 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) @@ -231,6 +231,16 @@ def write_elf_sections(file:) 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 + if data_section data_offset = file.pos file.write(data_section.build) @@ -460,6 +470,7 @@ def dynamic_section = @dynamic_section ||= @write_sections.find { |s| ".dynamic" def interp_section = @interp_section ||= @write_sections.find { |s| ".interp" === s.section_name.to_s } def rela_dyn_section = @rela_dyn_section ||= @write_sections.find { |s| ".rela.dyn" === s.section_name.to_s } def data_section = @data_section ||= @write_sections.find { |s| ".data" === s.section_name.to_s } + def rodata_section = @rodata_section ||= @write_sections.find { |s| ".rodata" === s.section_name.to_s } def hash_section = @hash_section ||= @write_sections.find { |s| ".hash" === s.section_name.to_s } 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 } diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index 31bb155..ac05b91 100644 --- a/test/caotral/linker/writer_test.rb +++ b/test/caotral/linker/writer_test.rb @@ -17,7 +17,7 @@ def test_write written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "write.o", debug: false) read_written_elf = Caotral::Binary::ELF::Reader.read!(input: written_output, debug: false) assert_equal @elf_obj.header.shoffset, read_written_elf.header.shoffset - assert_equal 6, read_written_elf.sections.size + assert_equal 7, read_written_elf.sections.size assert_equal 0x401000, read_written_elf.header.entry end From 75d81c16ca03ef6587a093544fcfdc23bc4d9ec0 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 14 Apr 2026 23:36:16 +0900 Subject: [PATCH 10/10] rewrite text relocations by target section in writer --- lib/caotral/binary/elf.rb | 4 +-- lib/caotral/linker.rb | 3 +- lib/caotral/linker/builder.rb | 10 ++++-- lib/caotral/linker/writer.rb | 57 ++++++++++++++++++++--------------- 4 files changed, 42 insertions(+), 32 deletions(-) diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index 3888739..172b522 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -16,13 +16,11 @@ module Binary class ELF include Enumerable attr_reader :sections, :program_headers - attr_accessor :header, :rel_texts, :got_plt_offsets + attr_accessor :header def initialize @program_headers = [] @sections = [] @header = nil - @rel_texts = [] - @got_plt_offsets = {} end def each(&block) = @sections.each(&block) def [](idx) = @sections[idx] diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 973a14a..c927302 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -55,7 +55,8 @@ def to_elf(inputs: @inputs, output: @output, debug: @debug, shared: @shared, exe builder = Caotral::Linker::Builder.new(elf_objs:, debug:, shared:, executable:, pie:, needed:) builder.resolve_symbols elf_obj = builder.build - Caotral::Linker::Writer.new(elf_obj:, output:, debug:, shared:, executable:, pie:).write + metadata = builder.linker_metadata + Caotral::Linker::Writer.new(elf_obj:, output:, metadata:, debug:, shared:, executable:, pie:).write File.chmod(0755, output) if executable output end diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 308954b..cb1c916 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -26,13 +26,14 @@ class Builder DYNAMIC_TAGS[:JMPREL], ].freeze - attr_reader :symbols + attr_reader :symbols, :linker_metadata def initialize(elf_objs:, executable: true, debug: false, shared: false, pie: false, needed: []) @elf_objs = elf_objs @executable, @debug, @shared, @pie, @needed = executable, debug, shared, pie, needed @symbols = { locals: Set.new, globals: Set.new, weaks: Set.new, }.freeze _mode! + @linker_metadata = {} end def build @@ -113,6 +114,7 @@ def build start_len = start_bytes.length sections = [] rel_sections = [] + rel_texts = [] elf.header = elf_obj.header.dup strtab_names = [] text_offsets = {} @@ -227,6 +229,7 @@ def build raise Caotral::Binary::ELF::Error, "unsupported relocation type: #{rel.type_name}" end offset = rel.offset + text_offsets.fetch(elf_obj.object_id, 0) + offset += start_len if section.section_name.to_s.start_with?(".rela.text", ".rel.text") addend = rel.addend? ? rel.addend : nil new_rel = Caotral::Binary::ELF::Section::Rel.new(addend: rel.addend?) info = (sym << 32) | rel.type @@ -387,13 +390,14 @@ def build addralign: 8, entsize: rel_entsize(rel_section) ) - elf.rel_texts << rel_section if rel_section.section_name.to_s.start_with?(".rela.text", ".rel.text") + rel_texts << rel_section if rel_section.section_name.to_s.start_with?(".rela.text", ".rel.text") end sections = sections.reject { |section| RELOCATION_SECTION_NAMES.any? { |name| name === section.section_name.to_s } } if @executable sections.each { |section| elf.sections << section } - elf.got_plt_offsets = got_plt_offsets + @linker_metadata[:got_plt_offsets] = got_plt_offsets + @linker_metadata[:pending_text_relocations] = rel_texts elf end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index a7f4f42..c37dc07 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -7,15 +7,19 @@ 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 - def self.write!(elf_obj:, output:, entry: nil, debug: false, executable: true, shared: false) - new(elf_obj:, output:, entry:, debug:, shared:, executable:).write + attr_reader :elf_obj, :output, :entry, :debug, :got_plt_offsets, :pending_text_relocations + 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 end - def initialize(elf_obj:, output:, entry: nil, debug: false, executable: true, shared: false, pie: false) + 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 @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 @@ -81,6 +85,8 @@ def write rel.header.set!(offset: rel_offset, size: rel_size, entsize:) 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) @@ -103,29 +109,31 @@ def write private def rewrite_text_section(file:) cur = file.pos - got_plt_offsets = @elf_obj.got_plt_offsets - rel_text_sections.each do |rel| - target = @elf_obj.sections[rel.header.info] + 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) - 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<") + 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 @@ -355,7 +363,6 @@ def write_shared_dynamic_sections(file:) file.write(rest.flatten.pack("C*")) file.seek(current_offset) end - rewrite_text_section(file:) unless rel_text_sections.empty? end def ref_index(section_name) @@ -460,7 +467,7 @@ def dynamic_program_header = @dynamic_program_header ||= program_headers.find { 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 ||= @elf_obj.rel_texts + 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 }