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
1 change: 1 addition & 0 deletions lib/caotral/binary/elf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require_relative "elf/header"
require_relative "elf/program_header"
require_relative "elf/section"
require_relative "elf/section/dynamic"
require_relative "elf/section/rel"
require_relative "elf/section/strtab"
require_relative "elf/section/symtab"
Expand Down
31 changes: 31 additions & 0 deletions lib/caotral/binary/elf/section/dynamic.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
require "caotral/binary/elf/utils"
module Caotral
module Binary
class ELF
class Section
class Dynamic
include Caotral::Binary::ELF::Utils
TAG_TYPES = { NULL: 0 }.freeze

def initialize
@tag = num2bytes(0, 8)
# d_un is a C union. Depending on d_tag, interpret this 8-byte field as either
# a pointer (d_ptr) or an integer value (d_val).
@un = num2bytes(0, 8)
end

def set!(tag: nil, un: nil)
@tag = num2bytes(tag, 8) if check(tag, 8)
@un = num2bytes(un, 8) if check(un, 8)
self
end

def tag = @tag.pack("C*").unpack1("Q<")
def null? = tag == TAG_TYPES[:NULL]

private def bytes = [@tag, @un]
end
end
end
end
end
34 changes: 33 additions & 1 deletion lib/caotral/linker/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +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"].freeze

attr_reader :symbols

Expand Down Expand Up @@ -50,6 +51,7 @@ def build
section_name: ".shstrtab",
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
base_addr = 0x400000
Expand Down Expand Up @@ -150,6 +152,7 @@ def build
entsize: 24
)

sections += build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, text_section:) if @shared
sections << symtab_section

rel_sections.each { |s| sections << s.dup }
Expand All @@ -161,7 +164,7 @@ def build
entsize: 0
)

@elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./]).each do |section|
@elf_objs.first.without_sections(GENERATED_SECTION_NAMES).each do |section|
sections << section.dup
end

Expand Down Expand Up @@ -254,6 +257,35 @@ def resolve_symbols
end

private
def build_shared_dynamic_sections(sections:, symtab_section:, strtab_section:, text_section:)
sht = Caotral::Binary::ELF::SectionHeader::SHT
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)

dynsym_section = Caotral::Binary::ELF::Section.new(
body: [Caotral::Binary::ELF::Section::Symtab.new],
section_name: ".dynsym",
header: Caotral::Binary::ELF::SectionHeader.new
)

dynsym_section.header.set!(type: sht[:dynsym], flags: 0, addralign: 8, entsize: 24)

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]
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(".")
Expand Down
41 changes: 38 additions & 3 deletions lib/caotral/linker/writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ 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
symtab_offset = f.pos
symtab_section.body.each { |sym| f.write(sym.build) }
symtab_entsize = symtab_section.body.first&.build&.bytesize.to_i
Expand All @@ -67,7 +68,6 @@ def write
f.write(shstrtab_section.body.names)
shoffset = f.pos
shstrndx = write_section_index(".shstrtab")
strtabndx = write_section_index(".strtab")
symtabndx = write_section_index(".symtab")
shnum = @write_sections.size
@elf_obj.header.set!(shoffset:, shnum:, shstrndx:)
Expand All @@ -78,8 +78,8 @@ def write
lookup_name = section.section_name
name_offset = names.offset_of(lookup_name)
name, info, entsize = (name_offset.nil? ? 0 : name_offset), header.info, header.entsize
link = header.link
link = strtabndx if section.section_name == ".symtab"
link = link_index(section.section_name)
link = header.link if link.nil?
if [:rel, :rela].include?(header.type)
link = symtabndx
info = ref_index(section.section_name)
Expand All @@ -100,13 +100,34 @@ 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(".dynstr")
write_order << @elf_obj.find_by_name(".dynsym")
write_order << @elf_obj.find_by_name(".dynamic")
write_order << @elf_obj.find_by_name(".symtab")
write_order << @elf_obj.find_by_name(".strtab")
write_order.concat(@elf_obj.select_by_names(RELOCATION_SECTION_NAMES))
write_order << @elf_obj.find_by_name(".shstrtab")
write_order.compact
end
def write_section_index(section_name) = @write_sections.index { it.section_name == section_name }

def write_shared_dynamic_sections(file:)
dynstr_offset = file.pos
file.write(dynstr_section.body.build)
size = file.pos - dynstr_offset
dynstr_section.header.set!(offset: dynstr_offset, size:)

Comment on lines +115 to +119

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard shared writes when .dynstr/.dynsym are absent

When shared: true, write_shared_dynamic_sections unconditionally dereferences dynstr_section (and later dynsym_section). If a caller uses Writer.write! directly with an ELF object that lacks these sections (e.g., from Reader or a pre-change Builder), dynstr_section/dynsym_section will be nil and this will raise NoMethodError, preventing shared output. Consider guarding for missing sections or raising a clearer error so shared doesn’t crash on valid-but-minimal ELF inputs.

Useful? React with 👍 / 👎.

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:)

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:)
end

def ref_index(section_name)
ref_name = section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }
ref_name = "." + ref_name.join(".")
Expand All @@ -115,11 +136,25 @@ def ref_index(section_name)
write_section_index(ref.section_name)
end

def link_index(section_name)
case section_name
when ".symtab"
write_section_index(".strtab")
when ".dynsym", ".dynamic"
write_section_index(".dynstr")
else
nil
end
end

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 }
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 }
end
end
end
4 changes: 4 additions & 0 deletions test/caotral/linker/shared-object_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ def teardown
def test_link_shared_object
Caotral::Linker.link!(inputs: @inputs, output: @output, linker: "self", shared: true, executable: false)
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_equal(:DYN, elf.header.type)
assert_equal(:AMD64, elf.header.arch)
end
Expand Down