Skip to content
Open
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
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
Comment on lines +28 to +29

Choose a reason for hiding this comment

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

P1 Badge Include non-.text sections from all input objects

The new multi-object build only concatenates .text from every input, then copies all other sections (e.g., .data, .rodata, .bss, .symtab, relocation sections) from the first object only. This means any global data, constants, or relocations that live in later objects are silently dropped, so linking multiple inputs that define non-text symbols outside the first file will either produce missing data at runtime or fail to resolve relocations. To support multi-file linking, non-.text sections need to be merged (or at least appended) across all inputs, not just the first one.

Useful? React with 👍 / 👎.

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