From 107f011648f1526bd95d43476afcd7a84afb373b Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 13 Jan 2026 21:13:41 +0900 Subject: [PATCH 1/3] support multifile for linker for sig --- sig/caotral/linker.rbs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sig/caotral/linker.rbs b/sig/caotral/linker.rbs index f87cd68..d3971f9 100644 --- a/sig/caotral/linker.rbs +++ b/sig/caotral/linker.rbs @@ -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 From c3925f682db0762c960a814c242d787fedd2c0d9 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 13 Jan 2026 22:19:16 +0900 Subject: [PATCH 2/3] introduce multi-file linker interface --- lib/caotral.rb | 5 +++-- lib/caotral/linker.rb | 19 ++++++++++--------- sig/caotral/compiler/linker.rbs | 6 +++--- test/caotral/linker_test.rb | 6 +++--- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/caotral.rb b/lib/caotral.rb index 6d5ab81..7a11bdb 100644 --- a/lib/caotral.rb +++ b/lib/caotral.rb @@ -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:) @@ -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 diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index ea5764d..f865a8d 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -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 @@ -39,7 +39,7 @@ 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 @@ -47,8 +47,9 @@ def link_command(input: @input, output: @output, debug: @debug, shared: @shared) 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 + def to_elf(inputs: @inputs, output: @output, debug: @debug) + elf_objs = inputs.map { |input| Caotral::Binary::ELF::Reader.new(input:, debug:).read } + elf_obj = elf_objs.first builder = Caotral::Linker::Builder.new(elf_obj:) builder.resolve_symbols Caotral::Linker::Writer.new(elf_obj:, output:, debug:).write diff --git a/sig/caotral/compiler/linker.rbs b/sig/caotral/compiler/linker.rbs index 7fbf9de..e69fc64 100644 --- a/sig/caotral/compiler/linker.rbs +++ b/sig/caotral/compiler/linker.rbs @@ -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 diff --git a/test/caotral/linker_test.rb b/test/caotral/linker_test.rb index 1c117c2..e145e29 100644 --- a/test/caotral/linker_test.rb +++ b/test/caotral/linker_test.rb @@ -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 From fd8a6ea375a364f1a7eabb52b3d25bcdc2217fb0 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 13 Jan 2026 23:09:51 +0900 Subject: [PATCH 3/3] add build for multi-file --- lib/caotral/binary/elf.rb | 1 + lib/caotral/linker.rb | 4 ++-- lib/caotral/linker/builder.rb | 40 ++++++++++++++++++++++++++--------- sig/caotral/binary/elf.rbs | 1 + 4 files changed, 34 insertions(+), 12 deletions(-) diff --git a/lib/caotral/binary/elf.rb b/lib/caotral/binary/elf.rb index 0836008..9869314 100644 --- a/lib/caotral/binary/elf.rb +++ b/lib/caotral/binary/elf.rb @@ -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 diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index f865a8d..58f97c8 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -49,9 +49,9 @@ def gcc_libpath = @gcc_libpath ||= File.dirname(Dir.glob("/usr/lib/gcc/x86_64-*/ def to_elf(inputs: @inputs, output: @output, debug: @debug) elf_objs = inputs.map { |input| Caotral::Binary::ELF::Reader.new(input:, debug:).read } - elf_obj = elf_objs.first - builder = Caotral::Linker::Builder.new(elf_obj:) + 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 diff --git a/lib/caotral/linker/builder.rb b/lib/caotral/linker/builder.rb index 928353f..c1ab5a8 100644 --- a/lib/caotral/linker/builder.rb +++ b/lib/caotral/linker/builder.rb @@ -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 diff --git a/sig/caotral/binary/elf.rbs b/sig/caotral/binary/elf.rbs index df74cdc..8a0df98 100644 --- a/sig/caotral/binary/elf.rbs +++ b/sig/caotral/binary/elf.rbs @@ -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