diff --git a/lib/caotral/binary/elf/reader.rb b/lib/caotral/binary/elf/reader.rb index ffde1b31..ed1f8a95 100644 --- a/lib/caotral/binary/elf/reader.rb +++ b/lib/caotral/binary/elf/reader.rb @@ -143,16 +143,16 @@ def read def validate_relocations rela_dyn = @context.sections.find { |section| section.section_name.to_s == ".rela.dyn" } - pt_load = @context.program_headers.find { |ph| ph.type == :LOAD } + pt_loads = @context.program_headers.select { |ph| ph.type == :LOAD } dynamic = @context.sections.find { |section| section.section_name.to_s == ".dynamic" } rela_plt = @context.sections.find { |section| section.section_name.to_s == ".rela.plt" } rela_plt_exists = !rela_plt.body.empty? got_plt = @context.sections.find { |s| s.section_name.to_s == ".got.plt" } failed_messages = [] - unless rela_dyn && pt_load && dynamic + unless rela_dyn && pt_loads.any? && dynamic data = [ rela_dyn ? nil : ".rela.dyn section", - pt_load ? nil : "LOAD program header", + pt_loads.any? ? nil : "LOAD program header", dynamic ? nil : ".dynamic section" ].compact.join(", ") raise Caotral::Binary::ELF::Error, "Missing required relocations inputs: #{data}" @@ -170,8 +170,11 @@ def validate_relocations end end - valid_range = (pt_load.vaddr...(pt_load.vaddr + pt_load.memsz)) - unless rela_dyn.body.all? { |rel| valid_range.include?(rel.offset) } + within_load = ->(offset) do + pt_loads.any? { |ph| (ph.vaddr ... (ph.vaddr + ph.memsz)).include?(offset) } + end + valid_relocations = rela_dyn.body.all? { |rel| within_load.call(rel.offset) } + unless valid_relocations failed_messages << "Relocation entries in .rela.dyn exceed LOAD segment range" end @@ -187,7 +190,7 @@ def validate_relocations failed_messages << "#{basemsg} DT_PLTGOT does not match .got.plt address" if got_plt && !plt_got rela_plt.body.each do |rel| - vr = valid_range.include?(rel.offset) + vr = within_load.call(rel.offset) js = rel.type_name == :AMD64_JUMP_SLOT failed_messages << "Relocation entries in .rela.plt exceed LOAD segment range" unless vr failed_messages << "Unexpected relocation type in .rela.plt: #{rel.type_name}" unless js diff --git a/lib/caotral/binary/elf/section_header.rb b/lib/caotral/binary/elf/section_header.rb index 70e524ef..655f0caa 100644 --- a/lib/caotral/binary/elf/section_header.rb +++ b/lib/caotral/binary/elf/section_header.rb @@ -54,6 +54,8 @@ def addr = @addr.pack("C*").unpack1("Q<") def link = @link.pack("C*").unpack1("L<") def addralign = @addralign.pack("C*").unpack1("Q<") def allocated? = (@flags.pack("C*").unpack1("Q<") & SHF[:ALLOC]) != 0 + def writable? = (@flags.pack("C*").unpack1("Q<") & SHF[:WRITE]) != 0 + def execinstr? = (@flags.pack("C*").unpack1("Q<") & SHF[:EXECINSTR]) != 0 private def bytes = [@name, @type, @flags, @addr, @offset, @size, @link, @info, @addralign, @entsize] end diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 644d23c1..e5353df2 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -118,6 +118,7 @@ def build sections = [] rel_sections = [] rel_texts = [] + pending_relative_relocations = [] elf.header = elf_obj.header.dup strtab_names = [] text_offsets = {} @@ -184,24 +185,22 @@ def build 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/ - vaddr + start_len + text_offsets.fetch(elf_obj.object_id, 0) - when /\.rela?\.data/ - vaddr + text_offset + data_offsets.fetch(elf_obj.object_id, 0) + start_len - else 0 - end - offset = rel.offset + base_offset - sym = symtab_section.body[sym_index = base_index.nil? ? rel.sym : base_index + rel.sym] - sym_addr = if sym.shndx == text_index - vaddr + sym.value - elsif sym.shndx == data_index - vaddr + text_offset + sym.value + start_len - else - 0 - end - 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:) + relocation = Caotral::Binary::ELF::Section::Rel.new.set!( + offset: 0, + info: (0 << 32) | REL_TYPES[:AMD64_RELATIVE], + addend: 0 + ) + symbol = symtab_section.body[sym] + addend = rel.addend? ? rel.addend : 0 + target, target_offset = + case section.section_name.to_s + when /\.rela?\.text/ + [text_section, rel.offset + start_len + text_offsets.fetch(elf_obj.object_id, 0)] + when /\.rela?\.data/ + [data_section, rel.offset + data_offsets.fetch(elf_obj.object_id, 0)] + end + rela_dyn_section.body << relocation + pending_relative_relocations << { relocation:, target:, target_offset:, symbol:, addend: } next elsif (undefined = symtab.body[rel.sym]&.shndx.to_i == 0) && ALLOW_RELOCATION_TYPES.include?(rel.type) sym = base_index.to_i + rel.sym @@ -425,6 +424,7 @@ def build @linker_metadata[:got_plt_offsets] = got_plt_offsets @linker_metadata[:pending_text_relocations] = rel_texts + @linker_metadata[:pending_relative_relocations] = pending_relative_relocations elf end diff --git a/lib/caotral/linker/finalizer.rb b/lib/caotral/linker/finalizer.rb index 6ba58341..9ed21ff7 100644 --- a/lib/caotral/linker/finalizer.rb +++ b/lib/caotral/linker/finalizer.rb @@ -18,6 +18,7 @@ def apply! finalize_entry! finalize_plt_relocations! finalize_text_relocations! + finalize_relative_relocations! finalize_dynamic_sections! if dynamic? finalize_shared_sections! if dynamic? && plt finalize_section_headers! @@ -74,6 +75,19 @@ def finalize_text_relocations! end end + def finalize_relative_relocations! + pending_relative_relocations.each do |rrh| + symbol = rrh[:symbol] + symbol_section = @elf.sections[symbol.shndx] + target = rrh[:target] + relocation = rrh[:relocation] + relocation.set!( + offset: target.header.addr + rrh[:target_offset], + addend: symbol_section.header.addr + symbol.value + rrh[:addend] + ) + end + end + def finalize_dynamic_sections! dynsym.body.each do |dynsym_body| if dynsym_body.shndx != 0 @@ -183,6 +197,7 @@ def dynamic = @dynamic ||= @elf.find_by_name(".dynamic") def rela_dyn = @rela_dyn ||= @elf.find_by_name(".rela.dyn") def shstrtab = @shstrtab ||= @elf.find_by_name(".shstrtab") def pending_text_relocations = @pending_text_relocations ||= @metadata.fetch(:pending_text_relocations, []) + def pending_relative_relocations = @pending_relative_relocations ||= @metadata.fetch(:pending_relative_relocations, []) def got_plt_offsets = @got_plt_offsets ||= @metadata.fetch(:got_plt_offsets, {}) def dynamic? = (@shared || @pie) end diff --git a/lib/caotral/linker/layout.rb b/lib/caotral/linker/layout.rb index 0f883a97..70014039 100644 --- a/lib/caotral/linker/layout.rb +++ b/lib/caotral/linker/layout.rb @@ -7,6 +7,8 @@ class Linker class Layout attr_reader :debug, :shared, :executable, :pie LATE_SECTIONS = ["", ".shstrtab"].freeze + PF = Caotral::Binary::ELF::ProgramHeader::PF + LOAD_ALIGNMENT = 0x1000 def initialize(elf:, shared:, executable:, pie:) @elf = elf @@ -14,6 +16,7 @@ def initialize(elf:, shared:, executable:, pie:) end def apply! + classify_load_sections! build_program_headers! layout_sections! layout_section_headers! @@ -23,19 +26,34 @@ def apply! end 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? } + if allocated_sections.any? { |s| s.header.execinstr? && s.header.writable? } + raise Caotral::Linker::Error, "can not support alloc section write and executable" + end + + @load_sections = { + R: read_only_sections, + RX: executable_sections, + RW: writable_sections, + } + end + def build_program_headers! - lph = build_program_header(type: 1) + read_only = build_program_header(type: 1, flags: PF[:R]) + executable = build_program_header(type: 1, flags: PF[:RX]) if @load_sections[:RX].any? + writable = build_program_header(type: 1, flags: PF[:RW]) if @load_sections[:RW].any? + lph = [read_only, executable, writable].compact + iph = build_program_header(type: 3) if @elf.find_by_name(".interp") dph = build_program_header(type: 2) if @elf.find_by_name(".dynamic") pph = build_program_header(type: 6) if @pie - if @pie || @shared - gsph = build_program_header( - type: 0x6474e551, - flags: Caotral::Binary::ELF::ProgramHeader::PF[:RW] - ) - end + gsph = build_program_header(type: 0x6474e551, flags: PF[:RW]) if dynamic? - @elf.program_headers.replace([pph, lph, iph, dph, gsph].compact) + @elf.program_headers.replace([pph, *lph, iph, dph, gsph].compact) end def build_program_header(type:, flags: 0) @@ -44,14 +62,23 @@ def build_program_header(type:, flags: 0) ph end def layout_sections! - header_end = (64 + (@elf.program_headers.size * 56)) - first_section = @elf.sections.find { |s| s.section_name && !LATE_SECTIONS.include?(s.section_name) } - first_offset = first_section&.header&.offset.to_i || 0 - @file_offset = [header_end, first_offset].max + @file_offset = header_end + text = @elf.find_by_name(".text") + @load_bias = text ? text.header.addr - text.header.offset : 0 - @elf.sections.each do |section| + @load_sections.each do |flags, sections| + next if sections.empty? + + @file_offset = align_up(@file_offset, LOAD_ALIGNMENT) unless flags == :R + sections.each do |section| + @file_offset = section.layout!(offset: @file_offset) + end + end + + @elf.sections.reject { |s| s.header.allocated? }.each do |section| next if section.section_name.nil? next if LATE_SECTIONS.include?(section.section_name) + @file_offset = section.layout!(offset: @file_offset) end @@ -60,7 +87,7 @@ def layout_sections! @elf.sections.each do |section| next unless section.header.allocated? - addr = text.header.addr + (section.header.offset - text.header.offset) + addr = @load_bias + section.header.offset section.header.set!(addr:) end end @@ -79,35 +106,29 @@ def layout_section_headers! @file_offset += @elf.sections.sum { |section| section.header.build.bytesize } end def finalize_program_headers! - text = @elf.find_by_name(".text") - load_bias = text.nil? ? 0 : text.header.addr - text.header.offset - phdr = @elf.program_headers.any? { |ph| ph.type == :PHDR } @elf.program_headers.each do |ph| case ph.type when :PHDR offset, align = 64, 8 - vaddr = load_bias + offset + vaddr = @load_bias + offset paddr = vaddr filesz = @elf.program_headers.size * 56 memsz = filesz - flags = Caotral::Binary::ELF::ProgramHeader::PF[:R] + flags = PF[:R] when :LOAD - text = @elf.find_by_name(".text") - next unless text + allocated_sections = @load_sections[ph.flags] - allocated_sections = @elf.sections.select { |s| s.header.allocated? } - segment_start = phdr ? 0 : allocated_sections.map { |s| s.header.offset }.min - segment_end = allocated_sections.map { |s| s.header.offset + s.header.size }.max - - next unless segment_end + section_end = allocated_sections.map { |s| s.header.offset + s.header.size }.max + segment_start = ph.flags == :R ? 0 : allocated_sections.map { |s| s.header.offset }.min + segment_end = ph.flags == :R ? [header_end, section_end].compact.max : section_end offset = segment_start - vaddr = load_bias + offset + vaddr = @load_bias + offset paddr = vaddr filesz = segment_end - segment_start memsz = filesz - flags = Caotral::Binary::ELF::ProgramHeader::PF[:RWX] - align = 0x1000 + flags = PF[ph.flags] + align = LOAD_ALIGNMENT when :INTERP interp = @elf.find_by_name(".interp") raise Caotral::Linker::Error, "Missing .interp section" unless interp @@ -115,7 +136,7 @@ def finalize_program_headers! offset, vaddr, paddr, align = header.offset, header.addr, header.addr, 1 filesz = interp.header.size memsz = filesz - flags = Caotral::Binary::ELF::ProgramHeader::PF[:R] + flags = PF[:R] when :DYNAMIC dynamic = @elf.find_by_name(".dynamic") raise Caotral::Linker::Error, "Missing .dynamic section" unless dynamic @@ -123,12 +144,10 @@ def finalize_program_headers! offset, vaddr, paddr, align = header.offset, header.addr, header.addr, header.addralign filesz = header.size memsz = filesz - flags = Caotral::Binary::ELF::ProgramHeader::PF[:RW] + flags = PF[:RW] when :GNU_STACK offset, vaddr, paddr, align = 0, 0, 0, 0 - filesz = 0 - memsz = 0 - flags = Caotral::Binary::ELF::ProgramHeader::PF[:RW] + filesz, memsz, flags = 0, 0, PF[:RW] else raise Caotral::Linker::Error, "Not Implemented: #{ph.type} program header layout" end @@ -151,7 +170,12 @@ def finalize_elf_header! shoffset: @section_headers_offset ) end + def align_up(val, align) = (val + align - 1) / align * align def dynamic? = @pie || @shared + 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 end end end diff --git a/sample/C/movabs.c b/sample/C/movabs.c index 8a678762..b0e69abd 100644 --- a/sample/C/movabs.c +++ b/sample/C/movabs.c @@ -11,5 +11,5 @@ uintptr_t get_addr(void) { int foo = 42; int main(void) { - return (int)get_addr(); + return *(int *)get_addr(); } diff --git a/sample/assembler/large_rodata.s b/sample/assembler/large_rodata.s new file mode 100644 index 00000000..5f34dfcc --- /dev/null +++ b/sample/assembler/large_rodata.s @@ -0,0 +1,10 @@ +.section .rodata +.balign 16 +.fill 9216, 1, 0x41 + +.text +.globl main +.type main, @function +main: + mov $42, %eax + ret diff --git a/sample/assembler/rela_data.s b/sample/assembler/rela_data.s new file mode 100644 index 00000000..b377d09a --- /dev/null +++ b/sample/assembler/rela_data.s @@ -0,0 +1,14 @@ +.section .data +.align 8 +ptr: + .quad value +value: + .quad 42 +.section .text +.globl _start +_start: + mov ptr(%rip), %rax + mov (%rax), %rdi + mov $60, %rax + syscall + diff --git a/sig/caotral/binary/elf/section/symtab.rbs b/sig/caotral/binary/elf/section/symtab.rbs index bddd938b..9e6a3fc8 100644 --- a/sig/caotral/binary/elf/section/symtab.rbs +++ b/sig/caotral/binary/elf/section/symtab.rbs @@ -13,5 +13,6 @@ class Caotral::Binary::ELF::Section::Symtab def name_offset: () -> Integer def info: () -> Integer + def shndx: () -> Integer def value: () -> Integer end diff --git a/sig/caotral/binary/elf/section_header.rbs b/sig/caotral/binary/elf/section_header.rbs index 8155d346..35530981 100644 --- a/sig/caotral/binary/elf/section_header.rbs +++ b/sig/caotral/binary/elf/section_header.rbs @@ -24,4 +24,6 @@ class Caotral::Binary::ELF::SectionHeader def type: () -> Symbol? def info: () -> Integer def addr: () -> Integer + def writable?: () -> bool + def execinstr?: () -> bool end diff --git a/sig/caotral/linker.rbs b/sig/caotral/linker.rbs index d651bdba..0e66ced1 100644 --- a/sig/caotral/linker.rbs +++ b/sig/caotral/linker.rbs @@ -1,4 +1,12 @@ class Caotral::Linker + type pending_relative_relocation = { + relocation: Caotral::Binary::ELF::Section::Rel, + target: Caotral::Binary::ELF::Section, + target_offset: Integer, + symbol: Caotral::Binary::ELF::Section::Symtab, + addend: Integer + } + @inputs: Array[String] @output: String @linker: String diff --git a/sig/caotral/linker/finalizer.rbs b/sig/caotral/linker/finalizer.rbs index 8ce93120..65a3bb45 100644 --- a/sig/caotral/linker/finalizer.rbs +++ b/sig/caotral/linker/finalizer.rbs @@ -4,7 +4,13 @@ class Caotral::Linker::Finalizer @shared: bool @executable: bool @pie: bool + @pending_relative_relocations: Array[Caotral::Linker::pending_relative_relocation]? def initialize: (elf: Caotral::Binary::ELF, metadata: Hash[Symbol, untyped], shared: bool, executable: bool, pie: bool, debug: bool) -> void def apply!: () -> Caotral::Binary::ELF + + private + + def finalize_relative_relocations!: () -> void + def pending_relative_relocations: () -> Array[Caotral::Linker::pending_relative_relocation] end diff --git a/test/caotral/linker/integration/pie_object_test.rb b/test/caotral/linker/integration/pie_object_test.rb index 409ab80c..7ffc0f8c 100644 --- a/test/caotral/linker/integration/pie_object_test.rb +++ b/test/caotral/linker/integration/pie_object_test.rb @@ -97,7 +97,7 @@ def test_executable_movabs_pie_object elf = elf_obj.read IO.popen("./pie").close exit_code, handle_code = check_process($?.to_i) - assert_equal(60, exit_code) + assert_equal(42, exit_code) assert_equal(0, handle_code) section_names = elf.sections.map(&:section_name) @@ -118,4 +118,69 @@ def test_executable_movabs_pie_object assert_not_equal(0, elf.header.entry) assert(elf_obj.validate_relocations) end + + def test_rela_data_pie_object + path = Pathname.new("sample/assembler/rela_data.s").to_s + IO.popen(["as", "-o", @inputs.first, path]).close + + Caotral::Linker.link!( + inputs: @inputs, + output: @output, + linker: "self", + pie: true + ) + + elf_obj = Caotral::Binary::ELF::Reader.new(input: @output, debug: false) + elf = elf_obj.read + data = elf.find_by_name(".data") + rela_dyn = elf.find_by_name(".rela.dyn") + relocation = rela_dyn.body.first + rw_load = elf.program_headers.find { + |ph| ph.type == :LOAD && ph.flags == :RW + } + + assert_equal( + data.header.addr, + relocation.offset, + "R_X86_64_RELATIVE target must follow the final .data address" + ) + assert_equal( + data.header.addr + 8, + relocation.addend, + "R_X86_64_RELATIVE addend must point to value" + ) + assert_operator(relocation.offset, :>=, rw_load.vaddr) + assert_operator(relocation.offset, :<, rw_load.vaddr + rw_load.memsz) + + IO.popen(["./#{@output}"]).close + exit_code, handle_code = check_process($?.to_i) + assert_equal(42, exit_code) + assert_equal(0, handle_code) + end + + def test_executable_large_rodata_pie_object + path = Pathname.new("sample/assembler/large_rodata.s").to_s + IO.popen(["as", "-o", @inputs.first, path]).close + + Caotral::Linker.link!( + inputs: @inputs, + output: @output, + linker: "self", + pie: true + ) + + elf_reader = Caotral::Binary::ELF::Reader.new(input: @output, debug: false) + elf = elf_reader.read + text = elf.find_by_name(".text") + rodata = elf.find_by_name(".rodata") + + assert_operator(text.header.offset, :>=, 0x3000) + assert_equal(text.header.addr, text.header.offset) + assert_equal(9216, rodata.header.size) + + IO.popen(["./#{@output}"]).close + exit_code, handle_code = check_process($?.to_i) + assert_equal(42, exit_code) + assert_equal(0, handle_code) + end end diff --git a/test/caotral/linker/layout_test.rb b/test/caotral/linker/layout_test.rb index 8df6d789..7c4e863e 100644 --- a/test/caotral/linker/layout_test.rb +++ b/test/caotral/linker/layout_test.rb @@ -40,12 +40,12 @@ def test_layout_minimal_section end def test_layout_with_text - text = build_dummy_section(name: ".text", offset: 0x1000) + text = build_dummy_section(name: ".text", offset: 0x1000, flags: flags(:text)) elf.sections << text elf.sections << shstrtab layout = Caotral::Linker::Layout.new(elf:, shared: false, executable: true, pie: false) layout.apply! - assert_equal(1, elf.program_headers.size) + assert_equal(2, elf.program_headers.size) assert_equal(:LOAD, elf.program_headers[0].type) assert_equal(0x1000, text.header.offset) end @@ -54,11 +54,15 @@ def test_layout_with_pie elf.sections << shstrtab layout = Caotral::Linker::Layout.new(elf:, shared: false, executable: false, pie: true) layout.apply! + pph = elf.program_headers.find { |ph| ph.type == :PHDR } + lph = elf.program_headers.find { |ph| ph.type == :LOAD } + iph = elf.program_headers.find { |ph| ph.type == :INTERP } + gph = elf.program_headers.find { |ph| ph.type == :GNU_STACK } assert_equal(4, elf.program_headers.size) - assert_equal(:PHDR, elf.program_headers[0].type) - assert_equal(:LOAD, elf.program_headers[1].type) - assert_equal(:INTERP, elf.program_headers[2].type) - assert_equal(:GNU_STACK, elf.program_headers[3].type) + assert_equal(:PHDR, pph.type) + assert_equal(:LOAD, lph.type) + assert_equal(:INTERP, iph.type) + assert_equal(:GNU_STACK, gph.type) assert_equal(:DYN, elf.header.type) end @@ -74,39 +78,45 @@ def test_layout_with_dynamic elf.sections << shstrtab layout = Caotral::Linker::Layout.new(elf:, shared: true, executable: false, pie: false) layout.apply! - assert_equal(3, elf.program_headers.size) - assert_equal(:LOAD, elf.program_headers[0].type) - assert_equal(:DYNAMIC, elf.program_headers[1].type) - assert_equal(:GNU_STACK, elf.program_headers[2].type) + rph = elf.program_headers.find { |ph| ph.type == :LOAD && ph.flags == :R } + wph = elf.program_headers.find { |ph| ph.type == :LOAD && ph.flags == :RW } + dph = elf.program_headers.find { |ph| ph.type == :DYNAMIC } + gph = elf.program_headers.find { |ph| ph.type == :GNU_STACK } + + assert_equal(4, elf.program_headers.size) + assert_equal(:LOAD, rph.type) + assert_equal(:LOAD, wph.type) + assert_equal(:DYNAMIC, dph.type) + assert_equal(:GNU_STACK, gph.type) assert_equal(:DYN, elf.header.type) - assert_equal(dynamic.header.offset, elf.program_headers[1].offset) - assert_equal(dynamic.header.addr, elf.program_headers[1].vaddr) - assert_equal(dynamic.header.addr, elf.program_headers[1].paddr) - assert_equal(dynamic.header.size, elf.program_headers[1].filesz) - assert_equal(dynamic.header.size, elf.program_headers[1].memsz) - assert_equal(dynamic.header.addralign, elf.program_headers[1].align) - assert_equal(:RW, elf.program_headers[1].flags) + assert_equal(dynamic.header.offset, dph.offset) + assert_equal(dynamic.header.addr, dph.vaddr) + assert_equal(dynamic.header.addr, dph.paddr) + assert_equal(dynamic.header.size, dph.filesz) + assert_equal(dynamic.header.size, dph.memsz) + assert_equal(dynamic.header.addralign, dph.align) + assert_equal(:RW, dph.flags) end def test_layout_sections - text = build_dummy_section(name: ".text", body: "abc".b, addralign: 16, flags: flags(:text), addr: 0x10) + text = build_dummy_section(name: ".text", body: "abc".b, addralign: 16, flags: flags(:text)) data = build_dummy_section(name: ".data", body: "def".b, addralign: 8, flags: flags(:data)) elf.sections << text elf.sections << data elf.sections << shstrtab layout = Caotral::Linker::Layout.new(elf:, shared: false, executable: true, pie: false) layout.apply! + xph = elf.program_headers.find { |ph| ph.type == :LOAD && ph.flags == :RX } - assert_equal(128, text.header.offset) + assert_equal(3, elf.program_headers.size) + assert_equal(0x1000, text.header.offset) assert_equal(3, text.header.size) - assert_equal(136, data.header.offset) + assert_equal(0x2000, data.header.offset) assert_equal(3, data.header.size) - assert_equal(139, shstrtab.header.offset) + assert_equal(0x2003, shstrtab.header.offset) assert_equal(22, shstrtab.header.size) assert_equal(".text\0.data\0.shstrtab\0", shstrtab.body.names) - assert_equal(0x10, text.header.addr) - assert_equal(0x18, data.header.addr) - assert_equal(text.header.offset, elf.program_headers[0].offset) + assert_equal(text.header.offset, xph.offset) end private diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index 7b077a96..4a132405 100644 --- a/test/caotral/linker/writer_test.rb +++ b/test/caotral/linker/writer_test.rb @@ -17,6 +17,7 @@ def teardown File.delete("write") if File.exist?("write") File.delete("relocatable.o") if File.exist?("relocatable.o") File.delete("relocated_exec") if File.exist?("relocated_exec") + File.delete("output") if File.exist?("output") end def test_write written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "write.o") @@ -51,10 +52,24 @@ def test_relocation_write_and_execute assert_equal(0, exit_code) end + def test_writer_do_not_mutate_finalized_elf + before_elf_sections = snapshot(@elf_obj) + output = "output" + Caotral::Linker::Writer.new(elf_obj: @elf_obj, output:).write + assert_equal(before_elf_sections, snapshot(@elf_obj)) + end + private def check_process(status) exit_code = status >> 8 handle_code = status & 0x7f [exit_code, handle_code] end + def snapshot(elf) + { + header: elf.header.build, + program_header: elf.program_headers.map(&:build), + sections: elf.sections.map { |s| [s.section_name, s.header.build, s.build] } + } + end end