diff --git a/lib/caotral/binary/elf/header.rb b/lib/caotral/binary/elf/header.rb index bdd32cb..949bf1c 100644 --- a/lib/caotral/binary/elf/header.rb +++ b/lib/caotral/binary/elf/header.rb @@ -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 @version = num2bytes(1, 4) @entry = num2bytes(0x00, 8) @phoffset = num2bytes(0x00, 8) @@ -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) @@ -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 diff --git a/lib/caotral/binary/elf/reader.rb b/lib/caotral/binary/elf/reader.rb index 856ac09..89deda9 100644 --- a/lib/caotral/binary/elf/reader.rb +++ b/lib/caotral/binary/elf/reader.rb @@ -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| diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index fc97df7..d803912 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -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 @@ -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 = [] @@ -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!( @@ -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 @@ -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 diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 203f44b..29cd6e4 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -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 @@ -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) diff --git a/sample/C/shared-object.c b/sample/C/shared-object.c new file mode 100644 index 0000000..bf7759e --- /dev/null +++ b/sample/C/shared-object.c @@ -0,0 +1 @@ +int foo() { return 42; } diff --git a/test/caotral/linker/shared-object_test.rb b/test/caotral/linker/shared-object_test.rb new file mode 100644 index 0000000..5356dbf --- /dev/null +++ b/test/caotral/linker/shared-object_test.rb @@ -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