From 94a0acf0d2242b9c147aa20e10d4a9fa58c3e285 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 28 Jan 2026 09:16:20 +0900 Subject: [PATCH 01/15] add .interp section and pie flag plumbing --- lib/caotral/linker/builder.rb | 48 +++++++++++++++++++++++++---------- lib/caotral/linker/writer.rb | 13 ++++++++-- 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index e6017b0..8d59d0b 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -11,15 +11,16 @@ class Builder BIND_BY_VALUE = SYMTAB_BIND.invert.freeze RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze - GENERATED_SECTION_NAMES = [".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic"].freeze + GENERATED_SECTION_NAMES = [".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic", ".interp"].freeze + SHT = Caotral::Binary::ELF::SectionHeader::SHT attr_reader :symbols - def initialize(elf_objs:, executable: true, debug: false, shared: false) + def initialize(elf_objs:, executable: true, debug: false, shared: false, pie: false) @elf_objs = elf_objs - @executable, @debug, @shared = executable, debug, shared + @executable, @debug, @shared, @pie = executable, debug, shared, pie @symbols = { locals: Set.new, globals: Set.new, weaks: Set.new } - sharing_object! + _mode! end def build @@ -152,7 +153,9 @@ def build entsize: 24 ) - sections += build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, text_section:) if @shared + sections += build_pie_sections if @pie + sections += build_shared_dynamic_sections if @shared || @pie + sections << build_dynamic_section if @shared || @pie sections << symtab_section rel_sections.each { |s| sections << s.dup } @@ -257,15 +260,15 @@ def resolve_symbols end private - def build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, text_section:) - sht = Caotral::Binary::ELF::SectionHeader::SHT + + def build_shared_dynamic_sections dynstr_section = Caotral::Binary::ELF::Section.new( body: Caotral::Binary::ELF::Section::Strtab.new("\0".b), section_name: ".dynstr", header: Caotral::Binary::ELF::SectionHeader.new ) - dynstr_section.header.set!(type: sht[:strtab], flags: 0, addralign: 1, entsize: 0) + dynstr_section.header.set!(type: SHT[:strtab], flags: 0, addralign: 1, entsize: 0) dynsym_section = Caotral::Binary::ELF::Section.new( body: [Caotral::Binary::ELF::Section::Symtab.new], @@ -273,19 +276,33 @@ def build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, t header: Caotral::Binary::ELF::SectionHeader.new ) - dynsym_section.header.set!(type: sht[:dynsym], flags: 0, addralign: 8, entsize: 24) + dynsym_section.header.set!(type: SHT[:dynsym], flags: 0, addralign: 8, entsize: 24) + + [dynstr_section, dynsym_section,] + end + + def build_pie_sections + interp_section = Caotral::Binary::ELF::Section.new( + body: "/lib64/ld-linux-x86-64.so.2\0".b, + section_name: ".interp", + header: Caotral::Binary::ELF::SectionHeader.new + ) + interp_section.header.set!(type: SHT[:progbits], addralign: 1, flags: 0, entsize: 0) + [interp_section] + end + + def build_dynamic_section dynamic_section = Caotral::Binary::ELF::Section.new( body: [Caotral::Binary::ELF::Section::Dynamic.new], section_name: ".dynamic", header: Caotral::Binary::ELF::SectionHeader.new ) - dynamic_section.header.set!(type: sht[:dynamic], flags: 0, addralign: 8, entsize: 16) - - [dynstr_section, dynsym_section, dynamic_section] + dynamic_section.header.set!(type: SHT[:dynamic], flags: 0, addralign: 8, entsize: 16) + dynamic_section end - + def ref_index(sections, section_name) raise Caotral::Binary::ELF::Error, "invalid section name: #{section_name}" if section_name.nil? ref_names = "." + section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.join(".") @@ -295,7 +312,10 @@ def ref_index(sections, section_name) def rel_type(section) = section.section_name&.start_with?(".rela.") ? 4 : 9 def rel_entsize(section) = section.section_name&.start_with?(".rela.") ? 24 : 16 - def sharing_object! = (@executable = false if @shared) + def _mode! + @executable = false if @shared + raise Caotral::Binary::ELF::Error, "disallow both mode: shared and PIE" if @pie && @shared + end end end end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index c10a062..d686343 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -12,8 +12,8 @@ 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) - @elf_obj, @output, @entry, @debug, @executable, @shared = elf_obj, output, entry, debug, executable, shared + 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 @write_sections = write_order_sections end @@ -100,6 +100,7 @@ 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(".interp") write_order << @elf_obj.find_by_name(".dynstr") write_order << @elf_obj.find_by_name(".dynsym") write_order << @elf_obj.find_by_name(".dynamic") @@ -112,6 +113,13 @@ def write_order_sections def write_section_index(section_name) = @write_sections.index { it.section_name == section_name } def write_shared_dynamic_sections(file:) + if interp_section + interp_offset = file.pos + file.write(interp_section.body) + size = file.pos - interp_offset + interp_section.header.set!(offset: interp_offset, size:) + end + dynstr_offset = file.pos file.write(dynstr_section.body.build) size = file.pos - dynstr_offset @@ -155,6 +163,7 @@ def shstrtab_section = @shstrtab_section ||= @write_sections.find { |s| ".shstrt def dynstr_section = @dynstr_section ||= @write_sections.find { |s| ".dynstr" === s.section_name.to_s } def dynsym_section = @dynsym_section ||= @write_sections.find { |s| ".dynsym" === s.section_name.to_s } def dynamic_section = @dynamic_section ||= @write_sections.find { |s| ".dynamic" === s.section_name.to_s } + def interp_section = @interp_section ||= @write_sections.find { |s| ".interp" === s.section_name.to_s } end end end From 3810e5ef667fb41922017d09205564c8ae6ad7de Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 29 Jan 2026 22:43:08 +0900 Subject: [PATCH 02/15] write PT_INTERP program header for PIE --- lib/caotral/linker/writer.rb | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index d686343..7b2fa0a 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -19,12 +19,16 @@ def initialize(elf_obj:, output:, entry: nil, debug: false, executable: true, sh def write f = File.open(@output, "wb") - phoffset, phnum, phsize, ehsize = 64, 1, 56, 64 + phoffset, phsize, ehsize = 64, 56, 64 file_type = @shared ? :DYN : :EXEC e_type = Caotral::Binary::ELF::Header::TYPE[file_type] + # PT_LOAD + lph = Caotral::Binary::ELF::ProgramHeader.new + # PT_INTERP + iph = Caotral::Binary::ELF::ProgramHeader.new if interp_section + phnum = iph ? 2 : 1 header = @elf_obj.header.set!(type: e_type, phoffset:, phnum:, phsize:, ehsize:) - ph = Caotral::Binary::ELF::ProgramHeader.new text_offset = text_section.header.offset align = 0x1000 vaddr = text_section.header.addr @@ -35,13 +39,22 @@ def write entry = @shared ? 0 : (@entry || vaddr) header.set!(entry:) - ph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:) + lph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:) f.write(@elf_obj.header.build) - f.write(ph.build) + f.write(lph.build) + f.write(iph.build) if iph gap = [text_offset - f.pos, 0].max f.write("\0" * gap) f.write(text_section.body) - write_shared_dynamic_sections(file: f) if @shared + write_shared_dynamic_sections(file: f) if @shared || @pie + if iph + ish = interp_section.header + iph.set!(type: 3, offset: ish.offset, vaddr: 0, paddr: 0, filesz: ish.size, memsz: ish.size, flags: 4, align: 1) + cur = f.pos + f.seek(phoffset + phsize) + f.write(iph.build) + f.seek(cur) + end symtab_offset = f.pos symtab_section.body.each { |sym| f.write(sym.build) } symtab_entsize = symtab_section.body.first&.build&.bytesize.to_i From 8699e22a436fe09bff176bb34f34d47b24cfcd96 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 31 Jan 2026 20:58:15 +0900 Subject: [PATCH 03/15] support pie headers in linker --- lib/caotral/binary/elf/program_header.rb | 13 +++++++++++ lib/caotral/binary/elf/section_header.rb | 1 + lib/caotral/linker.rb | 18 +++++++-------- lib/caotral/linker/writer.rb | 25 ++++++++++++++++----- test/caotral/linker/pie-object_test.rb | 28 ++++++++++++++++++++++++ 5 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 test/caotral/linker/pie-object_test.rb diff --git a/lib/caotral/binary/elf/program_header.rb b/lib/caotral/binary/elf/program_header.rb index 9eaaf68..fe75a2b 100644 --- a/lib/caotral/binary/elf/program_header.rb +++ b/lib/caotral/binary/elf/program_header.rb @@ -5,6 +5,18 @@ module Binary class ELF class ProgramHeader include Caotral::Binary::ELF::Utils + PF_X = 1 + PF_W = 2 + PF_R = 4 + PF = { + RWX: PF_R | PF_W | PF_X, + RW: PF_R | PF_W, + RX: PF_R | PF_X, + WX: PF_W | PF_X, + R: PF_R, + W: PF_W, + X: PF_X, + } def initialize @type = num2bytes(0, 4) @flags = num2bytes(0, 4) @@ -27,6 +39,7 @@ def set!(type: nil, flags: nil, offset: nil, vaddr: nil, paddr: nil, filesz: nil @align = num2bytes(align, 8) if check(align, 8) self end + private def bytes = [@type, @flags, @offset, @vaddr, @paddr, @filesz, @memsz, @align] end end diff --git a/lib/caotral/binary/elf/section_header.rb b/lib/caotral/binary/elf/section_header.rb index 77f6aa4..44ca070 100644 --- a/lib/caotral/binary/elf/section_header.rb +++ b/lib/caotral/binary/elf/section_header.rb @@ -47,6 +47,7 @@ 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<") + def addralign = @addralign.pack("C*").unpack1("Q<") 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 e2d3e7a..2663d63 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -5,18 +5,18 @@ module Caotral class Linker - def self.link!(inputs:, output: "a.out", linker: "mold", debug: false, shared: false, executable: true) - new(inputs:, output:, linker:, debug:, shared:, executable:).link + 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 end - def initialize(inputs:, output: "a.out", linker: "mold", linker_options: [], executable: true, shared: false, debug: false) + def initialize(inputs:, output: "a.out", linker: "mold", linker_options: [], executable: true, shared: false, pie: false, debug: false) @inputs, @output, @linker = inputs, output, linker @options = linker_options - @executable, @debug, @shared = executable, debug, shared + @executable, @debug, @shared, @pie = executable, debug, shared, pie end - def link(inputs: @inputs, output: @output, debug: @debug, shared: @shared, executable: @executable) - return to_elf(inputs:, output:, debug:, shared:, executable:) if @linker == "self" + def link(inputs: @inputs, output: @output, debug: @debug, shared: @shared, executable: @executable, pie: @pie) + return to_elf(inputs:, output:, debug:, shared:, executable:, pie:) if @linker == "self" IO.popen(link_command).close end @@ -49,12 +49,12 @@ 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) + def to_elf(inputs: @inputs, output: @output, debug: @debug, shared: @shared, executable: @executable, pie: @pie) elf_objs = inputs.map { |input| Caotral::Binary::ELF::Reader.new(input:, debug:).read } - builder = Caotral::Linker::Builder.new(elf_objs:, debug:, shared:, executable:) + builder = Caotral::Linker::Builder.new(elf_objs:, debug:, shared:, executable:, pie:) builder.resolve_symbols elf_obj = builder.build - Caotral::Linker::Writer.new(elf_obj:, output:, debug:, shared:, executable:).write + Caotral::Linker::Writer.new(elf_obj:, output:, debug:, shared:, executable:, pie:).write File.chmod(0755, output) if executable output end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 7b2fa0a..b7de7b0 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -20,20 +20,21 @@ def initialize(elf_obj:, output:, entry: nil, debug: false, executable: true, sh def write f = File.open(@output, "wb") phoffset, phsize, ehsize = 64, 56, 64 - file_type = @shared ? :DYN : :EXEC - e_type = Caotral::Binary::ELF::Header::TYPE[file_type] + e_type = elf_type # PT_LOAD lph = Caotral::Binary::ELF::ProgramHeader.new # PT_INTERP iph = Caotral::Binary::ELF::ProgramHeader.new if interp_section - phnum = iph ? 2 : 1 + # PT_DYNAMIC + dph = Caotral::Binary::ELF::ProgramHeader.new if dynamic_section + phnum = [lph, iph, dph].compact.size header = @elf_obj.header.set!(type: e_type, phoffset:, phnum:, phsize:, ehsize:) text_offset = text_section.header.offset align = 0x1000 vaddr = text_section.header.addr paddr = vaddr - type, flags = 1, 5 + type, flags = 1, program_header_flags(:RX) filesz = text_section.body.bytesize memsz = filesz entry = @shared ? 0 : (@entry || vaddr) @@ -47,14 +48,23 @@ def write f.write("\0" * gap) f.write(text_section.body) write_shared_dynamic_sections(file: f) if @shared || @pie + ph_pos = phoffset if iph ish = interp_section.header - iph.set!(type: 3, offset: ish.offset, vaddr: 0, paddr: 0, filesz: ish.size, memsz: ish.size, flags: 4, align: 1) + iph.set!(type: 3, offset: ish.offset, vaddr: 0, paddr: 0, filesz: ish.size, memsz: ish.size, flags: program_header_flags(:R), align: 1) cur = f.pos - f.seek(phoffset + phsize) + f.seek(ph_pos += phsize) f.write(iph.build) f.seek(cur) end + if dph + dsh = dynamic_section.header + dph.set!(type: 2, offset: dsh.offset, filesz: dsh.size, memsz: dsh.size, vaddr: dsh.addr || 0, paddr: dsh.addr || 0, flags: program_header_flags(:R), align: dsh.addralign) + cur = f.pos + f.seek(ph_pos += phsize) + f.write(dph.build) + f.seek(cur) + end symtab_offset = f.pos symtab_section.body.each { |sym| f.write(sym.build) } symtab_entsize = symtab_section.body.first&.build&.bytesize.to_i @@ -168,6 +178,9 @@ def link_index(section_name) end end + def program_header_flags(flag) = Caotral::Binary::ELF::ProgramHeader::PF[flag.to_sym] + def elf_type = Caotral::Binary::ELF::Header::TYPE[@shared || @pie ? :DYN : :EXEC] + def text_section = @text_section ||= @write_sections.find { |s| ".text" === s.section_name.to_s } def rel_sections = @rel_sections ||= @write_sections.select { RELOCATION_SECTION_NAMES.include?(it.section_name) } def symtab_section = @symtab_section ||= @write_sections.find { |s| ".symtab" === s.section_name.to_s } diff --git a/test/caotral/linker/pie-object_test.rb b/test/caotral/linker/pie-object_test.rb new file mode 100644 index 0000000..3e6aa80 --- /dev/null +++ b/test/caotral/linker/pie-object_test.rb @@ -0,0 +1,28 @@ +require_relative "../../test_suite" + +class Caotral::Linker::PIEObjectLinkingTest < Test::Unit::TestCase + include TestProcessHelper + def setup + @inputs = ["pie.o"] + @output = "pie" + path = Pathname.new("sample/C/shared-object.c").to_s + IO.popen(["gcc", "-fPIE", "-c", "-o", @inputs.first, "%s" % path]).close + end + + def teardown + File.delete(@inputs[0]) if File.exist?(@inputs[0]) + File.delete(@output) if File.exist?(@output) + end + + def test_pie_object + Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", executable: false, pie: true) + elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) + section_names = elf.sections.map(&:section_name) + assert_include(section_names, ".dynstr") + assert_include(section_names, ".dynsym") + assert_include(section_names, ".dynamic") + assert_include(section_names, ".interp") + assert_equal(:DYN, elf.header.type) + assert_equal(:AMD64, elf.header.arch) + end +end From 99da7cb2ab65042f4e6d1a540e72f70141d044ec Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 31 Jan 2026 22:05:25 +0900 Subject: [PATCH 04/15] read program headers in elf reader --- lib/caotral/binary/elf.rb | 3 ++- lib/caotral/binary/elf/program_header.rb | 17 +++++++++++++- lib/caotral/binary/elf/reader.rb | 30 +++++++++++++++++++++++- test/caotral/linker/pie-object_test.rb | 8 +++++++ 4 files changed, 55 insertions(+), 3 deletions(-) diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index fad0a02..a79b222 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -14,9 +14,10 @@ module Caotral module Binary class ELF include Enumerable - attr_reader :sections + attr_reader :sections, :program_headers attr_accessor :header def initialize + @program_headers = [] @sections = [] @header = nil end diff --git a/lib/caotral/binary/elf/program_header.rb b/lib/caotral/binary/elf/program_header.rb index fe75a2b..bf53dff 100644 --- a/lib/caotral/binary/elf/program_header.rb +++ b/lib/caotral/binary/elf/program_header.rb @@ -16,7 +16,16 @@ class ProgramHeader R: PF_R, W: PF_W, X: PF_X, - } + NOP: 0, + }.freeze + PF_BY_V = PF.invert.freeze + PT = { + NULL: 0, + LOAD: 1, + DYNAMIC: 2, + INTERP: 3, + }.freeze + PT_BY_V = PT.invert.freeze def initialize @type = num2bytes(0, 4) @flags = num2bytes(0, 4) @@ -40,6 +49,12 @@ def set!(type: nil, flags: nil, offset: nil, vaddr: nil, paddr: nil, filesz: nil self end + def type = PT_BY_V[@type.pack("C*").unpack1("L<")] + def flags = PF_BY_V[@flags.pack("C*").unpack1("L<")] + def offset = @offset.pack("C*").unpack1("Q<") + def filesz = @filesz.pack("C*").unpack1("Q<") + def memsz = @memsz.pack("C*").unpack1("Q<") + private def bytes = [@type, @flags, @offset, @vaddr, @paddr, @filesz, @memsz, @align] end end diff --git a/lib/caotral/binary/elf/reader.rb b/lib/caotral/binary/elf/reader.rb index 89deda9..3300f5f 100644 --- a/lib/caotral/binary/elf/reader.rb +++ b/lib/caotral/binary/elf/reader.rb @@ -24,10 +24,28 @@ def read entry = header[24, 8].unpack1("Q<") phoffset = header[32, 8].unpack1("Q<") shoffset = header[40, 8].unpack1("Q<") + phsize = header[54, 2].unpack1("S<") + phnum = header[56, 2].unpack1("S<") shentsize = header[58, 2].unpack1("S<") shnum = header[60, 2].unpack1("S<") shstrndx = header[62, 2].unpack1("S<") - @context.header.set!(type:, arch:, entry:, phoffset:, shoffset:, shnum:, shstrndx:) + @context.header.set!(type:, arch:, entry:, phoffset:, phsize:, phnum:, shoffset:, shnum:, shstrndx:) + + @bin.pos = phoffset + phnum.times do |i| + ph_entry = @bin.read(phsize) + type = ph_entry[0, 4].unpack1("L<") + flags = ph_entry[4, 4].unpack1("L<") + offset = ph_entry[8, 8].unpack1("Q<") + vaddr = ph_entry[16, 8].unpack1("Q<") + paddr = ph_entry[24, 8].unpack1("Q<") + filesz = ph_entry[32, 8].unpack1("Q<") + memsz = ph_entry[40, 8].unpack1("Q<") + align = ph_entry[48, 8].unpack1("Q<") + ph = Caotral::Binary::ELF::ProgramHeader.new + ph.set!(type:, flags:, offset:, vaddr:, paddr:, filesz:, memsz:, align:) + @context.program_headers.push(ph) + end @bin.pos = shoffset shnum.times do |i| @@ -93,6 +111,16 @@ def read addend = rela ? rel_bin[16, 8].unpack1("q<") : nil Caotral::Binary::ELF::Section::Rel.new(addend: rela).set!(offset:, info:, addend:) end + when :dynamic + dyn_entsize = section.header.entsize + dyn_entsize = 16 if dyn_entsize.zero? + count = body_bin.bytesize / dyn_entsize + count.times.map do |i| + dyn_bin = body_bin.byteslice(i * dyn_entsize, dyn_entsize) + tag = dyn_bin[0, 8].unpack1("Q<") + un = dyn_bin[8, 8].unpack1("Q<") + Caotral::Binary::ELF::Section::Dynamic.new.set!(tag:, un:) + end when :progbits body_bin end diff --git a/test/caotral/linker/pie-object_test.rb b/test/caotral/linker/pie-object_test.rb index 3e6aa80..2738edf 100644 --- a/test/caotral/linker/pie-object_test.rb +++ b/test/caotral/linker/pie-object_test.rb @@ -18,6 +18,14 @@ def test_pie_object Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", executable: false, pie: true) elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) section_names = elf.sections.map(&:section_name) + program_header_types = elf.program_headers.map(&:type) + interp = elf.find_by_name(".interp") + dynamic = elf.find_by_name(".dynamic").body.first + assert_include(program_header_types, :LOAD) + assert_include(program_header_types, :DYNAMIC) + assert_include(program_header_types, :INTERP) + assert_equal(interp.body.split("\0").first, "/lib64/ld-linux-x86-64.so.2") + assert_equal(dynamic.null?, true) assert_include(section_names, ".dynstr") assert_include(section_names, ".dynsym") assert_include(section_names, ".dynamic") From 4189b7477d87feeb319f55b28613a275fa914ba8 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 31 Jan 2026 23:03:35 +0900 Subject: [PATCH 05/15] adjust pie entry behavior --- lib/caotral/linker/writer.rb | 2 +- sample/C/pie-object.c | 3 +++ sample/C/shared-object.c | 1 + test/caotral/linker/pie-object_test.rb | 26 ++++++++++++++++++++++++-- 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 sample/C/pie-object.c diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index b7de7b0..4f5c50e 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -37,7 +37,7 @@ def write type, flags = 1, program_header_flags(:RX) filesz = text_section.body.bytesize memsz = filesz - entry = @shared ? 0 : (@entry || vaddr) + entry = (@shared || !@executable) ? 0 : (@entry || vaddr) header.set!(entry:) lph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:) diff --git a/sample/C/pie-object.c b/sample/C/pie-object.c new file mode 100644 index 0000000..d3ee408 --- /dev/null +++ b/sample/C/pie-object.c @@ -0,0 +1,3 @@ +int foo() { return 42; } + +int main() { return foo(); } diff --git a/sample/C/shared-object.c b/sample/C/shared-object.c index bf7759e..c1b207f 100644 --- a/sample/C/shared-object.c +++ b/sample/C/shared-object.c @@ -1 +1,2 @@ int foo() { return 42; } + diff --git a/test/caotral/linker/pie-object_test.rb b/test/caotral/linker/pie-object_test.rb index 2738edf..582d661 100644 --- a/test/caotral/linker/pie-object_test.rb +++ b/test/caotral/linker/pie-object_test.rb @@ -5,7 +5,7 @@ class Caotral::Linker::PIEObjectLinkingTest < Test::Unit::TestCase def setup @inputs = ["pie.o"] @output = "pie" - path = Pathname.new("sample/C/shared-object.c").to_s + path = Pathname.new("sample/C/pie-object.c").to_s IO.popen(["gcc", "-fPIE", "-c", "-o", @inputs.first, "%s" % path]).close end @@ -14,7 +14,7 @@ def teardown File.delete(@output) if File.exist?(@output) end - def test_pie_object + def test_non_executable_pie_object Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", executable: false, pie: true) elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) section_names = elf.sections.map(&:section_name) @@ -32,5 +32,27 @@ def test_pie_object assert_include(section_names, ".interp") assert_equal(:DYN, elf.header.type) assert_equal(:AMD64, elf.header.arch) + assert_equal(0, elf.header.entry) + end + + def test_executable_pie_object + Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", pie: true) + elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) + section_names = elf.sections.map(&:section_name) + program_header_types = elf.program_headers.map(&:type) + interp = elf.find_by_name(".interp") + dynamic = elf.find_by_name(".dynamic").body.first + assert_include(program_header_types, :LOAD) + assert_include(program_header_types, :DYNAMIC) + assert_include(program_header_types, :INTERP) + assert_equal(interp.body.split("\0").first, "/lib64/ld-linux-x86-64.so.2") + assert_equal(dynamic.null?, true) + assert_include(section_names, ".dynstr") + assert_include(section_names, ".dynsym") + assert_include(section_names, ".dynamic") + assert_include(section_names, ".interp") + assert_equal(:DYN, elf.header.type) + assert_equal(:AMD64, elf.header.arch) + assert_not_equal(0, elf.header.entry) end end From 9dc16f611b7463ed6aed79185abfe3925d6a706d Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sun, 1 Feb 2026 14:34:54 +0900 Subject: [PATCH 06/15] add rela.dyn section support --- lib/caotral/binary/elf/section/dynamic.rb | 8 +++++++- lib/caotral/linker/builder.rb | 24 ++++++++++++++++++++--- lib/caotral/linker/writer.rb | 8 +++++--- 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/lib/caotral/binary/elf/section/dynamic.rb b/lib/caotral/binary/elf/section/dynamic.rb index c531d20..2f4be5b 100644 --- a/lib/caotral/binary/elf/section/dynamic.rb +++ b/lib/caotral/binary/elf/section/dynamic.rb @@ -5,7 +5,13 @@ class ELF class Section class Dynamic include Caotral::Binary::ELF::Utils - TAG_TYPES = { NULL: 0 }.freeze + TAG_TYPES = { + NULL: 0, + RELA: 7, + RELASZ: 8, + RELAENT: 9, + }.freeze + TAG_TYPES_BY_V = TAG_TYPES.invert.freeze def initialize @tag = num2bytes(0, 8) diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 8d59d0b..8568c4b 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -11,7 +11,7 @@ class Builder BIND_BY_VALUE = SYMTAB_BIND.invert.freeze RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze - GENERATED_SECTION_NAMES = [".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic", ".interp"].freeze + GENERATED_SECTION_NAMES = [".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic", ".interp", ".rela.dyn"].freeze SHT = Caotral::Binary::ELF::SectionHeader::SHT attr_reader :symbols @@ -154,8 +154,15 @@ def build ) sections += build_pie_sections if @pie - sections += build_shared_dynamic_sections if @shared || @pie - sections << build_dynamic_section if @shared || @pie + if @shared || @pie + dynstr, dynsym = build_shared_dynamic_sections + rela_dyn = build_rela_dyn_section + sections << dynstr + sections << dynsym + sections << rela_dyn + rela_dyn.header.set!(link: sections.index(dynsym)) + sections << build_dynamic_section + end sections << symtab_section rel_sections.each { |s| sections << s.dup } @@ -303,6 +310,17 @@ def build_dynamic_section dynamic_section end + def build_rela_dyn_section + rela_dyn_section = Caotral::Binary::ELF::Section.new( + body: [], + section_name: ".rela.dyn", + header: Caotral::Binary::ELF::SectionHeader.new + ) + + rela_dyn_section.header.set!(type: SHT[:rela], entsize: 24, addralign: 8, info: 0) + rela_dyn_section + end + def ref_index(sections, section_name) raise Caotral::Binary::ELF::Error, "invalid section name: #{section_name}" if section_name.nil? ref_names = "." + section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.join(".") diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 4f5c50e..db5a562 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -7,7 +7,7 @@ class Writer R_X86_64_PC32 = 2 R_X86_64_PLT32 = 4 ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze - RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze + RELOCATION_SECTION_NAMES = [".rela.text", ".rela.dyn"].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 @@ -142,7 +142,7 @@ def write_shared_dynamic_sections(file:) size = file.pos - interp_offset interp_section.header.set!(offset: interp_offset, size:) end - + dynstr_offset = file.pos file.write(dynstr_section.body.build) size = file.pos - dynstr_offset @@ -160,6 +160,7 @@ def write_shared_dynamic_sections(file:) end def ref_index(section_name) + return 0 if section_name == ".rela.dyn" ref_name = section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" } ref_name = "." + ref_name.join(".") ref = @write_sections.find { |s| s.section_name == ref_name } @@ -182,7 +183,7 @@ def program_header_flags(flag) = Caotral::Binary::ELF::ProgramHeader::PF[flag.to def elf_type = Caotral::Binary::ELF::Header::TYPE[@shared || @pie ? :DYN : :EXEC] def text_section = @text_section ||= @write_sections.find { |s| ".text" === s.section_name.to_s } - def rel_sections = @rel_sections ||= @write_sections.select { RELOCATION_SECTION_NAMES.include?(it.section_name) } + def rel_sections = @rel_sections ||= @write_sections.select { |s| RELOCATION_SECTION_NAMES.include?(s.section_name.to_s) } 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 } @@ -190,6 +191,7 @@ def dynstr_section = @dynstr_section ||= @write_sections.find { |s| ".dynstr" == def dynsym_section = @dynsym_section ||= @write_sections.find { |s| ".dynsym" === s.section_name.to_s } def dynamic_section = @dynamic_section ||= @write_sections.find { |s| ".dynamic" === s.section_name.to_s } def interp_section = @interp_section ||= @write_sections.find { |s| ".interp" === s.section_name.to_s } + def reladyn_section = @reladyn_section ||= @write_sections.find { |s| ".rela.dyn" === s.section_name.to_s } end end end From 6d63bd861da00b6a0bf5918dea934ed3b62024c4 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sun, 1 Feb 2026 16:46:46 +0900 Subject: [PATCH 07/15] patch dynamic rela info --- lib/caotral/linker/builder.rb | 12 ++++++++++-- lib/caotral/linker/writer.rb | 22 ++++++++++++++++++---- test/caotral/linker/pie-object_test.rb | 4 ++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 8568c4b..56b6eab 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -154,7 +154,7 @@ def build ) sections += build_pie_sections if @pie - if @shared || @pie + if dynamic? dynstr, dynsym = build_shared_dynamic_sections rela_dyn = build_rela_dyn_section sections << dynstr @@ -300,8 +300,14 @@ def build_pie_sections end def build_dynamic_section + tag_types = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES dynamic_section = Caotral::Binary::ELF::Section.new( - body: [Caotral::Binary::ELF::Section::Dynamic.new], + body: [ + Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:RELA], un: 0), + Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:RELASZ], un: 0), + Caotral::Binary::ELF::Section::Dynamic.new.set!(tag: tag_types[:RELAENT], un: 24), + Caotral::Binary::ELF::Section::Dynamic.new + ], section_name: ".dynamic", header: Caotral::Binary::ELF::SectionHeader.new ) @@ -334,6 +340,8 @@ def _mode! @executable = false if @shared raise Caotral::Binary::ELF::Error, "disallow both mode: shared and PIE" if @pie && @shared end + + def dynamic? = @shared || @pie end end end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index db5a562..1295915 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -37,7 +37,7 @@ def write type, flags = 1, program_header_flags(:RX) filesz = text_section.body.bytesize memsz = filesz - entry = (@shared || !@executable) ? 0 : (@entry || vaddr) + entry = non_executable? ? 0 : (@entry || vaddr) header.set!(entry:) lph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:) @@ -47,7 +47,9 @@ def write gap = [text_offset - f.pos, 0].max f.write("\0" * gap) f.write(text_section.body) - write_shared_dynamic_sections(file: f) if @shared || @pie + if dynamic? + write_shared_dynamic_sections(file: f) + end ph_pos = phoffset if iph ish = interp_section.header @@ -81,6 +83,13 @@ def write entsize = rel.body.first&.build&.bytesize.to_i rel.header.set!(offset: rel_offset, size: rel_size, entsize:) end + if dynamic? && dynamic_section + rdsh = rela_dyn_section&.header + rela = dynamic_section.body.find { |dyn| dyn.tag == dynamic_tables[:RELA] } + relasz = dynamic_section.body.find { |dyn| dyn.tag == dynamic_tables[:RELASZ] } + rela&.set!(un: rdsh&.addr.to_i) + relasz&.set!(un: rdsh&.size.to_i) + end offset = f.pos names = @write_sections.map { |s| s.section_name.to_s } if names.last != ".shstrtab" @@ -180,7 +189,12 @@ def link_index(section_name) end def program_header_flags(flag) = Caotral::Binary::ELF::ProgramHeader::PF[flag.to_sym] - def elf_type = Caotral::Binary::ELF::Header::TYPE[@shared || @pie ? :DYN : :EXEC] + def elf_type = Caotral::Binary::ELF::Header::TYPE[dynamic? ? :DYN : :EXEC] + + def non_executable? = (@shared || !@executable) + def dynamic? = (@shared || @pie) + + def dynamic_tables = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES 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) } @@ -191,7 +205,7 @@ def dynstr_section = @dynstr_section ||= @write_sections.find { |s| ".dynstr" == def dynsym_section = @dynsym_section ||= @write_sections.find { |s| ".dynsym" === s.section_name.to_s } def dynamic_section = @dynamic_section ||= @write_sections.find { |s| ".dynamic" === s.section_name.to_s } def interp_section = @interp_section ||= @write_sections.find { |s| ".interp" === s.section_name.to_s } - def reladyn_section = @reladyn_section ||= @write_sections.find { |s| ".rela.dyn" === s.section_name.to_s } + def rela_dyn_section = @rela_dyn_section ||= @write_sections.find { |s| ".rela.dyn" === s.section_name.to_s } end end end diff --git a/test/caotral/linker/pie-object_test.rb b/test/caotral/linker/pie-object_test.rb index 582d661..fe2ca6e 100644 --- a/test/caotral/linker/pie-object_test.rb +++ b/test/caotral/linker/pie-object_test.rb @@ -20,7 +20,7 @@ def test_non_executable_pie_object section_names = elf.sections.map(&:section_name) program_header_types = elf.program_headers.map(&:type) interp = elf.find_by_name(".interp") - dynamic = elf.find_by_name(".dynamic").body.first + dynamic = elf.find_by_name(".dynamic").body.last assert_include(program_header_types, :LOAD) assert_include(program_header_types, :DYNAMIC) assert_include(program_header_types, :INTERP) @@ -41,7 +41,7 @@ def test_executable_pie_object section_names = elf.sections.map(&:section_name) program_header_types = elf.program_headers.map(&:type) interp = elf.find_by_name(".interp") - dynamic = elf.find_by_name(".dynamic").body.first + dynamic = elf.find_by_name(".dynamic").body.last assert_include(program_header_types, :LOAD) assert_include(program_header_types, :DYNAMIC) assert_include(program_header_types, :INTERP) From 2c690c166414e57765e979fcb9a65ebd53896619 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Mon, 2 Feb 2026 23:52:36 +0900 Subject: [PATCH 08/15] collect relative relocations for pie --- lib/caotral/binary/elf/section/rel.rb | 12 ++++++++++++ lib/caotral/linker/builder.rb | 28 +++++++++++++-------------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/lib/caotral/binary/elf/section/rel.rb b/lib/caotral/binary/elf/section/rel.rb index 5e17fe2..7aab386 100644 --- a/lib/caotral/binary/elf/section/rel.rb +++ b/lib/caotral/binary/elf/section/rel.rb @@ -5,6 +5,17 @@ class ELF class Section class Rel include Caotral::Binary::ELF::Utils + TYPES = { + AMD64_NONE: 0, + AMD64_64: 1, + AMD64_PC32: 2, + AMD64_GOT32: 3, + AMD64_PLT32: 4, + AMD64_COPY: 5, + AMD64_GLOB_DAT: 6, + }.freeze + TYPES_BY_V = TYPES.invert.freeze + def initialize(addend: true) @offset = num2bytes(0, 8) @info = num2bytes(0, 8) @@ -27,6 +38,7 @@ def addend end def sym = @info.pack("C*").unpack1("Q<") >> 32 def type = @info.pack("C*").unpack1("Q<") & 0xffffffff + def type_name = TYPES_BY_V[type] def addend? = !!@addend private def bytes = addend? ? [@offset, @info, @addend] : [@offset, @info] diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 56b6eab..4145024 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -5,8 +5,10 @@ module Caotral class Linker class Builder include Caotral::Binary::ELF::Utils + R_X86_64_64 = 1 R_X86_64_PC32 = 2 R_X86_64_PLT32 = 4 + R_X86_64_RELATIVE = 8 SYMTAB_BIND = { locals: 0, globals: 1, weaks: 2, }.freeze BIND_BY_VALUE = SYMTAB_BIND.invert.freeze RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze @@ -52,6 +54,11 @@ def build section_name: ".shstrtab", header: Caotral::Binary::ELF::SectionHeader.new ) + rela_dyn_section = Caotral::Binary::ELF::Section.new( + body: [], + section_name: ".rela.dyn", + header: Caotral::Binary::ELF::SectionHeader.new + ) start_bytes = [0xe8, *[0] * 4, 0x48, 0x89, 0xc7, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05] exec_text_offset = 0x1000 @@ -101,6 +108,10 @@ def build ) section.body.each do |rel| offset = rel.offset + text_offsets.fetch(elf_obj.object_id, 0) + if rel.type == R_X86_64_64 + rela_dyn_section.body << Caotral::Binary::ELF::Section::Rel.new.set!(offset:, info: (0 << 32) | R_X86_64_RELATIVE, addend: rel.addend? ? rel.addend : 0) + next + end 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 @@ -156,11 +167,11 @@ def build sections += build_pie_sections if @pie if dynamic? dynstr, dynsym = build_shared_dynamic_sections - rela_dyn = build_rela_dyn_section sections << dynstr sections << dynsym - sections << rela_dyn - rela_dyn.header.set!(link: sections.index(dynsym)) + sections << rela_dyn_section + sym = sections.index(dynsym) + rela_dyn_section.header.set!(link: sym, type: rel_type(rela_dyn_section), info: 0, addralign: 8, entsize: rel_entsize(rela_dyn_section)) sections << build_dynamic_section end sections << symtab_section @@ -316,17 +327,6 @@ def build_dynamic_section dynamic_section end - def build_rela_dyn_section - rela_dyn_section = Caotral::Binary::ELF::Section.new( - body: [], - section_name: ".rela.dyn", - header: Caotral::Binary::ELF::SectionHeader.new - ) - - rela_dyn_section.header.set!(type: SHT[:rela], entsize: 24, addralign: 8, info: 0) - rela_dyn_section - end - def ref_index(sections, section_name) raise Caotral::Binary::ELF::Error, "invalid section name: #{section_name}" if section_name.nil? ref_names = "." + section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.join(".") From 2396ebe86f9c330eeec11e6232ee88b80d09d5a9 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 7 Feb 2026 14:18:39 +0900 Subject: [PATCH 09/15] emit rela.dyn entries for pie --- lib/caotral/linker/writer.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 1295915..1ff035e 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -76,6 +76,10 @@ def write f.write(strtab_section.body.build) strtab_section.header.set!(offset: strtab_offset, size: strtab_section.body.names.bytesize) + if rela_dyn_section + rela_dyn_section.body.each { |entry| entry.set!(offset: entry.offset + text_section.header.addr) } + end + rel_sections.each do |rel| rel_offset = f.pos rel.body.each { |entry| f.write(entry.build) } @@ -83,7 +87,7 @@ def write entsize = rel.body.first&.build&.bytesize.to_i rel.header.set!(offset: rel_offset, size: rel_size, entsize:) end - if dynamic? && dynamic_section + if dynamic? && dynamic_section && rela_dyn_section rdsh = rela_dyn_section&.header rela = dynamic_section.body.find { |dyn| dyn.tag == dynamic_tables[:RELA] } relasz = dynamic_section.body.find { |dyn| dyn.tag == dynamic_tables[:RELASZ] } From 567fc79dae941936f7aa0fd5145f609343ebcfa0 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 7 Feb 2026 17:18:58 +0900 Subject: [PATCH 10/15] write dynamic reloacations --- lib/caotral/linker/writer.rb | 33 +++++++++++++-- test/caotral/linker/pie-object_test.rb | 56 +++++++++++++++++++++++++- 2 files changed, 83 insertions(+), 6 deletions(-) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 1ff035e..24450ef 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -87,13 +87,35 @@ def write entsize = rel.body.first&.build&.bytesize.to_i rel.header.set!(offset: rel_offset, size: rel_size, entsize:) end + + dynamic_sections.each do |dyn| + addr = text_section.header.addr + (dyn.header.offset - text_section.header.offset) + dyn.header.set!(addr:) + end + if dynamic? && dynamic_section && rela_dyn_section rdsh = rela_dyn_section&.header rela = dynamic_section.body.find { |dyn| dyn.tag == dynamic_tables[:RELA] } relasz = dynamic_section.body.find { |dyn| dyn.tag == dynamic_tables[:RELASZ] } rela&.set!(un: rdsh&.addr.to_i) relasz&.set!(un: rdsh&.size.to_i) + + segment_start = text_section.header.offset + segment_end = [text_section,].concat(dynamic_sections).compact.map { |s| s.header.offset + s.header.size }.max + + dynamic_filesz = segment_end - segment_start + cur = f.pos + lph.set!(filesz: dynamic_filesz, memsz: dynamic_filesz) + f.seek(phoffset) + f.write(lph.build) + f.seek(cur) + + cur = f.pos + f.seek(dynamic_section.header.offset) + dynamic_section.body.each { |dyn| f.write(dyn.build) } + f.seek(cur) end + offset = f.pos names = @write_sections.map { |s| s.section_name.to_s } if names.last != ".shstrtab" @@ -149,27 +171,29 @@ def write_order_sections def write_section_index(section_name) = @write_sections.index { it.section_name == section_name } def write_shared_dynamic_sections(file:) + tsh = text_section&.header + text_addr = tsh&.addr || 0 if interp_section interp_offset = file.pos file.write(interp_section.body) size = file.pos - interp_offset - interp_section.header.set!(offset: interp_offset, size:) + interp_section.header.set!(offset: interp_offset, size:, addr: text_addr + (interp_offset - tsh.offset)) end dynstr_offset = file.pos file.write(dynstr_section.body.build) size = file.pos - dynstr_offset - dynstr_section.header.set!(offset: dynstr_offset, size:) + dynstr_section.header.set!(offset: dynstr_offset, size:, addr: text_addr + (dynstr_offset - tsh.offset)) dynsym_offset = file.pos dynsym_section.body.each { |dynsym| file.write(dynsym.build) } size = file.pos - dynsym_offset - dynsym_section.header.set!(offset: dynsym_offset, size:) + dynsym_section.header.set!(offset: dynsym_offset, size:, addr: text_addr + (dynsym_offset - tsh.offset)) dynamic_offset = file.pos dynamic_section.body.each { |dynamic| file.write(dynamic.build) } size = file.pos - dynamic_offset - dynamic_section.header.set!(offset: dynamic_offset, size:) + dynamic_section.header.set!(offset: dynamic_offset, size:, addr: text_addr + (dynamic_offset - tsh.offset)) end def ref_index(section_name) @@ -210,6 +234,7 @@ def dynsym_section = @dynsym_section ||= @write_sections.find { |s| ".dynsym" == def dynamic_section = @dynamic_section ||= @write_sections.find { |s| ".dynamic" === s.section_name.to_s } 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 dynamic_sections = @dynamic_sections ||= [interp_section, dynstr_section, dynsym_section, dynamic_section, rela_dyn_section].compact end end end diff --git a/test/caotral/linker/pie-object_test.rb b/test/caotral/linker/pie-object_test.rb index fe2ca6e..64f7897 100644 --- a/test/caotral/linker/pie-object_test.rb +++ b/test/caotral/linker/pie-object_test.rb @@ -5,8 +5,6 @@ class Caotral::Linker::PIEObjectLinkingTest < Test::Unit::TestCase def setup @inputs = ["pie.o"] @output = "pie" - path = Pathname.new("sample/C/pie-object.c").to_s - IO.popen(["gcc", "-fPIE", "-c", "-o", @inputs.first, "%s" % path]).close end def teardown @@ -15,6 +13,9 @@ def teardown end def test_non_executable_pie_object + path = Pathname.new("sample/C/pie-object.c").to_s + IO.popen(["gcc", "-fPIE", "-c", "-o", @inputs.first, "%s" % path]).close + Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", executable: false, pie: true) elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) section_names = elf.sections.map(&:section_name) @@ -36,6 +37,57 @@ def test_non_executable_pie_object end def test_executable_pie_object + path = Pathname.new("sample/C/pie-object.c").to_s + IO.popen(["gcc", "-fPIE", "-c", "-o", @inputs.first, "%s" % path]).close + + Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", pie: true) + elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) + section_names = elf.sections.map(&:section_name) + program_header_types = elf.program_headers.map(&:type) + interp = elf.find_by_name(".interp") + dynamic = elf.find_by_name(".dynamic").body.last + assert_include(program_header_types, :LOAD) + assert_include(program_header_types, :DYNAMIC) + assert_include(program_header_types, :INTERP) + assert_equal(interp.body.split("\0").first, "/lib64/ld-linux-x86-64.so.2") + assert_equal(dynamic.null?, true) + assert_include(section_names, ".dynstr") + assert_include(section_names, ".dynsym") + assert_include(section_names, ".dynamic") + assert_include(section_names, ".interp") + assert_equal(:DYN, elf.header.type) + assert_equal(:AMD64, elf.header.arch) + assert_not_equal(0, elf.header.entry) + end + + def test_movabs_pie_object + path = Pathname.new("sample/C/movabs.c").to_s + IO.popen(["gcc", "-fno-pic", "-fno-plt", "-c", "-o", @inputs.first, "%s" % path]).close + + Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", executable: false, pie: true) + elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) + section_names = elf.sections.map(&:section_name) + program_header_types = elf.program_headers.map(&:type) + interp = elf.find_by_name(".interp") + dynamic = elf.find_by_name(".dynamic").body.last + assert_include(program_header_types, :LOAD) + assert_include(program_header_types, :DYNAMIC) + assert_include(program_header_types, :INTERP) + assert_equal(interp.body.split("\0").first, "/lib64/ld-linux-x86-64.so.2") + assert_equal(dynamic.null?, true) + assert_include(section_names, ".dynstr") + assert_include(section_names, ".dynsym") + assert_include(section_names, ".dynamic") + assert_include(section_names, ".interp") + assert_equal(:DYN, elf.header.type) + assert_equal(:AMD64, elf.header.arch) + assert_equal(0, elf.header.entry) + end + + def test_executable_movabs_pie_object + path = Pathname.new("sample/C/movabs.c").to_s + IO.popen(["gcc", "-fno-pic", "-fno-plt", "-c", "-o", @inputs.first, "%s" % path]).close + Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", pie: true) elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) section_names = elf.sections.map(&:section_name) From e916262e9ff68364ea659adb40c0492aff6c5c17 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 7 Feb 2026 17:38:23 +0900 Subject: [PATCH 11/15] set alloc flags for dynamic sections --- lib/caotral/binary/elf/section_header.rb | 5 +++++ lib/caotral/linker/builder.rb | 11 ++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/caotral/binary/elf/section_header.rb b/lib/caotral/binary/elf/section_header.rb index 44ca070..20b3da7 100644 --- a/lib/caotral/binary/elf/section_header.rb +++ b/lib/caotral/binary/elf/section_header.rb @@ -6,6 +6,11 @@ class ELF class SectionHeader include Caotral::Binary::ELF::Utils SHT = { null: 0, progbits: 1, symtab: 2, strtab: 3, rela: 4, hash: 5, dynamic: 6, note: 7, nobits: 8, rel: 9, shlib: 10, dynsym: 11, }.freeze + SHF = { + WRITE: 0x1, + ALLOC: 0x2, + EXECINSTR: 0x4, + }.freeze SHT_BY_VALUE = SHT.invert.freeze def initialize diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 4145024..d95ca69 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -15,6 +15,7 @@ class Builder ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze GENERATED_SECTION_NAMES = [".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic", ".interp", ".rela.dyn"].freeze SHT = Caotral::Binary::ELF::SectionHeader::SHT + SHF = Caotral::Binary::ELF::SectionHeader::SHF attr_reader :symbols @@ -171,7 +172,7 @@ def build sections << dynsym sections << rela_dyn_section sym = sections.index(dynsym) - rela_dyn_section.header.set!(link: sym, type: rel_type(rela_dyn_section), info: 0, addralign: 8, entsize: rel_entsize(rela_dyn_section)) + rela_dyn_section.header.set!(flags: SHF[:ALLOC], link: sym, type: rel_type(rela_dyn_section), info: 0, addralign: 8, entsize: rel_entsize(rela_dyn_section)) sections << build_dynamic_section end sections << symtab_section @@ -286,7 +287,7 @@ def build_shared_dynamic_sections header: Caotral::Binary::ELF::SectionHeader.new ) - dynstr_section.header.set!(type: SHT[:strtab], flags: 0, addralign: 1, entsize: 0) + dynstr_section.header.set!(type: SHT[:strtab], flags: SHF[:ALLOC], addralign: 1, entsize: 0) dynsym_section = Caotral::Binary::ELF::Section.new( body: [Caotral::Binary::ELF::Section::Symtab.new], @@ -294,7 +295,7 @@ def build_shared_dynamic_sections header: Caotral::Binary::ELF::SectionHeader.new ) - dynsym_section.header.set!(type: SHT[:dynsym], flags: 0, addralign: 8, entsize: 24) + dynsym_section.header.set!(info: 1, type: SHT[:dynsym], flags: SHF[:ALLOC], addralign: 8, entsize: 24) [dynstr_section, dynsym_section,] end @@ -306,7 +307,7 @@ def build_pie_sections header: Caotral::Binary::ELF::SectionHeader.new ) - interp_section.header.set!(type: SHT[:progbits], addralign: 1, flags: 0, entsize: 0) + interp_section.header.set!(type: SHT[:progbits], addralign: 1, flags: SHF[:ALLOC], entsize: 0) [interp_section] end @@ -323,7 +324,7 @@ def build_dynamic_section header: Caotral::Binary::ELF::SectionHeader.new ) - dynamic_section.header.set!(type: SHT[:dynamic], flags: 0, addralign: 8, entsize: 16) + dynamic_section.header.set!(type: SHT[:dynamic], flags: SHF[:ALLOC] | SHF[:WRITE], addralign: 8, entsize: 16) dynamic_section end From 6d6b7db4785d922ed9fa11144f2afa88e0b5a148 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sun, 8 Feb 2026 10:47:48 +0900 Subject: [PATCH 12/15] add DT_HASH and hash section for pie --- lib/caotral/binary/elf.rb | 1 + lib/caotral/binary/elf/section/dynamic.rb | 1 + lib/caotral/binary/elf/section/hash.rb | 20 ++++++++++++++++++++ lib/caotral/linker/writer.rb | 17 +++++++++++++---- 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 lib/caotral/binary/elf/section/hash.rb diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index a79b222..172b522 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -4,6 +4,7 @@ require_relative "elf/program_header" require_relative "elf/section" require_relative "elf/section/dynamic" +require_relative "elf/section/hash" require_relative "elf/section/rel" require_relative "elf/section/strtab" require_relative "elf/section/symtab" diff --git a/lib/caotral/binary/elf/section/dynamic.rb b/lib/caotral/binary/elf/section/dynamic.rb index 2f4be5b..616e022 100644 --- a/lib/caotral/binary/elf/section/dynamic.rb +++ b/lib/caotral/binary/elf/section/dynamic.rb @@ -7,6 +7,7 @@ class Dynamic include Caotral::Binary::ELF::Utils TAG_TYPES = { NULL: 0, + HASH: 4, RELA: 7, RELASZ: 8, RELAENT: 9, diff --git a/lib/caotral/binary/elf/section/hash.rb b/lib/caotral/binary/elf/section/hash.rb new file mode 100644 index 0000000..258413d --- /dev/null +++ b/lib/caotral/binary/elf/section/hash.rb @@ -0,0 +1,20 @@ +require "caotral/binary/elf/utils" +module Caotral + module Binary + class ELF + class Section + class Hash + include Caotral::Binary::ELF::Utils + def initialize(nchain:, nbucket: 1) + @nbucket = num2bytes(nbucket, 4) + @nchain = num2bytes(nchain, 4) + @bucket = Array.new(nbucket, num2bytes(0, 4)) + @chain = Array.new(nchain, num2bytes(0, 4)) + end + + private def bytes = [@nbucket, @nchain, *@bucket, *@chain] + end + end + end + end +end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 24450ef..713f243 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -95,10 +95,14 @@ def write if dynamic? && dynamic_section && rela_dyn_section rdsh = rela_dyn_section&.header - rela = dynamic_section.body.find { |dyn| dyn.tag == dynamic_tables[:RELA] } - relasz = dynamic_section.body.find { |dyn| dyn.tag == dynamic_tables[:RELASZ] } - rela&.set!(un: rdsh&.addr.to_i) - relasz&.set!(un: rdsh&.size.to_i) + bodies = dynamic_section.body + 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) + bodies.find { |dyn| dyn.tag == dynamic_tables[:SYMENT] }&.set!(un: dynsym_section.header.entsize.to_i) + bodies.find { |dyn| dyn.tag == dynamic_tables[:STRTAB] }&.set!(un: dynstr_section.header.addr.to_i) + bodies.find { |dyn| dyn.tag == dynamic_tables[:SYMTAB] }&.set!(un: dynsym_section.header.addr.to_i) + bodies.find { |dyn| dyn.tag == dynamic_tables[:HASH] }&.set!(un: hash_section.header.addr.to_i) if hash_section segment_start = text_section.header.offset segment_end = [text_section,].concat(dynamic_sections).compact.map { |s| s.header.offset + s.header.size }.max @@ -161,6 +165,7 @@ def write_order_sections write_order << @elf_obj.find_by_name(".interp") write_order << @elf_obj.find_by_name(".dynstr") write_order << @elf_obj.find_by_name(".dynsym") + write_order << @elf_obj.find_by_name(".hash") write_order << @elf_obj.find_by_name(".dynamic") write_order << @elf_obj.find_by_name(".symtab") write_order << @elf_obj.find_by_name(".strtab") @@ -211,6 +216,8 @@ def link_index(section_name) write_section_index(".strtab") when ".dynsym", ".dynamic" write_section_index(".dynstr") + when ".hash" + write_section_index(".dynsym") else nil end @@ -234,6 +241,8 @@ def dynsym_section = @dynsym_section ||= @write_sections.find { |s| ".dynsym" == def dynamic_section = @dynamic_section ||= @write_sections.find { |s| ".dynamic" === s.section_name.to_s } 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 hash_section = @hash_section ||= @write_sections.find { |s| ".hash" === s.section_name.to_s } + def dynamic_sections = @dynamic_sections ||= [interp_section, dynstr_section, dynsym_section, dynamic_section, rela_dyn_section].compact end end From b78ba323e4f04e7630d183a8e819a3a9254aa5dc Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sun, 8 Feb 2026 10:52:13 +0900 Subject: [PATCH 13/15] fix pie phar and loader layout --- lib/caotral/linker/writer.rb | 46 +++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 713f243..44983a1 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -28,7 +28,10 @@ def write iph = Caotral::Binary::ELF::ProgramHeader.new if interp_section # PT_DYNAMIC dph = Caotral::Binary::ELF::ProgramHeader.new if dynamic_section - phnum = [lph, iph, dph].compact.size + # PT_PHDR + pph = Caotral::Binary::ELF::ProgramHeader.new if @pie + phs = [pph, lph, iph, dph].compact + phnum = phs.size header = @elf_obj.header.set!(type: e_type, phoffset:, phnum:, phsize:, ehsize:) text_offset = text_section.header.offset align = 0x1000 @@ -42,31 +45,44 @@ def write header.set!(entry:) lph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:) f.write(@elf_obj.header.build) - f.write(lph.build) - f.write(iph.build) if iph + 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 gap = [text_offset - f.pos, 0].max f.write("\0" * gap) f.write(text_section.body) if dynamic? write_shared_dynamic_sections(file: f) end - ph_pos = phoffset + if iph ish = interp_section.header iph.set!(type: 3, offset: ish.offset, vaddr: 0, paddr: 0, filesz: ish.size, memsz: ish.size, flags: program_header_flags(:R), align: 1) - cur = f.pos - f.seek(ph_pos += phsize) - f.write(iph.build) - f.seek(cur) end + if dph dsh = dynamic_section.header dph.set!(type: 2, offset: dsh.offset, filesz: dsh.size, memsz: dsh.size, vaddr: dsh.addr || 0, paddr: dsh.addr || 0, flags: program_header_flags(:R), align: dsh.addralign) - cur = f.pos - f.seek(ph_pos += phsize) - f.write(dph.build) - f.seek(cur) end + + cur = f.pos + phs.each_with_index do |ph, idx| + next if ph == lph + f.seek(phoffset + (idx * phsize)) + f.write(ph.build) + end + f.seek(cur) + symtab_offset = f.pos symtab_section.body.each { |sym| f.write(sym.build) } symtab_entsize = symtab_section.body.first&.build&.bytesize.to_i @@ -109,8 +125,10 @@ def write dynamic_filesz = segment_end - segment_start cur = f.pos - lph.set!(filesz: dynamic_filesz, memsz: dynamic_filesz) - f.seek(phoffset) + lphndx = phs.index(lph) + + lph.set!(offset: 0, vaddr: 0, paddr: 0, filesz: dynamic_filesz, memsz: dynamic_filesz) + f.seek(phoffset + phsize * lphndx) f.write(lph.build) f.seek(cur) From 8c3fd035a356b0b32355ebbd00c8f93c76649407 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sun, 8 Feb 2026 10:55:13 +0900 Subject: [PATCH 14/15] support data section and pie dynamic metadata --- lib/caotral/binary/elf/section/dynamic.rb | 5 ++ lib/caotral/linker/builder.rb | 69 +++++++++++++++++++++-- lib/caotral/linker/writer.rb | 29 +++++++--- sample/C/movabs.c | 15 +++++ test/caotral/linker/pie-object_test.rb | 5 ++ test/caotral/linker/writer_test.rb | 2 +- 6 files changed, 111 insertions(+), 14 deletions(-) create mode 100644 sample/C/movabs.c diff --git a/lib/caotral/binary/elf/section/dynamic.rb b/lib/caotral/binary/elf/section/dynamic.rb index 616e022..77f7311 100644 --- a/lib/caotral/binary/elf/section/dynamic.rb +++ b/lib/caotral/binary/elf/section/dynamic.rb @@ -8,9 +8,14 @@ class Dynamic TAG_TYPES = { NULL: 0, HASH: 4, + STRTAB: 5, + SYMTAB: 6, RELA: 7, RELASZ: 8, RELAENT: 9, + STRSZ: 10, + SYMENT: 11, + TEXTREL: 22, }.freeze TAG_TYPES_BY_V = TAG_TYPES.invert.freeze diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index d95ca69..4bbc477 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -11,9 +11,9 @@ class Builder R_X86_64_RELATIVE = 8 SYMTAB_BIND = { locals: 0, globals: 1, weaks: 2, }.freeze BIND_BY_VALUE = SYMTAB_BIND.invert.freeze - RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze + RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text", ".rela.data", ".rel.data"].freeze ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze - GENERATED_SECTION_NAMES = [".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic", ".interp", ".rela.dyn"].freeze + GENERATED_SECTION_NAMES = [".text", ".data", ".strtab", ".symtab", ".shstrtab", /\.rela?\./, ".dynstr", ".dynsym", ".dynamic", ".interp", ".rela.dyn"].freeze SHT = Caotral::Binary::ELF::SectionHeader::SHT SHF = Caotral::Binary::ELF::SectionHeader::SHF @@ -40,6 +40,11 @@ def build section_name: ".text", header: Caotral::Binary::ELF::SectionHeader.new ) + data_section = Caotral::Binary::ELF::Section.new( + body: String.new, + section_name: ".data", + header: Caotral::Binary::ELF::SectionHeader.new + ) strtab_section = Caotral::Binary::ELF::Section.new( body: Caotral::Binary::ELF::Section::Strtab.new("\0".b), section_name: ".strtab", @@ -61,9 +66,12 @@ def build header: Caotral::Binary::ELF::SectionHeader.new ) + data_section.header.set!(type: SHT[:progbits], flags: SHF[:ALLOC] | SHF[:WRITE], addralign: 8) + start_bytes = [0xe8, *[0] * 4, 0x48, 0x89, 0xc7, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05] exec_text_offset = 0x1000 base_addr = 0x400000 + base_addr = 0 if @pie unless @executable start_bytes = [] base_addr = 0 @@ -75,7 +83,9 @@ def build elf.header = elf_obj.header.dup strtab_names = [] text_offsets = {} + data_offsets = {} text_offset = 0 + data_offset = 0 sym_by_elf = Hash.new { |h, k| h[k] = [] } @elf_objs.each do |elf_obj| text = elf_obj.find_by_name(".text") @@ -85,17 +95,27 @@ def build size = text.body.bytesize text_offset += size end + data = elf_obj.find_by_name(".data") + unless data.nil? + data_section.body << data.body + data_offsets[elf_obj.object_id] = data_offset + data_offset += data.body.bytesize + end strtab = elf_obj.find_by_name(".strtab") strtab.body.names.split("\0").each { |name| strtab_names << name } unless strtab.nil? symtab = elf_obj.find_by_name(".symtab") base_index = nil unless symtab.nil? 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? + symtab.body.each_with_index do |st, index| sym = Caotral::Binary::ELF::Section::Symtab.new name, info, other, shndx, value, size = st.build.unpack("L + +extern int foo; + +uintptr_t get_addr(void) { + uintptr_t x; + __asm__ volatile ("movabs $foo, %0" : "=r"(x)); + return x; +} + +int foo = 42; + +int main(void) { + return (int)get_addr(); +} diff --git a/test/caotral/linker/pie-object_test.rb b/test/caotral/linker/pie-object_test.rb index 64f7897..846c46f 100644 --- a/test/caotral/linker/pie-object_test.rb +++ b/test/caotral/linker/pie-object_test.rb @@ -90,6 +90,11 @@ def test_executable_movabs_pie_object Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", pie: true) elf = Caotral::Binary::ELF::Reader.read!(input: @output, debug: false) + IO.popen("./pie").close + exit_code, handle_code = check_process($?.to_i) + assert_equal(60, exit_code) + assert_equal(0, handle_code) + section_names = elf.sections.map(&:section_name) program_header_types = elf.program_headers.map(&:type) interp = elf.find_by_name(".interp") diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index 0053b78..31bb155 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 5, read_written_elf.sections.size + assert_equal 6, read_written_elf.sections.size assert_equal 0x401000, read_written_elf.header.entry end From 563e0da58175efc4956ab8ba55757de6d5848f33 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Mon, 9 Feb 2026 01:04:53 +0900 Subject: [PATCH 15/15] add sig for pie implemted classes --- sig/caotral/assembler.rbs | 1 + sig/caotral/binary/elf/section/hash.rbs | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 sig/caotral/binary/elf/section/hash.rbs diff --git a/sig/caotral/assembler.rbs b/sig/caotral/assembler.rbs index e5ae9d2..29fc5f0 100644 --- a/sig/caotral/assembler.rbs +++ b/sig/caotral/assembler.rbs @@ -5,6 +5,7 @@ class Caotral::Assembler @asm_reader: Caotral::Assembler::Reader @debug: bool + def self.assemble!: (input: String, ?output: String, ?assembler: String, ?assembler_options: Array[String] | [], ?debug: bool, ?shared: bool) -> String def initialize: (input: String, ?output: String, ?assembler: String, ?type: Symbol, ?debug: bool) -> void def assemble: (?assembler: String, ?assembler_options: Array[String] | [], ?input: String, ?output: String, ?debug: bool, ?shared: bool) -> String def obj_file: () -> String diff --git a/sig/caotral/binary/elf/section/hash.rbs b/sig/caotral/binary/elf/section/hash.rbs new file mode 100644 index 0000000..09c8343 --- /dev/null +++ b/sig/caotral/binary/elf/section/hash.rbs @@ -0,0 +1,4 @@ +class Caotral::Binary::ELF::Section::Hash + def initialize: (nchain: Integer, ?nbucket: Integer) -> void + def build: () -> String +end