Skip to content
5 changes: 3 additions & 2 deletions lib/caotral.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def compile!(input:, assembler: "as", linker: "ld", output: "tmp", debug: false,
execf = "#{basename}#{File.extname(d)}"
compile(input:, output: basename+".s", debug:, shared:)
assemble(input: basename+".s", output: basename+".o", assembler:, debug:, shared:)
link(input: basename+".o", output: execf, linker:, debug:, shared:)
link(input: [basename+".o"], output: execf, linker:, debug:, shared:)
end
def compile(input:, output: "tmp.s", debug: false, shared: false)
Caotral::Compiler.compile!(input:, output:, debug:)
Expand All @@ -23,6 +23,7 @@ def assemble(input:, output: "tmp.o", debug: false, shared: false, assembler: "a
Caotral::Assembler.assemble!(input:, output:, debug:, assembler:, shared:)
end
def link(input:, output: "tmp", linker: "ld", debug: false, shared: false)
Caotral::Linker.link!(input:, output:, linker:, debug:, shared:)
inputs = Array === input ? input : [input]
Caotral::Linker.link!(inputs:, output:, linker:, debug:, shared:)
end
end
1 change: 1 addition & 0 deletions lib/caotral/binary/elf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def find_by_name(section_name) = @sections.find { |s| section_name == s.section_
def select_by_name(section_name) = @sections.select { |s| section_name == s.section_name }
def index(section_name) = @sections.index { |s| section_name == s.section_name }
def select_by_names(section_names) = @sections.select { |section| section_names.any? { |name| name === section.section_name.to_s } }
def without_section(name) = @sections.reject { |s| s.section_name == name }
end
end
end
21 changes: 11 additions & 10 deletions lib/caotral/linker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@

module Caotral
class Linker
def self.link!(input:, output: "a.out", linker: "mold", debug: false, shared: false) = new(input:, output:, linker:, debug:, shared:).link
def self.link!(inputs:, output: "a.out", linker: "mold", debug: false, shared: false) = new(inputs:, output:, linker:, debug:, shared:).link

def initialize(input:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false)
@input, @output, @linker = input, output, linker
def initialize(inputs:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false)
@inputs, @output, @linker = inputs, output, linker
@options = linker_options
@debug, @shared = debug, shared
end

def link(input: @input, output: @output, debug: @debug, shared: @shared)
return to_elf(input:, output:, debug:) if @linker == "self"
def link(inputs: @inputs, output: @output, debug: @debug, shared: @shared)
return to_elf(inputs:, output:, debug:) if @linker == "self"

IO.popen(link_command).close
end

def link_command(input: @input, output: @output, debug: @debug, shared: @shared)
def link_command(inputs: @inputs, output: @output)
ld_path = []

if @shared
Expand All @@ -39,18 +39,19 @@ def link_command(input: @input, output: @output, debug: @debug, shared: @shared)

ld_path << "#{libpath}/libc.so"
ld_path << "#{libpath}/crtn.o"
cmd = [@linker, "-o", @output, "-m", "elf_x86_64", *@options, *ld_path, @input].join(' ')
cmd = [@linker, "-o", @output, "-m", "elf_x86_64", *@options, *ld_path, *inputs].join(' ')
puts cmd if @debug
cmd
end

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(input: @input, output: @output, debug: @debug)
elf_obj = Caotral::Binary::ELF::Reader.new(input:, debug:).read
builder = Caotral::Linker::Builder.new(elf_obj:)
def to_elf(inputs: @inputs, output: @output, debug: @debug)
elf_objs = inputs.map { |input| Caotral::Binary::ELF::Reader.new(input:, debug:).read }
builder = Caotral::Linker::Builder.new(elf_objs:)
builder.resolve_symbols
elf_obj = builder.build
Caotral::Linker::Writer.new(elf_obj:, output:, debug:).write
end
end
Expand Down
40 changes: 30 additions & 10 deletions lib/caotral/linker/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,40 @@ class Builder
BIND_BY_VALUE = SYMTAB_BIND.invert.freeze
attr_reader :symbols

def initialize(elf_obj:)
@elf_obj = elf_obj
def initialize(elf_objs:)
@elf_objs = elf_objs
@symbols = { locals: Set.new, globals: Set.new, weaks: Set.new }
end

def build
raise Caotral::Binary::ELF::Error, "no ELF objects to link" if @elf_objs.empty?
elf = Caotral::Binary::ELF.new
elf_obj = @elf_objs.first
first_text = elf_obj.find_by_name(".text")
text_section = Caotral::Binary::ELF::Section.new(body: String.new, section_name: ".text", header: Caotral::Binary::ELF::SectionHeader.new)
elf.header = elf_obj.header.dup
@elf_objs.each do |elf_obj|
text = elf_obj.find_by_name(".text")
text_section.body << text.body unless text.nil?
end
elf.sections << text_section
@elf_objs.first.without_section(".text").each do |section|
elf.sections << section
end
elf
end
def resolve_symbols
@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 && @symbols[bind].include?(name)
raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}"
@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 && @symbols[bind].include?(name)
raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}"
end
@symbols[bind] << name
end
@symbols[bind] << name
end
@symbols
end
Expand Down
1 change: 1 addition & 0 deletions sig/caotral/binary/elf.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ class Caotral::Binary::ELF
def select_by_name: (String | Symbol) -> Array[Caotral::Binary::ELF::Section]
def index: (String | Symbol) -> Integer?
def select_by_names: (Array[String | Regexp]) -> Array[Caotral::Binary::ELF::Section]
def without_section: (String | Symbol) -> Array[Caotral::Binary::ELF::Section]
end
6 changes: 3 additions & 3 deletions sig/caotral/compiler/linker.rbs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
class Caotral::Compiler::Linker
@input: String
@inputs: Array[String]
@output: String
@linker: String
@options: Array[String]
@shared: bool
@debug: bool

def initialize: (input: String, ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool, ?debug: bool) -> void
def link: (input: String, ?output: String, ?shared: bool, ?debug: bool) -> void
def initialize: (inputs: Array[String], ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool, ?debug: bool) -> void
def link: (inputs: Array[String], ?output: String, ?shared: bool, ?debug: bool) -> void

def link_command: () -> String
def libpath: () -> String
Expand Down
12 changes: 6 additions & 6 deletions sig/caotral/linker.rbs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
class Caotral::Linker
@input: String
@inputs: Array[String]
@output: String
@linker: String
@options: Array[String]
@shared: bool
@debug: bool

def self.link!: (input: String, ?output: String, ?linker: String, ?debug: bool, ?shared: bool) -> void
def initialize: (input: String, ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool, ?debug: bool) -> void
def link: (input: String, ?output: String, ?shared: bool, ?debug: bool) -> void
def self.link!: (inputs: Array[String], ?output: String, ?linker: String, ?debug: bool, ?shared: bool) -> void
def initialize: (inputs: Array[String], ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool, ?debug: bool) -> void
def link: (inputs: Array[String], ?output: String, ?shared: bool, ?debug: bool) -> void

def link_command: (input: String, ?output: String, ?shared: bool, ?debug: bool) -> String
def link_command: (inputs: Array[String], ?output: String, ?shared: bool, ?debug: bool) -> String
def libpath: () -> String
def gcc_libpath: () -> String
def to_elf: (input: String, ?output: String, ?debug: bool) -> String
def to_elf: (inputs: Array[String], ?output: String, ?debug: bool) -> String
end
6 changes: 3 additions & 3 deletions test/caotral/linker_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

class Caotral::LinkerTest < Test::Unit::TestCase
def test_librarie
linker = Caotral::Linker.new(input: "tmp.o")
linker = Caotral::Linker.new(inputs: ["tmp.o"])
assert_false(linker.libpath.empty?, "should not be empty")
assert_false(linker.gcc_libpath.empty?, "should not be empty")
end

def test_link_command
linker = Caotral::Linker.new(input: "tmp.o", output: "tmp")
linker = Caotral::Linker.new(inputs: ["tmp.o"], output: "tmp")
assert_match(%r|mold -o tmp -m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 /.+/crt1.o /.+/crti.o /.+/crtbegin.o /.+/crtend.o /.+/libc.so /.+/crtn.o tmp.o|, linker.link_command)
linker = Caotral::Linker.new(input: "tmp.o", output: "tmp", shared: true)
linker = Caotral::Linker.new(inputs: ["tmp.o"], output: "tmp", shared: true)
assert_match(%r|mold -o tmp -m elf_x86_64 --shared /.+/crti.o /.+/crtbeginS.o /.+/crtendS.o /.+/libc.so /.+/crtn.o tmp.o|, linker.link_command)
end
end