From 5fa4a5a973fbc46f88f9ccb948b996bc922ebc85 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 18 Aug 2026 22:56:56 +0900 Subject: [PATCH 1/5] preserve input section alignment --- lib/caotral/linker/builder.rb | 44 +++++++++++++---- sample/assembler/aligned_sections.s | 25 ++++++++++ sample/assembler/alignment_prefix.s | 21 +++++++++ test/caotral/linker/builder_test.rb | 44 +++++++++++++++++ .../linker/integration/alignment_test.rb | 47 +++++++++++++++++++ 5 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 sample/assembler/aligned_sections.s create mode 100644 sample/assembler/alignment_prefix.s create mode 100644 test/caotral/linker/builder_test.rb create mode 100644 test/caotral/linker/integration/alignment_test.rb diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 8354ba2..7c5c7a8 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -5,6 +5,8 @@ module Caotral class Linker class Builder include Caotral::Binary::ELF::Utils + NULL_BIN = "\x00".b.freeze + NOP_BIN = "\x90".b.freeze REL_TYPES = Caotral::Binary::ELF::Section::Rel::TYPES DYNAMIC_TAGS = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES SYMTAB_BIND = { locals: 0, globals: 1, weaks: 2, }.freeze @@ -135,11 +137,14 @@ def build got_plt_offsets = {} text_offset = 0 rodata_offset = 0 + data_offset = 0 bss_offset = 0 + got_plt_offset = 24 bss_present = false + text_addralign = 1 + rodata_addralign = 1 + data_addralign = 1 bss_addralign = 1 - data_offset = 0 - got_plt_offset = 24 sym_by_elf = Hash.new { |h, k| h[k] = [] } dynstr, dynsym = build_shared_dynamic_sections if dynamic? @@ -148,22 +153,39 @@ def build @elf_objs.each do |elf_obj| text = elf_obj.find_by_name(".text") unless text.nil? + alignment = [text.header.addralign, 1].max + aligned_offset = align_up(start_len + text_offset, alignment) - start_len + padding_size = aligned_offset - text_offset + + text_section.body << (NOP_BIN * padding_size) + text_offsets[elf_obj.object_id] = aligned_offset text_section.body << text.body - text_offsets[elf_obj.object_id] = text_offset - size = text.body.bytesize - text_offset += size + text_offset = aligned_offset + text.build.bytesize + text_addralign = [text_addralign, alignment].max end rodata = elf_obj.find_by_name(".rodata") unless rodata.nil? + alignment = [rodata.header.addralign, 1].max + aligned_offset = align_up(rodata_offset, alignment) + padding_size = aligned_offset - rodata_offset + + rodata_section.body << (NULL_BIN * padding_size) + rodata_offsets[elf_obj.object_id] = aligned_offset rodata_section.body << rodata.body - rodata_offsets[elf_obj.object_id] = rodata_offset - rodata_offset += rodata.body.bytesize + rodata_offset = aligned_offset + rodata.build.bytesize + rodata_addralign = [rodata_addralign, alignment].max end data = elf_obj.find_by_name(".data") unless data.nil? + alignment = [data.header.addralign, 1].max + aligned_offset = align_up(data_offset, alignment) + padding_size = aligned_offset - data_offset + + data_section.body << (NULL_BIN * padding_size) + data_offsets[elf_obj.object_id] = aligned_offset data_section.body << data.body - data_offsets[elf_obj.object_id] = data_offset - data_offset += data.body.bytesize + data_offset = aligned_offset + data.build.bytesize + data_addralign = [data_addralign, alignment].max end bss = elf_obj.find_by_name(".bss") unless bss.nil? @@ -289,12 +311,14 @@ def build addr: vaddr, offset: exec_text_offset, size: text_section.body.bytesize, - addralign: 16 + addralign: text_addralign ) sections << text_section strtab_section.header.set!(type: 3, flags: 0, addralign: 1, entsize: 0) + rodata_section.header.set!(addralign: rodata_addralign) sections << rodata_section + data_section.header.set!(addralign: data_addralign) sections << data_section if dynamic? sections << plt_section diff --git a/sample/assembler/aligned_sections.s b/sample/assembler/aligned_sections.s new file mode 100644 index 0000000..b5719fb --- /dev/null +++ b/sample/assembler/aligned_sections.s @@ -0,0 +1,25 @@ +.text +.balign 32 +.globl aligned_function +.type aligned_function, @function +aligned_function: + mov aligned_rodata(%rip), %eax + add aligned_data(%rip), %eax + ret +.size aligned_function, .-aligned_function + +.section .rodata +.balign 32 +.globl aligned_rodata +.type aligned_rodata, @object +aligned_rodata: + .long 40 +.size aligned_rodata, .-aligned_rodata + +.data +.balign 32 +.globl aligned_data +.type aligned_data, @object +aligned_data: + .long 2 +.size aligned_data, .-aligned_data diff --git a/sample/assembler/alignment_prefix.s b/sample/assembler/alignment_prefix.s new file mode 100644 index 0000000..004fa9c --- /dev/null +++ b/sample/assembler/alignment_prefix.s @@ -0,0 +1,21 @@ +.text +.globl main +.type main, @function +main: + call aligned_function + ret +.size main, .-main + +.section .rodata +.globl prefix_rodata +.type prefix_rodata, @object +prefix_rodata: + .byte 0x11 +.size prefix_rodata, .-prefix_rodata + +.data +.globl prefix_data +.type prefix_data, @object +prefix_data: + .byte 0x22 +.size prefix_data, .-prefix_data diff --git a/test/caotral/linker/builder_test.rb b/test/caotral/linker/builder_test.rb new file mode 100644 index 0000000..7d51f2d --- /dev/null +++ b/test/caotral/linker/builder_test.rb @@ -0,0 +1,44 @@ +require_relative "../../test_suite" + +class Caotral::Linker::BuilderTest < Test::Unit::TestCase + attr_reader :inputs, :output, :elf_objs + def setup + @inputs = [] + @elf_objs = [] + end + def teardown = inputs.each { |i| File.delete(i) if File.exist?(i) } + + def test_build_alignment + [ + ["sample/assembler/alignment_prefix.s", "alignment_prefix.o"], + ["sample/assembler/aligned_sections.s", "aligned_sections.o"], + ].each do |(path, input)| + IO.popen(["as", "-o", input, path]).close + @elf_objs << Caotral::Binary::ELF::Reader.read!(input:) + @inputs << input + end + + builder = Caotral::Linker::Builder.new(elf_objs:) + elf = builder.build + text = elf.find_by_name(".text") + rodata = elf.find_by_name(".rodata") + data = elf.find_by_name(".data") + + assert_equal(32, text.header.addralign) + assert_equal(32, rodata.header.addralign) + assert_equal(32, data.header.addralign) + + assert_equal([0x90] * 9, text.body.byteslice(23, 9).bytes) + assert_equal([0x00] * 31, rodata.body.byteslice(1, 31).bytes) + assert_equal([0x00] * 31, data.body.byteslice(1, 31).bytes) + + symtab = elf.find_by_name(".symtab").body + aligned_function = symtab.find { |sym| sym.name_string == "aligned_function" && sym.shndx != 0 } + aligned_rodata = symtab.find { |sym| sym.name_string == "aligned_rodata" && sym.shndx != 0 } + aligned_data = symtab.find { |sym| sym.name_string == "aligned_data" && sym.shndx != 0 } + + assert_equal(32, aligned_function.value) + assert_equal(32, aligned_rodata.value) + assert_equal(32, aligned_data.value) + end +end diff --git a/test/caotral/linker/integration/alignment_test.rb b/test/caotral/linker/integration/alignment_test.rb new file mode 100644 index 0000000..a0f13ba --- /dev/null +++ b/test/caotral/linker/integration/alignment_test.rb @@ -0,0 +1,47 @@ +require_relative "../../../test_suite" + +class Caotral::Linker::AlignmentTest < Test::Unit::TestCase + include TestProcessHelper + + def setup + @inputs = ["alignment_prefix_integration.o", "aligned_sections_integration.o"] + @output = "alignment_integration" + end + + def teardown + [*@inputs, @output].each do |path| + File.delete(path) if File.exist?(path) + end + end + + def test_aligned_contributions_execute + fixtures = [ + "sample/assembler/alignment_prefix.s", + "sample/assembler/aligned_sections.s", + ] + @inputs.zip(fixtures).each do |input, fixture| + IO.popen(["as", "-o", input, fixture]).close + end + + Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self") + + elf = Caotral::Binary::ELF::Reader.read!(input: @output) + symtab = elf.find_by_name(".symtab").body + { + ".text" => "aligned_function", + ".rodata" => "aligned_rodata", + ".data" => "aligned_data", + }.each do |section_name, symbol_name| + section = elf.find_by_name(section_name) + symbol = symtab.find { |sym| sym.name_string == symbol_name && sym.shndx != 0 } + + assert_equal(32, section.header.addralign) + assert_equal(0, (section.header.addr + symbol.value) % 32) + end + + _stdout, _stderr, status = Open3.capture3("./#{@output}") + exit_code, handle_code = check_process(status.to_i) + assert_equal(42, exit_code) + assert_equal(0, handle_code) + end +end From 223dce44d0ab472a28cba367e5240f9fd3004021 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 19 Aug 2026 01:47:07 +0900 Subject: [PATCH 2/5] add defined symbol lookup --- lib/caotral/binary/elf.rb | 2 ++ lib/caotral/binary/elf/reader.rb | 6 +++- lib/caotral/binary/elf/symtab_methods.rb | 9 ++++++ lib/caotral/linker.rb | 3 +- lib/caotral/linker/builder.rb | 8 ++++-- test/caotral/linker/builder_test.rb | 28 ++++++++++++++++--- .../linker/integration/alignment_test.rb | 4 +-- test/caotral/linker/integration/bss_test.rb | 6 ++-- 8 files changed, 53 insertions(+), 13 deletions(-) create mode 100644 lib/caotral/binary/elf/symtab_methods.rb diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index 172b522..34acf7f 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -9,6 +9,8 @@ require_relative "elf/section/strtab" require_relative "elf/section/symtab" require_relative "elf/section_header" +require_relative "elf/symtab_methods" + require_relative "elf/reader" module Caotral diff --git a/lib/caotral/binary/elf/reader.rb b/lib/caotral/binary/elf/reader.rb index ed1f8a9..f78337b 100644 --- a/lib/caotral/binary/elf/reader.rb +++ b/lib/caotral/binary/elf/reader.rb @@ -6,6 +6,7 @@ class ELF class Reader attr_reader :context def self.read!(input:, debug: false, linker_options: []) = new(input:, debug:, linker_options:).read + SymbolTable = Caotral::Binary::ELF::SymbolTable def initialize(input:, debug: false, linker_options: []) @input = decision(input) @@ -99,7 +100,9 @@ def read size = sym_bin[16, 8].unpack1("Q<") 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:) + symtab = Caotral::Binary::ELF::Section::Symtab.new + symtab.set!(name:, info:, other:, shndx:, value:, size:, name_string:) + symtab end when :rel, :rela rela = type == :rela @@ -126,6 +129,7 @@ def read when :progbits body_bin end + section.extend(SymbolTable) if [:symtab, :dynsym].include?(type) end strtab = @context.find_by_name(".strtab") diff --git a/lib/caotral/binary/elf/symtab_methods.rb b/lib/caotral/binary/elf/symtab_methods.rb new file mode 100644 index 0000000..8c3357e --- /dev/null +++ b/lib/caotral/binary/elf/symtab_methods.rb @@ -0,0 +1,9 @@ +module Caotral + module Binary + class ELF + module SymbolTable + def find_defined_symbol(symbol_name) = body.find { |sym| sym.name_string == symbol_name && sym.shndx != 0 } + end + end + end +end diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 60c4c08..3368711 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 "binary/elf" require_relative "linker/builder" require_relative "linker/layout" require_relative "linker/finalizer" diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 7c5c7a8..bae0914 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -17,6 +17,7 @@ class Builder SHT = Caotral::Binary::ELF::SectionHeader::SHT SHF = Caotral::Binary::ELF::SectionHeader::SHF SectionDynamic = Caotral::Binary::ELF::Section::Dynamic + SymbolTableMethods = Caotral::Binary::ELF::SymbolTable UNSUPPORTED_REL_TYPES = [ REL_TYPES[:AMD64_GOTPCREL], REL_TYPES[:AMD64_GOTPCRELX], @@ -101,6 +102,7 @@ def build section_name: ".symtab", header: Caotral::Binary::ELF::SectionHeader.new ) + symtab_section.extend(SymbolTableMethods) shstrtab_section = Caotral::Binary::ELF::Section.new( body: Caotral::Binary::ELF::Section::Strtab.new("\0".b), section_name: ".shstrtab", @@ -295,8 +297,8 @@ def build strtab_section.body.names = strtab_names.to_a.sort.join("\0") + "\0" sections << null_section - _start = symtab_section.body.find { |sym| sym.name_string == "_start" } - entry_sym = _start || symtab_section.body.find { |sym| sym.name_string == "main" } + _start = symtab_section.find_defined_symbol("_start") + entry_sym = _start || symtab_section.find_defined_symbol("main") raise Caotral::Binary::ELF::Error, "main or _start function not found" if @executable && entry_sym.nil? if _start.nil? @@ -521,6 +523,8 @@ def build_shared_dynamic_sections header: Caotral::Binary::ELF::SectionHeader.new ) + dynsym_section.extend(SymbolTableMethods) + dynsym_section.header.set!(info: 1, type: SHT[:dynsym], flags: SHF[:ALLOC], addralign: 8, entsize: 24) [dynstr_section, dynsym_section,] diff --git a/test/caotral/linker/builder_test.rb b/test/caotral/linker/builder_test.rb index 7d51f2d..d9f488b 100644 --- a/test/caotral/linker/builder_test.rb +++ b/test/caotral/linker/builder_test.rb @@ -32,13 +32,33 @@ def test_build_alignment assert_equal([0x00] * 31, rodata.body.byteslice(1, 31).bytes) assert_equal([0x00] * 31, data.body.byteslice(1, 31).bytes) - symtab = elf.find_by_name(".symtab").body - aligned_function = symtab.find { |sym| sym.name_string == "aligned_function" && sym.shndx != 0 } - aligned_rodata = symtab.find { |sym| sym.name_string == "aligned_rodata" && sym.shndx != 0 } - aligned_data = symtab.find { |sym| sym.name_string == "aligned_data" && sym.shndx != 0 } + symtab = elf.find_by_name(".symtab") + aligned_function = symtab.find_defined_symbol("aligned_function") + aligned_rodata = symtab.find_defined_symbol("aligned_rodata") + aligned_data = symtab.find_defined_symbol("aligned_data") assert_equal(32, aligned_function.value) assert_equal(32, aligned_rodata.value) assert_equal(32, aligned_data.value) end + + def test_align_first_text_contribution_after_synthetic_start + [ + ["sample/assembler/aligned_sections.s", "aligned_sections_first.o"], + ["sample/assembler/alignment_prefix.s", "alignment_prefix_second.o"], + ].each do |(path, input)| + IO.popen(["as", "-o", input, path]).close + @elf_objs << Caotral::Binary::ELF::Reader.read!(input:) + @inputs << input + end + + builder = Caotral::Linker::Builder.new(elf_objs:) + elf = builder.build + text = elf.find_by_name(".text") + symtab = elf.find_by_name(".symtab") + aligned_function = symtab.find_defined_symbol("aligned_function") + + assert_equal([0x90] * 15, text.body.byteslice(17, 15).bytes) + assert_equal(32, aligned_function.value) + end end diff --git a/test/caotral/linker/integration/alignment_test.rb b/test/caotral/linker/integration/alignment_test.rb index a0f13ba..9dbca21 100644 --- a/test/caotral/linker/integration/alignment_test.rb +++ b/test/caotral/linker/integration/alignment_test.rb @@ -26,14 +26,14 @@ def test_aligned_contributions_execute Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self") elf = Caotral::Binary::ELF::Reader.read!(input: @output) - symtab = elf.find_by_name(".symtab").body + symtab = elf.find_by_name(".symtab") { ".text" => "aligned_function", ".rodata" => "aligned_rodata", ".data" => "aligned_data", }.each do |section_name, symbol_name| section = elf.find_by_name(section_name) - symbol = symtab.find { |sym| sym.name_string == symbol_name && sym.shndx != 0 } + symbol = symtab.find_defined_symbol(symbol_name) assert_equal(32, section.header.addralign) assert_equal(0, (section.header.addr + symbol.value) % 32) diff --git a/test/caotral/linker/integration/bss_test.rb b/test/caotral/linker/integration/bss_test.rb index e47003d..df81fcd 100644 --- a/test/caotral/linker/integration/bss_test.rb +++ b/test/caotral/linker/integration/bss_test.rb @@ -63,7 +63,7 @@ def test_initialize_zero_pie_bss reader = Caotral::Binary::ELF::Reader.new(input: output, debug: false) elf = reader.read bss = elf.find_by_name(".bss") - dynamic_symbol = elf.find_by_name(".dynsym").body.find { |sym| sym.name_string == "value" } + dynamic_symbol = elf.find_by_name(".dynsym").find_defined_symbol("value") assert_equal(elf.index(".bss"), dynamic_symbol.shndx) assert_equal(:nobits, bss.header.type) @@ -92,7 +92,7 @@ def test_multiple_bss reader = Caotral::Binary::ELF::Reader.new(input: output, debug: false) elf = reader.read bss = elf.find_by_name(".bss") - aligned_symbol = elf.find_by_name(".symtab").body.find { |sym| sym.name_string == "aligned_value" } + aligned_symbol = elf.find_by_name(".symtab").find_defined_symbol("aligned_value") assert_equal(elf.index(".bss"), aligned_symbol.shndx) assert_equal(32, bss.header.size) @@ -120,7 +120,7 @@ def test_zero_size_bss reader = Caotral::Binary::ELF::Reader.new(input: output, debug: false) elf = reader.read bss = elf.find_by_name(".bss") - symbol = elf.find_by_name(".symtab").body.find { |sym| sym.name_string == "value" } + symbol = elf.find_by_name(".symtab").find_defined_symbol("value") assert_not_nil(bss) assert_equal(elf.index(".bss"), symbol.shndx) From dba0061a9e3bf341032998c8f6949af42386530f Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 19 Aug 2026 12:56:36 +0900 Subject: [PATCH 3/5] align data relocation offsets --- lib/caotral/linker/builder.rb | 9 +++++++-- sample/assembler/aligned_data_relocation.s | 7 +++++++ test/caotral/linker/builder_test.rb | 20 ++++++++++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 sample/assembler/aligned_data_relocation.s diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index bae0914..e856646 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -281,8 +281,13 @@ def build elsif UNSUPPORTED_REL_TYPES.include?(rel.type) 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") + + offset = case section.section_name.to_s + when ".rela.text", ".rel.text" + rel.offset + start_len + text_offsets.fetch(elf_obj.object_id, 0) + when ".rela.data", ".rel.data" + rel.offset + data_offsets.fetch(elf_obj.object_id, 0) + end addend = rel.addend? ? rel.addend : nil new_rel = Caotral::Binary::ELF::Section::Rel.new(addend: rel.addend?) info = (sym << 32) | rel.type diff --git a/sample/assembler/aligned_data_relocation.s b/sample/assembler/aligned_data_relocation.s new file mode 100644 index 0000000..58c9510 --- /dev/null +++ b/sample/assembler/aligned_data_relocation.s @@ -0,0 +1,7 @@ +.data +.balign 32 +.globl aligned_data_reference +.type aligned_data_reference, @object +aligned_data_reference: + .long prefix_data +.size aligned_data_reference, .-aligned_data_reference diff --git a/test/caotral/linker/builder_test.rb b/test/caotral/linker/builder_test.rb index d9f488b..b5da1ef 100644 --- a/test/caotral/linker/builder_test.rb +++ b/test/caotral/linker/builder_test.rb @@ -61,4 +61,24 @@ def test_align_first_text_contribution_after_synthetic_start assert_equal([0x90] * 15, text.body.byteslice(17, 15).bytes) assert_equal(32, aligned_function.value) end + + def test_align_data_relocation_offset + [ + ["sample/assembler/alignment_prefix.s", "alignment_prefix_relocation.o"], + ["sample/assembler/aligned_data_relocation.s", "aligned_data_relocation.o"], + ].each do |(path, input)| + IO.popen(["as", "-o", input, path]).close + @elf_objs << Caotral::Binary::ELF::Reader.read!(input:) + @inputs << input + end + + builder = Caotral::Linker::Builder.new(elf_objs:, executable: false) + elf = builder.build + symtab = elf.find_by_name(".symtab") + symbol = symtab.find_defined_symbol("aligned_data_reference") + relocation = elf.find_by_name(".rela.data").body.first + + assert_equal(32, symbol.value) + assert_equal(symbol.value, relocation.offset) + end end From 8eb6fab0c504a04d3fdf8e43212bdb66f2631a05 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 19 Aug 2026 13:48:30 +0900 Subject: [PATCH 4/5] keep synthetic start detection consistent --- lib/caotral/binary/elf.rb | 8 ++++++++ lib/caotral/linker/builder.rb | 2 +- sample/assembler/undefined_start.s | 12 ++++++++++++ sig/caotral/binary/elf.rbs | 2 ++ sig/caotral/binary/elf/symtab_methods.rbs | 3 +++ test/caotral/linker/builder_test.rb | 22 ++++++++++++++++++++++ 6 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 sample/assembler/undefined_start.s create mode 100644 sig/caotral/binary/elf/symtab_methods.rbs diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index 34acf7f..47fae99 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -31,6 +31,14 @@ def select_by_name(section_name) = @sections.select { |s| section_name == s.sect 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 } } def without_sections(names) = @sections.reject { |s| names.any? { |name| name === s.section_name.to_s } } + def entry_symbol + symtab = find_by_name(".symtab") + raise Caotral::Binary::ELF::Error, "missing .symtab section!!!" unless symtab + raise Caotral::Binary::ELF::Error, "invalid .symtab section!!!" unless symtab.respond_to?(:find_defined_symbol) + symtab.find_defined_symbol("_start") || symtab.find_defined_symbol("main") + end + + def entry_start? = entry_symbol&.name_string == "_start" end end end diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index e856646..259294f 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -150,7 +150,7 @@ def build 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" } } + start_len = 0 if @executable && @elf_objs.any? { |elf| elf.entry_start? } @elf_objs.each do |elf_obj| text = elf_obj.find_by_name(".text") diff --git a/sample/assembler/undefined_start.s b/sample/assembler/undefined_start.s new file mode 100644 index 0000000..bbd183b --- /dev/null +++ b/sample/assembler/undefined_start.s @@ -0,0 +1,12 @@ +.text +.globl main +.type main, @function +main: + mov $42, %eax + ret +.size main, .-main + +.weak _start + +.data + .long _start diff --git a/sig/caotral/binary/elf.rbs b/sig/caotral/binary/elf.rbs index e77b5d5..bba29a7 100644 --- a/sig/caotral/binary/elf.rbs +++ b/sig/caotral/binary/elf.rbs @@ -13,4 +13,6 @@ class Caotral::Binary::ELF def index: (String | Symbol) -> Integer? def select_by_names: (Array[String | Regexp]) -> Array[Caotral::Binary::ELF::Section] def without_sections: (Array[String | Regexp]) -> Array[Caotral::Binary::ELF::Section] + def entry_symbol: () -> Caotral::Binary::ELF::Section::Symtab? + def entry_start?: () -> bool end diff --git a/sig/caotral/binary/elf/symtab_methods.rbs b/sig/caotral/binary/elf/symtab_methods.rbs new file mode 100644 index 0000000..2d0927a --- /dev/null +++ b/sig/caotral/binary/elf/symtab_methods.rbs @@ -0,0 +1,3 @@ +module Caotral::Binary::ELF::SymbolTable + def find_defined_symbol: (String) -> Caotral::Binary::ELF::Section::Symtab? +end diff --git a/test/caotral/linker/builder_test.rb b/test/caotral/linker/builder_test.rb index b5da1ef..3c62bf6 100644 --- a/test/caotral/linker/builder_test.rb +++ b/test/caotral/linker/builder_test.rb @@ -81,4 +81,26 @@ def test_align_data_relocation_offset assert_equal(32, symbol.value) assert_equal(symbol.value, relocation.offset) end + + def test_undefined_start_uses_synthetic_start + input = "undefined_start.o" + IO.popen(["as", "-o", input, "sample/assembler/undefined_start.s"]).close + input_elf = Caotral::Binary::ELF::Reader.read!(input:) + @elf_objs << input_elf + @inputs << input + + input_start = input_elf.find_by_name(".symtab").body.find do |symbol| + symbol.name_string == "_start" + end + + assert_equal(0, input_start.shndx) + assert_false(input_elf.entry_start?) + + elf = Caotral::Linker::Builder.new(elf_objs:).build + text = elf.find_by_name(".text") + main = elf.find_by_name(".symtab").find_defined_symbol("main") + + assert_equal(17, main.value) + assert_equal(main.value - 5, text.body.byteslice(1, 4).unpack1("l<")) + end end From f61943dbc5a331681de630b8a38c25ca0ae8225a Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 19 Aug 2026 15:40:48 +0900 Subject: [PATCH 5/5] align load bias for large sections --- lib/caotral/linker/layout.rb | 13 +++++++++++-- test/caotral/linker/layout_test.rb | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/caotral/linker/layout.rb b/lib/caotral/linker/layout.rb index 546b22d..3d2c2ec 100644 --- a/lib/caotral/linker/layout.rb +++ b/lib/caotral/linker/layout.rb @@ -27,7 +27,6 @@ def apply! private def classify_load_sections! - allocated_sections = @elf.sections.select { |s| s.header.allocated? } read_only_sections = allocated_sections.select { |s| !s.header.execinstr? && !s.header.writable? } executable_sections = allocated_sections.select { |s| s.header.execinstr? && !s.header.writable? } writable_sections = allocated_sections.select { |s| !s.header.execinstr? && s.header.writable? } @@ -64,7 +63,14 @@ def build_program_header(type:, flags: 0) def layout_sections! @file_offset = header_end text = @elf.find_by_name(".text") - @load_bias = text ? text.header.addr - text.header.offset : 0 + entry_offset = 0 + @load_bias = 0 + if text + entry_offset = @elf.header.entry - text.header.addr + requested_bias = text.header.addr - text.header.offset + max_alignment = allocated_sections.map { |s| [s.header.addralign, 1].max }.max + @load_bias = align_up(requested_bias, max_alignment) + end @load_sections.each do |flags, sections| next if sections.empty? @@ -90,6 +96,8 @@ def layout_sections! addr = @load_bias + section.header.offset section.header.set!(addr:) end + @elf.header.set!(entry: text.header.addr + entry_offset) if @executable && text + @elf end def layout_section_headers! shstrtab = @elf.find_by_name(".shstrtab") @@ -184,6 +192,7 @@ def header_end raise Error, "must have ELF header" unless Caotral::Binary::ELF::Header === @elf.header @elf.header.build.bytesize + @elf.program_headers.sum { |ph| ph.build.bytesize } end + def allocated_sections = @elf.sections.select { |s| s.header.allocated? } end end end diff --git a/test/caotral/linker/layout_test.rb b/test/caotral/linker/layout_test.rb index 7c4e863..4689f6b 100644 --- a/test/caotral/linker/layout_test.rb +++ b/test/caotral/linker/layout_test.rb @@ -119,6 +119,20 @@ def test_layout_sections assert_equal(text.header.offset, xph.offset) end + def test_layout_sections_with_huge_text_alignment + text = build_dummy_section(name: ".text", body: "abc".b, addralign: 0x800000, addr: 0x400000, flags: flags(:text)) + elf.sections << text + elf.sections << shstrtab + elf.header.set!(entry: 0x400002) + layout = Caotral::Linker::Layout.new(elf:, shared: false, executable: true, pie: false) + entry_offset = elf.header.entry - text.header.addr + layout.apply! + text = elf.find_by_name(".text") + + assert_equal(0, text.header.addr % text.header.addralign) + assert_equal(entry_offset, elf.header.entry - text.header.addr) + end + private def build_dummy_section(name:, body: "".b, addralign: 1, offset: 0, flags: 0, addr: 0) header = Caotral::Binary::ELF::SectionHeader.new