Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/caotral/linker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def to_elf(inputs: @inputs, output: @output, debug: @debug, shared: @shared, exe
Caotral::Linker::Layout.new(elf:, shared:, executable:, pie:).apply!
elf_obj = Caotral::Linker::Finalizer.new(elf:, metadata:, shared:, executable:, pie:, debug:).apply!

Caotral::Linker::Writer.new(elf_obj:, output:, debug:, shared:, executable:, pie:).write
Caotral::Linker::Writer.new(elf_obj:, output:).write
File.chmod(0755, output) if executable
output
end
Expand Down
16 changes: 11 additions & 5 deletions lib/caotral/linker/layout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,15 @@ 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, vaddr, paddr, align = 64, 64, 64, 8
offset, align = 64, 8
vaddr = load_bias + offset
paddr = vaddr
filesz = @elf.program_headers.size * 56
memsz = filesz
flags = Caotral::Binary::ELF::ProgramHeader::PF[:R]
Expand All @@ -91,13 +96,13 @@ def finalize_program_headers!
next unless text

allocated_sections = @elf.sections.select { |s| s.header.allocated? }
segment_start = @pie ? 0 : text.header.offset
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

offset = segment_start
vaddr = @pie ? 0 : text.header.addr
vaddr = load_bias + offset
paddr = vaddr
filesz = segment_end - segment_start
memsz = filesz
Expand All @@ -106,7 +111,8 @@ def finalize_program_headers!
when :INTERP
interp = @elf.find_by_name(".interp")
raise Caotral::Linker::Error, "Missing .interp section" unless interp
offset, vaddr, paddr, align = interp.header.offset, 0, 0, 1
header = interp.header
offset, vaddr, paddr, align = header.offset, header.addr, header.addr, 1
filesz = interp.header.size
memsz = filesz
flags = Caotral::Binary::ELF::ProgramHeader::PF[:R]
Expand All @@ -117,7 +123,7 @@ 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[:R]
flags = Caotral::Binary::ELF::ProgramHeader::PF[:RW]
when :GNU_STACK
offset, vaddr, paddr, align = 0, 0, 0, 0
filesz = 0
Expand Down
84 changes: 12 additions & 72 deletions lib/caotral/linker/writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,93 +3,33 @@
module Caotral
class Linker
class Writer
include Caotral::Binary::ELF::Utils
RELOCATION_SECTION_NAMES = [".rela.text", ".rela.dyn", ".rela.data", ".rela.plt"].freeze
attr_reader :elf_obj, :output, :debug, :program_headers
def self.write!(elf_obj:, output:, debug: false, executable: true, shared: false)
new(elf_obj:, output:, debug:, shared:, executable:).write
end

def initialize(elf_obj:, output:, debug: false, executable: true, shared: false, pie: false)
@elf_obj, @output, @debug, @executable, @shared, @pie = elf_obj, output, debug, executable, shared, pie
@program_headers = elf_obj.program_headers
@write_sections = elf_obj.sections
end
def self.write!(elf_obj:, output:) = new(elf_obj:, output:).write
def initialize(elf_obj:, output:) = (@elf_obj, @output = elf_obj, output)

def write
f = File.open(@output, "wb")

f.write(@elf_obj.header.build)
program_headers.each { |ph| f.write(ph.build) }
File.open(@output, "wb") do |file|

write_elf_sections(file: f)

# relocation
rel_sections.each { |rel| write_section(file: f, section: rel) }
write_section(file: f, section: shstrtab_section)
shoffset = @elf_obj.header.shoffset
f.seek(shoffset)
write_section_headers(file: f)
output
ensure
f.close if f
end

private
def write_elf_sections(file:)
write_section(file:, section: text_section)
file.write(@elf_obj.header.build)
@elf_obj.program_headers.each { |ph| file.write(ph.build) }

write_section(file:, section: rodata_section) if rodata_section
write_section(file:, section: data_section) if data_section
write_elf_sections(file:)

if plt_section
write_section(file:, section: plt_section)
raise Caotral::Binary::ELF::Error, "missing .got.plt for .plt" if got_plt_section.nil?
write_section(file:, section: got_plt_section)
file.seek(@elf_obj.header.shoffset)
write_section_headers(file:)
end

write_shared_dynamic_sections(file:) if dynamic?
write_section(file:, section: symtab_section)
write_section(file:, section: strtab_section)
end

def write_shared_dynamic_sections(file:)
write_section(file:, section: interp_section) if interp_section
write_section(file:, section: dynstr_section) if dynstr_section
write_section(file:, section: dynsym_section) if dynsym_section
write_section(file:, section: hash_section) if dynamic?
write_section(file:, section: dynamic_section) if dynamic_section
@output
end

def write_section_headers(file:)
@write_sections.each { |section| file.write(section.header.build) }
end
private
def write_elf_sections(file:) = @elf_obj.sections.each { |section| write_section(file:, section:) }
def write_section_headers(file:) = @elf_obj.sections.each { |section| file.write(section.header.build) }

def write_section(file:, section:)
return unless section

file.seek(section.header.offset)
file.write(section.build)
end

def dynamic? = (@shared || @pie)

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) }
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 }
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 }
def rela_dyn_section = @rela_dyn_section ||= @write_sections.find { |s| ".rela.dyn" === s.section_name.to_s }
def data_section = @data_section ||= @write_sections.find { |s| ".data" === s.section_name.to_s }
def rodata_section = @rodata_section ||= @write_sections.find { |s| ".rodata" === s.section_name.to_s }
def hash_section = @hash_section ||= @write_sections.find { |s| ".hash" === s.section_name.to_s }
def plt_section = @plt_section ||= @write_sections.find { |s| ".plt" === s.section_name.to_s }
def got_plt_section = @got_plt_section ||= @write_sections.find { |s| ".got.plt" === s.section_name.to_s }
def rela_plt_section = @rela_plt_section ||= @write_sections.find { |s| ".rela.plt" === s.section_name.to_s }
end
end
end
15 changes: 2 additions & 13 deletions sig/caotral/linker/writer.rbs
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
class Caotral::Linker::Writer
@elf_obj: Caotral::Binary::ELF
@output: String
@debug: bool
@executable: bool
@shared: bool
@pie: bool
@program_headers: Array[Caotral::Binary::ELF::ProgramHeader]
@write_sections: Array[Caotral::Binary::ELF::Section]

attr_reader elf_obj: Caotral::Binary::ELF
attr_reader output: String
attr_reader debug: bool
attr_reader program_headers: Array[Caotral::Binary::ELF::ProgramHeader]

def self.write!: (elf_obj: Caotral::Binary::ELF, output: String, ?debug: bool, ?executable: bool, ?shared: bool) -> String
def initialize: (elf_obj: Caotral::Binary::ELF, output: String, ?debug: bool, ?executable: bool, ?shared: bool, ?pie: bool) -> void
def self.write!: (elf_obj: Caotral::Binary::ELF, output: String) -> String
def initialize: (elf_obj: Caotral::Binary::ELF, output: String) -> void
def write: () -> String
end
4 changes: 2 additions & 2 deletions test/caotral/linker/integration/needed_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def test_needed_in_executable
Caotral::Linker.link!(inputs: [@input], output: @output, linker: "self", pie: true, needed: ["libc.so.6"], executable: true)

IO.popen(["./#{@output}"]).close
pid, exit_code = check_process($?.to_i)
_pid, exit_code = check_process($?.to_i)
assert_equal(0, exit_code)
end

Expand Down Expand Up @@ -91,7 +91,7 @@ def test_start_in_executable
io = IO.popen(["./#{@output}"])
stdout = io.read
io.close
pid, exit_code = check_process($?.to_i)
_pid, exit_code = check_process($?.to_i)
assert_equal("hello, world!!!\n", stdout)
assert_equal(0, exit_code)
end
Expand Down
4 changes: 2 additions & 2 deletions test/caotral/linker/integration/symbol_remap_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def test_remap_external_symbols
assert_not_include(dynsym_names, "symbol_remap_external_b.c")
assert_empty(elf.find_by_name(".rela.plt").body)
_o, _e, status = Open3.capture3("./#{execute}")
ec, hc = check_process(status.to_i)
ec, _hc = check_process(status.to_i)
assert_equal(ec, 42)
end

Expand All @@ -44,7 +44,7 @@ def test_remap_external_symbols_using_glibc
assert_include(dynsym_names, "value_from_b")
assert_include(dynsym_names, "puts")
_o, _e, status = Open3.capture3("./#{execute}")
ec, hc = check_process(status.to_i)
ec, _hc = check_process(status.to_i)
assert_equal(ec, 42)
end
end
2 changes: 1 addition & 1 deletion test/caotral/linker/layout_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def test_layout_with_dynamic
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(:R, elf.program_headers[1].flags)
assert_equal(:RW, elf.program_headers[1].flags)
end

def test_layout_sections
Expand Down
6 changes: 3 additions & 3 deletions test/caotral/linker/writer_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ def teardown
File.delete("relocated_exec") if File.exist?("relocated_exec")
end
def test_write
written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "write.o", debug: false)
written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "write.o")
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 7, read_written_elf.sections.size
assert_equal 0x401000, read_written_elf.header.entry
end

def test_execute_written
Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "write", debug: false)
Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "write")
File.chmod(0755, "./write")
IO.popen("./write").close
exit_code, handle_code = check_process($?.to_i)
Expand All @@ -44,7 +44,7 @@ def test_relocation_write_and_execute
metadata = builder.linker_metadata
Caotral::Linker::Layout.new(elf:, shared: false, pie: false, executable: true).apply!
Caotral::Linker::Finalizer.new(elf:, metadata:, shared: false, pie: false, executable: true, debug: false).apply!
Caotral::Linker::Writer.write!(elf_obj: elf, output: "relocated_exec", debug: false)
Caotral::Linker::Writer.write!(elf_obj: elf, output: "relocated_exec")
File.chmod(0755, "./relocated_exec")
IO.popen("./relocated_exec").close
exit_code, _handle_code = check_process($?.to_i)
Expand Down
Loading