From d823f1c1cdd96a0f81f78a4376aa46eb27a4afb5 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sun, 11 Jan 2026 11:04:23 +0900 Subject: [PATCH 1/6] adjust symtab's section header --- lib/caotral/binary/elf/section_header.rb | 1 + lib/caotral/linker/writer.rb | 13 +++++++++++-- test/caotral/linker/writer_test.rb | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/caotral/binary/elf/section_header.rb b/lib/caotral/binary/elf/section_header.rb index 3532d12..77f6aa4 100644 --- a/lib/caotral/binary/elf/section_header.rb +++ b/lib/caotral/binary/elf/section_header.rb @@ -46,6 +46,7 @@ def size = @size.pack("C*").unpack1("Q<") def type = SHT_BY_VALUE[@type.pack("C*").unpack1("L<")] def info = @info.pack("C*").unpack1("L<") def addr = @addr.pack("C*").unpack1("Q<") + def link = @link.pack("C*").unpack1("L<") private def bytes = [@name, @type, @flags, @addr, @offset, @size, @link, @info, @addralign, @entsize] end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index ce74edf..1edcdf0 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -4,7 +4,7 @@ module Caotral class Linker class Writer include Caotral::Binary::ELF::Utils - ALLOW_SECTIONS = %w(.text .strtab .shstrtab).freeze + ALLOW_SECTIONS = %w(.text .strtab .shstrtab .symtab).freeze R_X86_64_PC32 = 2 R_X86_64_PLT32 = 4 ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze @@ -73,14 +73,23 @@ def write write_sections = @elf_obj.sections.select { ALLOW_SECTIONS.include?(it.section_name) || it.section_name.nil? } shoffset = f.pos shstrndx = write_sections.index { it.section_name == ".shstrtab" } + strtabndx = write_sections.index { it.section_name == ".strtab" } + shnum = write_sections.size @elf_obj.header.set!(shoffset:, shnum:, shstrndx:) names = @elf_obj.find_by_name(".shstrtab").body write_sections.each do |section| + header = section.header lookup_name = section.section_name name_offset = names.offset_of(lookup_name) - section.header.set!(name: name_offset) if name_offset + name, info, link, entsize = (name_offset.nil? ? 0 : name_offset), header.info, header.link, 0 + if header.type == :symtab + info = section.body.size + link = strtabndx + entsize = header.entsize.nonzero? || 24 + end + header.set!(name:, info:, link:, entsize:) f.write(section.header.build) end diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index 3b554fb..9c588dc 100644 --- a/test/caotral/linker/writer_test.rb +++ b/test/caotral/linker/writer_test.rb @@ -16,7 +16,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 4, read_written_elf.sections.size + assert_equal 5, read_written_elf.sections.size assert_equal 0x401000, read_written_elf.header.entry end From a145bf1f507e114eae035ea3de6ac45a9e526e8f Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sun, 11 Jan 2026 12:27:16 +0900 Subject: [PATCH 2/6] support rel/rela section in linker --- lib/caotral/binary/elf.rb | 9 +++++++++ lib/caotral/linker/writer.rb | 25 ++++++++++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index 8a652b2..1145b54 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -24,6 +24,15 @@ def [](idx) = @sections[idx] def find_by_name(section_name) = @sections.find { section_name == it.section_name } def select_by_name(section_name) = @sections.select { section_name == it.section_name } def index(section_name) = @sections.index { section_name == it.section_name } + def select_by_names(section_names) + @sections.select do |section| + next true if section.section_name.nil? + section_names.any? do |name| + re = Regexp === name + re ? section.section_name.match(name) : section.section_name == name + end + end + end end end end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 1edcdf0..9eefea2 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -4,7 +4,7 @@ module Caotral class Linker class Writer include Caotral::Binary::ELF::Utils - ALLOW_SECTIONS = %w(.text .strtab .shstrtab .symtab).freeze + ALLOW_SECTIONS = [".text", ".strtab", ".shstrtab", ".symtab", /\.rela\./, /\.rel\./].freeze R_X86_64_PC32 = 2 R_X86_64_PLT32 = 4 ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze @@ -15,6 +15,7 @@ def self.write!(elf_obj:, output:, entry: nil, debug: false) end def initialize(elf_obj:, output:, entry: nil, debug: false) @elf_obj, @output, @entry, @debug = elf_obj, output, entry, debug + @write_sections = @elf_obj.select_by_names(ALLOW_SECTIONS) end def write f = File.open(@output, "wb") @@ -70,16 +71,15 @@ def write shstrtab_offset = f.pos f.write(shstrtab.body.names) shstrtab.header.set!(offset: shstrtab_offset, size: shstrtab.body.names.bytesize) - write_sections = @elf_obj.sections.select { ALLOW_SECTIONS.include?(it.section_name) || it.section_name.nil? } shoffset = f.pos - shstrndx = write_sections.index { it.section_name == ".shstrtab" } - strtabndx = write_sections.index { it.section_name == ".strtab" } - - shnum = write_sections.size + shstrndx = write_section_index(".shstrtab") + strtabndx = write_section_index(".strtab") + symtabndx = write_section_index(".symtab") + shnum = @write_sections.size @elf_obj.header.set!(shoffset:, shnum:, shstrndx:) names = @elf_obj.find_by_name(".shstrtab").body - write_sections.each do |section| + @write_sections.each do |section| header = section.header lookup_name = section.section_name name_offset = names.offset_of(lookup_name) @@ -88,6 +88,9 @@ def write info = section.body.size link = strtabndx entsize = header.entsize.nonzero? || 24 + elsif [:rel, :rela].include?(header.type) + link = symtabndx + info = ref_index(section) end header.set!(name:, info:, link:, entsize:) f.write(section.header.build) @@ -99,6 +102,14 @@ def write ensure f.close if f end + + private + def write_section_index(section_name) = @write_sections.index { it.section_name == section_name } + def ref_index(section) + section_name = section.section_name + ref = @elf_obj.select_by_names(section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }).first + write_section_index(ref.section_name) + end end end end From 9c7236d7b9624630650bc4df48b6196f7fddbabc Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Mon, 12 Jan 2026 15:27:58 +0900 Subject: [PATCH 3/6] set writer order for writer sections --- lib/caotral/linker/writer.rb | 14 ++++++++++++-- test/caotral/linker/writer_test.rb | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 9eefea2..0ff76fe 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 - ALLOW_SECTIONS = [".text", ".strtab", ".shstrtab", ".symtab", /\.rela\./, /\.rel\./].freeze R_X86_64_PC32 = 2 R_X86_64_PLT32 = 4 ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze @@ -15,7 +14,7 @@ def self.write!(elf_obj:, output:, entry: nil, debug: false) end def initialize(elf_obj:, output:, entry: nil, debug: false) @elf_obj, @output, @entry, @debug = elf_obj, output, entry, debug - @write_sections = @elf_obj.select_by_names(ALLOW_SECTIONS) + @write_sections = write_order_sections end def write f = File.open(@output, "wb") @@ -59,6 +58,7 @@ def write end target.body = bytes end + header.set!(entry: @entry || base_addr + text_offset) ph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:) text_section.header.set!(size: text_section.body.bytesize, addr: vaddr, offset: text_offset) @@ -104,6 +104,16 @@ def write end private + def write_order_sections + write_order = [] + write_order << @elf_obj.sections.find { |s| s.section_name.nil? } + write_order << @elf_obj.find_by_name(".text") + write_order << @elf_obj.find_by_name(".symtab") + write_order << @elf_obj.find_by_name(".strtab") + write_order.concat(@elf_obj.select_by_names([/\.rel\./, /\.rela\./])) + write_order << @elf_obj.find_by_name(".shstrtab") + write_order.compact + end def write_section_index(section_name) = @write_sections.index { it.section_name == section_name } def ref_index(section) section_name = section.section_name diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index 9c588dc..dfba324 100644 --- a/test/caotral/linker/writer_test.rb +++ b/test/caotral/linker/writer_test.rb @@ -16,7 +16,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 5, read_written_elf.sections.size + assert_equal 6, read_written_elf.sections.size assert_equal 0x401000, read_written_elf.header.entry end From f1fd105b0346fca13f0bec364f15700f98ca0986 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Mon, 12 Jan 2026 23:43:26 +0900 Subject: [PATCH 4/6] reject duplicate global symbols --- lib/caotral/binary/elf.rb | 6 ++--- lib/caotral/binary/elf/section/symtab.rb | 1 + lib/caotral/linker.rb | 3 +++ lib/caotral/linker/builder.rb | 30 ++++++++++++++++++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 lib/caotral/linker/builder.rb diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index 1145b54..6486f0b 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -21,9 +21,9 @@ def initialize end def each(&block) = @sections.each(&block) def [](idx) = @sections[idx] - def find_by_name(section_name) = @sections.find { section_name == it.section_name } - def select_by_name(section_name) = @sections.select { section_name == it.section_name } - def index(section_name) = @sections.index { section_name == it.section_name } + def find_by_name(section_name) = @sections.find { |s| section_name == s.section_name } + def select_by_name(section_name) = @sections.select { |s| section_name == s.section_name } + def index(section_name) = @sections.index { |s| section_name == s.section_name } def select_by_names(section_names) @sections.select do |section| next true if section.section_name.nil? diff --git a/lib/caotral/binary/elf/section/symtab.rb b/lib/caotral/binary/elf/section/symtab.rb index f3882ce..06c7b0f 100644 --- a/lib/caotral/binary/elf/section/symtab.rb +++ b/lib/caotral/binary/elf/section/symtab.rb @@ -31,6 +31,7 @@ def set!(name: nil, info: nil, other: nil, shndx: nil, value: nil, size: nil) def name_offset = @name.pack("C*").unpack1("L<") def value = @value.pack("C*").unpack1("Q<") + def info = @info.pack("C*").unpack1("C") private def bytes = [@name, @info, @other, @shndx, @value, @size] end diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 8abe92d..ea5764d 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true require_relative "binary/elf/reader" +require_relative "linker/builder" require_relative "linker/writer" module Caotral @@ -48,6 +49,8 @@ def gcc_libpath = @gcc_libpath ||= File.dirname(Dir.glob("/usr/lib/gcc/x86_64-*/ def to_elf(input: @input, output: @output, debug: @debug) elf_obj = Caotral::Binary::ELF::Reader.new(input:, debug:).read + builder = Caotral::Linker::Builder.new(elf_obj:) + builder.resolve_symbols Caotral::Linker::Writer.new(elf_obj:, output:, debug:).write end end diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb new file mode 100644 index 0000000..928353f --- /dev/null +++ b/lib/caotral/linker/builder.rb @@ -0,0 +1,30 @@ +require "set" +require "caotral/binary/elf" + +module Caotral + class Linker + class Builder + SYMTAB_BIND = { locals: 0, globals: 1, weaks: 2, }.freeze + BIND_BY_VALUE = SYMTAB_BIND.invert.freeze + attr_reader :symbols + + def initialize(elf_obj:) + @elf_obj = elf_obj + @symbols = { locals: Set.new, globals: Set.new, weaks: Set.new } + end + def resolve_symbols + @elf_obj.find_by_name(".symtab").body.each do |symtab| + name = symtab.name_string + next if name.empty? + info = symtab.info + bind = BIND_BY_VALUE.fetch(info >> 4) + if bind == :globals && @symbols[bind].include?(name) + raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}" + end + @symbols[bind] << name + end + @symbols + end + end + end +end From 3a54b402629f496fe795fd09f46e7ad4c8d993e2 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 13 Jan 2026 09:59:39 +0900 Subject: [PATCH 5/6] fix search name condition --- lib/caotral/binary/elf.rb | 10 +--------- lib/caotral/linker/writer.rb | 2 +- test/caotral/linker/writer_test.rb | 2 +- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index 6486f0b..0836008 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -24,15 +24,7 @@ def [](idx) = @sections[idx] def find_by_name(section_name) = @sections.find { |s| section_name == s.section_name } def select_by_name(section_name) = @sections.select { |s| section_name == s.section_name } def index(section_name) = @sections.index { |s| section_name == s.section_name } - def select_by_names(section_names) - @sections.select do |section| - next true if section.section_name.nil? - section_names.any? do |name| - re = Regexp === name - re ? section.section_name.match(name) : section.section_name == name - end - end - end + def select_by_names(section_names) = @sections.select { |section| section_names.any? { |name| name === section.section_name.to_s } } end end end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 0ff76fe..01aad8b 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -117,7 +117,7 @@ def write_order_sections def write_section_index(section_name) = @write_sections.index { it.section_name == section_name } def ref_index(section) section_name = section.section_name - ref = @elf_obj.select_by_names(section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }).first + ref = @elf_obj.select_by_names(section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.map { "." + it }).first write_section_index(ref.section_name) end end diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index dfba324..9c588dc 100644 --- a/test/caotral/linker/writer_test.rb +++ b/test/caotral/linker/writer_test.rb @@ -16,7 +16,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 5, read_written_elf.sections.size assert_equal 0x401000, read_written_elf.header.entry end From c2de6af929e89acc175832d463d6149f994a71c7 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 13 Jan 2026 20:56:59 +0900 Subject: [PATCH 6/6] update sigs --- sig/caotral/binary/elf.rbs | 1 + sig/caotral/binary/elf/section/symtab.rbs | 1 + sig/caotral/linker/builder.rbs | 9 +++++++++ 3 files changed, 11 insertions(+) create mode 100644 sig/caotral/linker/builder.rbs diff --git a/sig/caotral/binary/elf.rbs b/sig/caotral/binary/elf.rbs index e7bb7a8..df74cdc 100644 --- a/sig/caotral/binary/elf.rbs +++ b/sig/caotral/binary/elf.rbs @@ -10,4 +10,5 @@ class Caotral::Binary::ELF def find_by_name: (String | Symbol) -> Caotral::Binary::ELF::Section? def select_by_name: (String | Symbol) -> Array[Caotral::Binary::ELF::Section] def index: (String | Symbol) -> Integer? + def select_by_names: (Array[String | Regexp]) -> Array[Caotral::Binary::ELF::Section] end diff --git a/sig/caotral/binary/elf/section/symtab.rbs b/sig/caotral/binary/elf/section/symtab.rbs index a69110d..bddd938 100644 --- a/sig/caotral/binary/elf/section/symtab.rbs +++ b/sig/caotral/binary/elf/section/symtab.rbs @@ -12,5 +12,6 @@ class Caotral::Binary::ELF::Section::Symtab ) -> self def name_offset: () -> Integer + def info: () -> Integer def value: () -> Integer end diff --git a/sig/caotral/linker/builder.rbs b/sig/caotral/linker/builder.rbs new file mode 100644 index 0000000..0160a38 --- /dev/null +++ b/sig/caotral/linker/builder.rbs @@ -0,0 +1,9 @@ +class Caotral::Linker::Builder + @elf_obj: Caotral::Binary::ELF + @symbols: Hash[Symbol, Set[String]] + + attr_reader symbols: Hash[Symbol, Set[String]] + + def initialize: (elf_obj: Caotral::Binary::ELF) -> void + def resolve_symbols: () -> Hash[Symbol, Set[String]] +end