diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index 8a652b2..0836008 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -21,9 +21,10 @@ 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 { |section| section_names.any? { |name| name === section.section_name.to_s } } end end end 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/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.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 diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index ce74edf..01aad8b 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 = %w(.text .strtab .shstrtab).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 +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 = write_order_sections end def write f = File.open(@output, "wb") @@ -58,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) @@ -70,17 +71,28 @@ 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" } - 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) - 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 + elsif [:rel, :rela].include?(header.type) + link = symtabndx + info = ref_index(section) + end + header.set!(name:, info:, link:, entsize:) f.write(section.header.build) end @@ -90,6 +102,24 @@ def write ensure f.close if f 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 + 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 end end 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 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