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
39 changes: 13 additions & 26 deletions lib/caotral/binary/elf/header.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ class Header
IDENT = [0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].freeze
IDENT_STR = IDENT.pack("C*").freeze
ELF_FILE_TYPE = { NONE: 0, REL: 1, EXEC: 2, DYN: 3, CORE: 4 }.freeze
ELF_TARGET_MACHINE = { AMD64: 62 }.freeze
TYPE = ELF_FILE_TYPE
ITYPE = TYPE.invert.freeze
MACHINE = ELF_TARGET_MACHINE
IMACHINE = MACHINE.invert.freeze

def initialize(endian: :little, type: :rel, arc: :amd64)
def initialize(endian: :little, type: :REL)
@ident = IDENT
@type = num2bytes(ELF_FILE_TYPE[elf(type)], 2)
@arch = arch(arc)
@type = num2bytes(ELF_FILE_TYPE[type], 2)
@arch = num2bytes(62, 2) # target: e_machine EM_x86_64
Comment on lines +16 to +19

Choose a reason for hiding this comment

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

P2 Badge Preserve Header.new keyword/type normalization

The initializer now directly indexes ELF_FILE_TYPE[type] and dropped the previous normalization logic, so callers that still pass type: :rel/:exec or string values (which were accepted before) will now hit nil and raise a TypeError inside num2bytes. Also, removing the arc: keyword means any existing caller supplying arc: now gets an ArgumentError. This is a runtime regression for existing usage patterns; consider keeping the normalization/keyword or raising a clearer validation error.

Useful? React with 👍 / 👎.

@version = num2bytes(1, 4)
@entry = num2bytes(0x00, 8)
@phoffset = num2bytes(0x00, 8)
Expand All @@ -27,8 +32,9 @@ def initialize(endian: :little, type: :rel, arc: :amd64)

def build = bytes.flatten.pack("C*")

def set!(type: nil, entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil, phsize: nil, phnum: nil, ehsize: nil)
def set!(type: nil, arch: nil, entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil, phsize: nil, phnum: nil, ehsize: nil)
@type = num2bytes(type, 2) if check(type, 2)
@arch = num2bytes(arch, 2) if check(arch, 2)
@entry = num2bytes(entry, 8) if check(entry, 8)
@phoffset = num2bytes(phoffset, 8) if check(phoffset, 8)
@phsize = num2bytes(phsize, 2) if check(phsize, 2)
Expand All @@ -48,33 +54,14 @@ def shentsize = @shentsize.pack("C*").unpack1("S<")
def shnum = @shnum.pack("C*").unpack1("S<")
def shstrndx = @shstrndx.pack("C*").unpack1("S<")
def shoffset = @shoffset.pack("C*").unpack1("Q<")
def type = ITYPE[@type.pack("C*").unpack1("S<")]
def arch = IMACHINE[@arch.pack("C*").unpack1("S<")]

private
def bytes = [
private def bytes = [
@ident, @type, @arch, @version, @entry, @phoffset,
@shoffset, @flags, @ehsize, @phsize, @phnum, @shentsize,
@shnum, @shstrndx
]

def arch(machine)
case machine.to_s
in "amd64" | "x86_64" | "x64"
[0x3e, 0x00]
end
end

def elf(type)
case type.to_s
in "relocatable" | "rel"
:REL
in "exe" | "ex" | "exec"
:EXEC
in "shared" | "share" | "dynamic" | "dyn"
:DYN
else
:NONE
end
end
end
end
end
Expand Down
18 changes: 10 additions & 8 deletions lib/caotral/binary/elf/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ def read
header = @bin.read(0x40)
ident = header[0, 16]
raise "Not ELF file" unless ident == Caotral::Binary::ELF::Header::IDENT_STR

entry = header[24, 8].unpack("Q<").first
phoffset = header[32, 8].unpack("Q<").first
shoffset = header[40, 8].unpack("Q<").first
shentsize = header[58, 2].unpack("S<").first
shnum = header[60, 2].unpack("S<").first
shstrndx = header[62, 2].unpack("S<").first
@context.header.set!(entry:, phoffset:, shoffset:, shnum:, shstrndx:)

type = header[16, 2].unpack1("S<")
arch = header[18, 2].unpack1("S<")
entry = header[24, 8].unpack1("Q<")
phoffset = header[32, 8].unpack1("Q<")
shoffset = header[40, 8].unpack1("Q<")
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:)

@bin.pos = shoffset
shnum.times do |i|
Expand Down
14 changes: 10 additions & 4 deletions lib/caotral/linker/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ class Builder
RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze
ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze

attr_reader :symbols, :executable, :debug
attr_reader :symbols

def initialize(elf_objs:, executable: true, debug: false, shared: false)
@elf_objs = elf_objs
@executable, @debug, @shared = executable, debug, shared
@symbols = { locals: Set.new, globals: Set.new, weaks: Set.new }
sharing_object!
end

def build
Expand Down Expand Up @@ -52,6 +53,10 @@ def build
start_bytes = [0xe8, *[0] * 4, 0x48, 0x89, 0xc7, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05]
exec_text_offset = 0x1000
base_addr = 0x400000
unless @executable
start_bytes = []
base_addr = 0
end
vaddr = base_addr + exec_text_offset
start_len = start_bytes.length
sections = []
Expand Down Expand Up @@ -108,9 +113,9 @@ def build
sections << null_section

main_sym = symtab_section.body.find { |sym| sym.name_string == "main" }
raise Caotral::Binary::ELF::Error, "main function not found" if executable && main_sym.nil?
raise Caotral::Binary::ELF::Error, "main function not found" if @executable && main_sym.nil?
main_offset = main_sym.nil? ? 0 : main_sym.value + start_len
start_bytes[1, 4] = num2bytes((main_offset - 5), 4)
start_bytes[1, 4] = num2bytes((main_offset - 5), 4) if @executable
text_section.body.prepend(start_bytes.pack("C*"))

text_section.header.set!(
Expand Down Expand Up @@ -226,7 +231,7 @@ def build
target.body = bytes
end

sections = sections.reject { |section| RELOCATION_SECTION_NAMES.any? { |name| name === section.section_name.to_s } } if executable
sections = sections.reject { |section| RELOCATION_SECTION_NAMES.any? { |name| name === section.section_name.to_s } } if @executable
sections.each { |section| elf.sections << section }

elf
Expand Down Expand Up @@ -258,6 +263,7 @@ 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)
end
end
end
9 changes: 7 additions & 2 deletions lib/caotral/linker/writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,14 @@ def initialize(elf_obj:, output:, entry: nil, debug: false, executable: true, sh
@elf_obj, @output, @entry, @debug, @executable, @shared = elf_obj, output, entry, debug, executable, shared
@write_sections = write_order_sections
end

def write
f = File.open(@output, "wb")
phoffset, phnum, phsize, ehsize = 64, 1, 56, 64
header = @elf_obj.header.set!(type: 2, phoffset:, phnum:, phsize:, ehsize:)
file_type = @shared ? :DYN : :EXEC
e_type = Caotral::Binary::ELF::Header::TYPE[file_type]

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
Expand All @@ -28,8 +32,9 @@ def write
type, flags = 1, 5
filesz = text_section.body.bytesize
memsz = filesz
entry = @shared ? 0 : (@entry || vaddr)

header.set!(entry: @entry || vaddr)
header.set!(entry:)
ph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:)
f.write(@elf_obj.header.build)
f.write(ph.build)
Expand Down
1 change: 1 addition & 0 deletions sample/C/shared-object.c
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int foo() { return 42; }
23 changes: 23 additions & 0 deletions test/caotral/linker/shared-object_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require_relative "../../test_suite"

class Caotral::Linker::SharedObjectLinkingTest < Test::Unit::TestCase
include TestProcessHelper
def setup
@inputs = ["shared.o"]
@output = "libshared.so"
path = Pathname.new("sample/C/shared-object.c").to_s
IO.popen(["gcc", "-fPIC", "-c", "-o", @inputs[0], "%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_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)
assert_equal(:DYN, elf.header.type)
assert_equal(:AMD64, elf.header.arch)
end
end