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
24 changes: 24 additions & 0 deletions lib/caotral/binary/archive.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require_relative "archive/error"

require_relative "archive/header"
require_relative "archive/member"
require_relative "archive/name_table"
require_relative "archive/symbol_table"

module Caotral
module Binary
class Archive
attr_accessor :name_table
attr_reader :members, :symbol_tables

def initialize
@members = []
@symbol_tables = []
@name_table = nil
end

def symbol_offset(name) = @symbol_tables.map { |symbol_table| symbol_table.offset_of(name) }&.compact&.first
def find_member_by_offset(offset) = @members.find { |member| member.offset == offset }
end
end
end
9 changes: 9 additions & 0 deletions lib/caotral/binary/archive/error.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module Caotral
module Binary
class Archive
class Error < StandardError
end
end
end
end

16 changes: 16 additions & 0 deletions lib/caotral/binary/archive/header.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module Caotral
module Binary
class Archive
class Header
attr_reader :name, :size, :fmsg
def initialize(str)
@name, size, @fmsg = str.unpack("A16x12x6x6x8A10a2")
@size = Integer(size, 10)
end

def symbol_table? = @name == "/"
def name_table? = @name == "//"
end
end
end
end
24 changes: 24 additions & 0 deletions lib/caotral/binary/archive/member.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Caotral
module Binary
class Archive
class Member
attr_reader :elf, :offset, :name

def initialize(name:, body:, offset:)
@name = name
@body = StringIO.new(body)
@offset = offset
end

def read
return self if @elf
reader = Caotral::Binary::ELF::Reader.new(input: @body)
reader.read
@elf = reader.context
@body = nil
self
end
end
end
end
end
33 changes: 33 additions & 0 deletions lib/caotral/binary/archive/name_table.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Caotral
module Binary
class Archive
class NameTable
attr_reader :names

def initialize(body:)
@body = StringIO.new(body)
@names = []
@name_offsets = {}
end

def parse!
until @body.eof?
offset = @body.pos
entry = @body.gets("/\n")
break if entry == "\n"
name = entry.delete_suffix("/\n")
@names << name
@name_offsets[offset] = name
end
self
end

def resolve(offset)
@name_offsets.fetch(offset) do
raise Caotral::Binary::Archive::Error, "invalid name offset: #{offset}"
end
end
end
end
end
end
64 changes: 64 additions & 0 deletions lib/caotral/binary/archive/reader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require_relative "../archive"
Comment thread
katsyoshi marked this conversation as resolved.

module Caotral
module Binary
class Archive
class Reader
attr_reader :archive

def initialize(path)
input = decision(path)
begin
@bin = StringIO.new(input.read)
@archive = Caotral::Binary::Archive.new
ensure
input.close
end
end

def read!
magic = @bin.read(8)
raise Caotral::Binary::Archive::Error, "unsupported archive type" unless magic == "!<arch>\n"
nt = nil
until @bin.eof?
offset = @bin.pos
header = Caotral::Binary::Archive::Header.new(@bin.read(60))
size = header.size
body = @bin.read(size)
raw_name = header.name
case header.name
when "/"
archive.symbol_tables << Caotral::Binary::Archive::SymbolTable.new(body:).parse!
when "//"
nt = archive.name_table = Caotral::Binary::Archive::NameTable.new(body:).parse!
else
name = if /\A\/(?<idx>\d+)\z/ =~ raw_name
nt.resolve(Integer(idx, 10))
else
raw_name.delete_suffix("/")
end

archive.members << Caotral::Binary::Archive::Member.new(name:, body:, offset:)
end
@bin.read(1) if size.odd?
end
@archive
ensure
@bin.close
end

private
def decision(path)
case path
when String, Pathname
File.open(File.expand_path(path.to_s), "rb")
when StringIO
path
else
raise ArgumentError, "wrong input type"
end
end
end
end
end
end
34 changes: 34 additions & 0 deletions lib/caotral/binary/archive/symbol_table.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Caotral
module Binary
class Archive
class SymbolTable
attr_reader :symbols

Symbol = Data.define(:name, :offset)
def initialize(body:)
@body = StringIO.new(body)
@symbols = []
end

def parse!
count = @body.read(4).unpack1("N")

offsets = count.times.map { @body.read(4).unpack1("N") }
names = count.times.map do
entry = @body.gets("\0")
unless entry&.end_with?("\0")
raise Caotral::Binary::Archive::Error, "invalid symbol table"
end

entry.delete_suffix("\0")
end
@symbols = names.zip(offsets).map { |name, offset| Symbol.new(name:, offset:) }
self
end

def names = @symbols.map(&:name)
def offset_of(name) = @symbols.find { |symbol| symbol.name == name }&.offset
end
end
end
end
3 changes: 3 additions & 0 deletions lib/caotral/binary/elf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
require_relative "elf/section_header"
require_relative "elf/symtab_methods"

require_relative "archive"

require_relative "archive/reader"
require_relative "elf/reader"

module Caotral
Expand Down
2 changes: 2 additions & 0 deletions lib/caotral/binary/elf/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,8 @@ def decision(input)
case input
when String, Pathname
File.open(File.expand_path(input.to_s), "rb")
when StringIO
input
else
raise ArgumentError, "wrong input type"
end
Expand Down
15 changes: 8 additions & 7 deletions lib/caotral/linker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

module Caotral
class Linker
def self.link!(inputs:, output: "a.out", linker: "mold", debug: false, shared: false, executable: true, pie: false, needed: [])
new(inputs:, output:, linker:, debug:, shared:, executable:, pie:, needed:).link
def self.link!(inputs:, output: "a.out", linker: "mold", debug: false, shared: false, executable: true, pie: false, needed: [], archives: [])
new(inputs:, output:, linker:, debug:, shared:, executable:, pie:, needed:, archives:).link
end

def initialize(inputs:, output: "a.out", linker: "mold", linker_options: [], executable: true, shared: false, pie: false, debug: false, needed: [])
def initialize(inputs:, output: "a.out", linker: "mold", linker_options: [], executable: true, shared: false, pie: false, debug: false, needed: [], archives: [])
@inputs, @output, @linker = inputs, output, linker
@options = linker_options
@needed = needed
@needed, @archives = needed, archives
@executable = shared ? false : executable
@debug, @shared, @pie = debug, shared, pie
end
Expand Down Expand Up @@ -54,12 +54,13 @@ 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, pie: @pie, needed: @needed)
def to_elf(inputs: @inputs, output: @output, debug: @debug, shared: @shared, executable: @executable, pie: @pie, needed: @needed, archives: @archives)
raise Caotral::Binary::ELF::Error, "disallow both mode: shared and PIE" if shared && pie

elf_objs = inputs.map { |input| Caotral::Binary::ELF::Reader.new(input:, debug:).read }
builder = Caotral::Linker::Builder.new(elf_objs:, debug:, shared:, executable:, pie:, needed:)
builder.resolve_symbols
builder = Caotral::Linker::Builder.new(elf_objs:, debug:, shared:, executable:, pie:, needed:, archives:)
builder.resolve_symbols!
builder.extract_archive_members! unless archives.empty?
elf = builder.build
metadata = builder.linker_metadata
Caotral::Linker::Layout.new(elf:, shared:, executable:, pie:).apply!
Expand Down
64 changes: 42 additions & 22 deletions lib/caotral/linker/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ class Builder

attr_reader :symbols, :linker_metadata

def initialize(elf_objs:, executable: true, debug: false, shared: false, pie: false, needed: [])
def initialize(elf_objs:, executable: true, debug: false, shared: false, pie: false, needed: [], archives: [])
@elf_objs = elf_objs
@archives = archives.map { |archive| Caotral::Binary::Archive::Reader.new(archive).read! }
@executable, @debug, @shared, @pie, @needed = executable, debug, shared, pie, needed
@symbols = {
locals: Set.new,
Expand Down Expand Up @@ -486,33 +487,52 @@ def build
elf
end

def resolve_symbols
@elf_objs.each do |elf_obj|
elf_obj.find_by_name(".symtab").body.each do |symtab|
name = symtab.name_string
next if name.empty?
info = symtab.info
bind = BIND_BY_VALUE.fetch(info >> 4)
if bind == :globals
if symtab.shndx == 0
@symbols[bind][:undefined] << name unless @symbols[bind][:defined].include?(name)
else
if @symbols[bind][:defined].keys.include?(name)
raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}"
end
@symbols[bind][:undefined].delete(name)
@symbols[bind][:defined][name] = symtab.shndx
end
else
@symbols[bind] << name
end
def resolve_symbols! = @elf_objs.each { |elf_obj| resolve_symbol_for(elf_obj) }
def extract_archive_members!
resolved = Set[]
loop do
elfs = []
@symbols[:globals][:undefined].each do |symbol|
archive = implement_symbol_archive(symbol)
next if archive.nil?
offset = archive.symbol_offset(symbol)
member = archive.find_member_by_offset(offset)
next if member.nil?
next unless resolved.add?(member)
elfs << member.read.elf
end
break if elfs.empty?
elfs.each { |elf| resolve_symbol_for(elf) }
@elf_objs += elfs
end
@symbols
end

private

def implement_symbol_archive(symbol) = @archives.find { |archive| archive.symbol_tables.any? { |symbol_table| symbol_table.names.include?(symbol) } }

def resolve_symbol_for(elf_obj)
elf_obj.find_by_name(".symtab").body.each do |symtab|
name = symtab.name_string
next if name.empty?
info = symtab.info
bind = BIND_BY_VALUE.fetch(info >> 4)
if bind == :globals
if symtab.shndx == 0
@symbols[bind][:undefined] << name unless @symbols[bind][:defined].include?(name)
else
if @symbols[bind][:defined].keys.include?(name)
raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}"
end
@symbols[bind][:undefined].delete(name)
@symbols[bind][:defined][name] = symtab.shndx
end
else
@symbols[bind] << name
end
end
end

def build_shared_dynamic_sections
dynstr_section = Caotral::Binary::ELF::Section.new(
body: Caotral::Binary::ELF::Section::Strtab.new("\0".b),
Expand Down
5 changes: 5 additions & 0 deletions sample/C/archive-recursive.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
int add(int x, int y);

int foo(void) {
return add(40, 2);
}
34 changes: 34 additions & 0 deletions test/caotral/binary/archive_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
require_relative "../../test_suite"

class Caotral::Binary::ArchiveTest < Test::Unit::TestCase
include CommonSetupHelper
include BuildFixtureHelper

def test_archive
@output = "archive.a"
@generated = [@output]
@inputs = []
inputs = [
"sample/C/multi-file-link-a.c",
"sample/C/add.c"
]
create_archive(output: @output, inputs:)
reader = Caotral::Binary::Archive::Reader.new(output)
reader.read!
archive = reader.archive
mul = archive.members.first
add = archive.members.last
symbol_tables = archive.symbol_tables
name_table = archive.name_table
assert_equal(2, archive.members.size)
assert_equal("multi-file-link-a.o", mul.name)
assert_equal("add.o", add.name)

assert_equal(1, symbol_tables.size)
assert_equal("foo", symbol_tables.first.symbols.first.name)
assert_equal(170, symbol_tables.first.symbols.first.offset)
assert_not_nil(name_table)
assert_equal(["multi-file-link-a.o"], name_table.names)
assert_equal("multi-file-link-a.o", name_table.resolve(0))
end
end
Loading
Loading