From e7e111e2b522170fc443cc712f36b5f67290e89b Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 24 Dec 2025 00:23:05 +0900 Subject: [PATCH 01/19] exclude ai agent docs --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2144d3d..d2d627d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /coverage/ /doc/ /pkg/ +/AGENTS.md /spec/reports/ /tmp/ /Gemfile.lock @@ -13,3 +14,5 @@ /.gem_rbs_collection /node_modules /package*.json +/*.md +!/README.md From d8167f38ff481e1ad6ac65f5ddb6cc775206fcbe Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 27 Dec 2025 12:15:24 +0900 Subject: [PATCH 02/19] add linker elf stubs --- lib/caotral/linker.rb | 7 +++ lib/caotral/linker/elf.rb | 7 +++ lib/caotral/linker/elf/header.rb | 60 ++++++++++++++++++++++ lib/caotral/linker/elf/section.rb | 13 +++++ lib/caotral/linker/elf/section/shstrtab.rb | 6 +++ lib/caotral/linker/elf/section/strtab.rb | 5 ++ lib/caotral/linker/elf/section/symtab.rb | 12 +++++ lib/caotral/linker/elf/section/text.rb | 4 ++ lib/caotral/linker/elf/section_header.rb | 37 +++++++++++++ lib/caotral/linker/elf/sections.rb | 18 +++++++ lib/caotral/linker/reader.rb | 29 +++++++++++ lib/caotral/linker/writer.rb | 8 +++ sig/caotral/linker.rbs | 17 ++++++ sig/caotral/linker/elf.rbs | 3 ++ sig/caotral/linker/reader.rbs | 5 ++ sig/caotral/linker/writer.rbs | 5 ++ 16 files changed, 236 insertions(+) create mode 100644 lib/caotral/linker/elf.rb create mode 100644 lib/caotral/linker/elf/header.rb create mode 100644 lib/caotral/linker/elf/section.rb create mode 100644 lib/caotral/linker/elf/section/shstrtab.rb create mode 100644 lib/caotral/linker/elf/section/strtab.rb create mode 100644 lib/caotral/linker/elf/section/symtab.rb create mode 100644 lib/caotral/linker/elf/section/text.rb create mode 100644 lib/caotral/linker/elf/section_header.rb create mode 100644 lib/caotral/linker/elf/sections.rb create mode 100644 lib/caotral/linker/reader.rb create mode 100644 lib/caotral/linker/writer.rb create mode 100644 sig/caotral/linker.rbs create mode 100644 sig/caotral/linker/elf.rbs create mode 100644 sig/caotral/linker/reader.rbs create mode 100644 sig/caotral/linker/writer.rbs diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 9a73dd2..80c2a3f 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -12,6 +12,8 @@ def link(input: @input, output: @output, debug: @debug, shared: @shared) = IO.po def link_command(input: @input, output: @output, debug: @debug, shared: @shared) ld_path = [] + return to_elf(input:, output:, debug:) if @linker == "self" + if @shared ld_path << "--shared" ld_path << "#{libpath}/crti.o" @@ -36,4 +38,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::Linker::Reader.new(input:, debug:).read + Caotral::Linker::Writer.new(elf_obj:, output:, debug:).write + end end diff --git a/lib/caotral/linker/elf.rb b/lib/caotral/linker/elf.rb new file mode 100644 index 0000000..75bcaae --- /dev/null +++ b/lib/caotral/linker/elf.rb @@ -0,0 +1,7 @@ +class Caotral::Linker::ELF + attr_reader :sections, :header + def initialize + @sections = Caotral::Linker::ELF::Sections.new + @header = Caotral::Linker::ELF::Header.new + end +end diff --git a/lib/caotral/linker/elf/header.rb b/lib/caotral/linker/elf/header.rb new file mode 100644 index 0000000..ea1785f --- /dev/null +++ b/lib/caotral/linker/elf/header.rb @@ -0,0 +1,60 @@ +class Caotral::Linker::ELF::Header + include Caotral::Assembler::ELF::Utils + IDENT = [0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].freeze + ELF_FILE_TYPE = { NONE: 0, REL: 1, EXEC: 2, DYN: 3, CORE: 4 }.freeze + + def initialize(endian: :little, type: :rel, arc: :amd64) + @ident = IDENT + @type = num2bytes(ELF_FILE_TYPE[elf(type)], 2) + @arch = arch(arc) + @version = num2bytes(1, 4) + @entry = num2bytes(0x00, 8) + @phoffset = num2bytes(0x00, 8) + @shoffset = num2bytes(0x00, 8) + @flags = num2bytes(0x00, 4) + @ehsize = num2bytes(0x40, 2) + @phsize = num2bytes(0x00, 2) + @phnum = num2bytes(0x00, 2) + @shentsize = num2bytes(0x40, 2) + @shnum = num2bytes(0x08, 2) + @shstrndx = num2bytes(0x07, 2) + end + + def build = bytes.flatten.pack("C*") + + def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil) + @entry = num2bytes(entry, 8) if check(entry, 8) + @phoffset = num2bytes(phoffset, 8) if check(phoffset, 8) + @shoffset = num2bytes(shoffset, 8) if check(shoffset, 8) + @shnum = num2bytes(shnum, 2) if check(shnum, 2) + @shstrndx = num2bytes(shstrndx, 2) if check(shstrndx, 2) + end + + 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 diff --git a/lib/caotral/linker/elf/section.rb b/lib/caotral/linker/elf/section.rb new file mode 100644 index 0000000..d7b46cb --- /dev/null +++ b/lib/caotral/linker/elf/section.rb @@ -0,0 +1,13 @@ +class Caotral::Linker::ELF::Section + attr_accessor :name + attr_reader :section_name, :header, :body + def initialize(type:, options: {}) + type_string = type.to_s.capitalize + type_string = type_string.upcase if type_string == "Bss" + # section_name is extra information about section type + @section_name = type_string.downcase + # name is used in section header string table in elf file + @name = section_name == "null" ? "" : "\0.#{section_name}" + @header, @body = nil, nil + end +end diff --git a/lib/caotral/linker/elf/section/shstrtab.rb b/lib/caotral/linker/elf/section/shstrtab.rb new file mode 100644 index 0000000..244e5e7 --- /dev/null +++ b/lib/caotral/linker/elf/section/shstrtab.rb @@ -0,0 +1,6 @@ +class Caotral::Linker::ELF::Section::Shstrtab + include Caotral::Assembler::ELF::Utils + def initialize(**opts) = @name = [] + def build = bytes.flatten.pack("C*") + def set!(name:) = (@name << name!(name); self) +end diff --git a/lib/caotral/linker/elf/section/strtab.rb b/lib/caotral/linker/elf/section/strtab.rb new file mode 100644 index 0000000..f44e311 --- /dev/null +++ b/lib/caotral/linker/elf/section/strtab.rb @@ -0,0 +1,5 @@ +class Caotral::Linker::ELF::Section::Strtab + include Caotral::Assembler::ELF::Utils + def initialize(names = "\0main\0", **opts) = @names = names + def build = @names.bytes.pack("C*") +end diff --git a/lib/caotral/linker/elf/section/symtab.rb b/lib/caotral/linker/elf/section/symtab.rb new file mode 100644 index 0000000..de51dc8 --- /dev/null +++ b/lib/caotral/linker/elf/section/symtab.rb @@ -0,0 +1,12 @@ +class Caotral::Linker::ELF::Section::Symtab + include Caotral::Assembler::ELF::Utils + def initialize(**opts) + @entsize = [] + @name = num2bytes(0, 4) + @info = num2bytes(0, 1) + @other = num2bytes(0, 1) + @shndx = num2bytes(0, 2) + @value = num2bytes(0, 8) + @size = num2bytes(0, 8) + end +end diff --git a/lib/caotral/linker/elf/section/text.rb b/lib/caotral/linker/elf/section/text.rb new file mode 100644 index 0000000..e3998f5 --- /dev/null +++ b/lib/caotral/linker/elf/section/text.rb @@ -0,0 +1,4 @@ +class Caotral::Linker::ELF::Section::Text + def initialize = @bytes = [] + def build = @bytes.flatten.pack("C*") +end diff --git a/lib/caotral/linker/elf/section_header.rb b/lib/caotral/linker/elf/section_header.rb new file mode 100644 index 0000000..98223ba --- /dev/null +++ b/lib/caotral/linker/elf/section_header.rb @@ -0,0 +1,37 @@ +class Caotral::Linker::ELF::SectionHeader + include Caotral::Assembler::ELF::Utils + def initialize + @name = nil + @type = nil + @flags = nil + @addr = nil + @offset = nil + @size = nil + @link = nil + @info = nil + @addralign = nil + @entsize = nil + end + + def build = bytes.flatten.pack("C*") + + def set!(name: nil, type: nil, flags: nil, addr: nil, + offset: nil, size: nil, link: nil, info: nil, + addralign: nil, entsize: nil) + @name = num2bytes(name, 4) if check(name, 4) + @type = num2bytes(type, 4) if check(type, 4) + @flags = num2bytes(flags, 8) if check(flags, 8) + @addr = num2bytes(addr, 8) if check(addr, 8) + @offset = num2bytes(offset, 8) if check(offset, 8) + @size = num2bytes(size, 8) if check(size, 8) + @link = num2bytes(link, 4) if check(link, 4) + @info = num2bytes(info, 4) if check(info, 4) + @addralign = num2bytes(addralign, 8) if check(addralign, 8) + @entsize = num2bytes(entsize, 8) if check(entsize, 8) + self + end + + def null! = set!(name: 0, type: 0, flags: 0, addr: 0, offset: 0, size: 0, link: 0, info: 0, addralign: 0, entsize: 0) + + private def bytes = [@name, @type, @flags, @addr, @offset, @size, @link, @info, @addralign, @entsize] +end diff --git a/lib/caotral/linker/elf/sections.rb b/lib/caotral/linker/elf/sections.rb new file mode 100644 index 0000000..e84c4a4 --- /dev/null +++ b/lib/caotral/linker/elf/sections.rb @@ -0,0 +1,18 @@ +class Caotral::Linker::ELF::Sections + include Enumerable + + def initialize = @sections = [] + def each(&block) = @sections.each(&block) + def add(section) = @sections << section + alias << add + def [](index) + case index + when Integer + @sections[index] + when String, Symbol + @sections.find { _1.section_name.to_s == index.to_s } + else + raise ArgumentError, "Invalid index type: #{index.class}" + end + end +end diff --git a/lib/caotral/linker/reader.rb b/lib/caotral/linker/reader.rb new file mode 100644 index 0000000..ec36eed --- /dev/null +++ b/lib/caotral/linker/reader.rb @@ -0,0 +1,29 @@ +class Caotral::Linker::Reader + attr_reader :sections + def self.read!(input:, debug: false, linker_options: []) + new(input:, debug:, linker_options:).read + end + + def initialize(input:, debug: false, linker_options: []) + @input = decision(input) + @bin = @input.read + @context = Caotral::Linker::ELF.new + end + + def read + @context + ensure + @input.close + end + + private + + def decision(input) + case input + when String, Pathname + File.open(File.expand_path(input.to_s), "rb") + else + raise ArgumentError, "wrong input type" + end + end +end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb new file mode 100644 index 0000000..689b84b --- /dev/null +++ b/lib/caotral/linker/writer.rb @@ -0,0 +1,8 @@ +class Caotral::Linker::Writer + attr_reader :elf_obj, :output, :debug + def self.write!(elf_obj:, output:, debug: false) + new(elf_obj:, output:, debug:).write + end + def initialize(elf_obj:, output:, debug: false) = @elf_obj, @output, @debug = elf_obj, output, debug + def write = output +end diff --git a/sig/caotral/linker.rbs b/sig/caotral/linker.rbs new file mode 100644 index 0000000..493aabe --- /dev/null +++ b/sig/caotral/linker.rbs @@ -0,0 +1,17 @@ +class Caotral::Linker + @input: 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 link_command: (input: String, ?output: String, ?shared: bool, ?debug: bool) -> String + def libpath: () -> String + def gcc_libpath: () -> String + def to_elf: (input: String, ?output: String, ?debug: bool) -> void +end diff --git a/sig/caotral/linker/elf.rbs b/sig/caotral/linker/elf.rbs new file mode 100644 index 0000000..a295925 --- /dev/null +++ b/sig/caotral/linker/elf.rbs @@ -0,0 +1,3 @@ +class Caotral::Linker::ELF + def initialize: () -> void +end diff --git a/sig/caotral/linker/reader.rbs b/sig/caotral/linker/reader.rbs new file mode 100644 index 0000000..689f5e4 --- /dev/null +++ b/sig/caotral/linker/reader.rbs @@ -0,0 +1,5 @@ +class Caotral::Linker::Reader + def self.read!: (input: String, ?debug: bool, ?linker_options: Array[String]) -> Caotral::Linker::ELF + def initialize: (input: String, ?debug: bool, ?linker_options: Array[String]) -> void + def read: () -> Caotral::Linker::ELF +end diff --git a/sig/caotral/linker/writer.rbs b/sig/caotral/linker/writer.rbs new file mode 100644 index 0000000..fdc3288 --- /dev/null +++ b/sig/caotral/linker/writer.rbs @@ -0,0 +1,5 @@ +class Caotral::Linker::Writer + def self.write!: (elf_obj: Caotral::Linker::ELF, output: String, ?debug: bool) -> String + def initialize: (elf_obj: Caotral::Linker::ELF, output: String, ?debug: bool) -> void + def write: () -> String +end From 0c6362dd5e7f561c8f04686a8d3996a0a6de8e39 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Mon, 29 Dec 2025 01:16:03 +0900 Subject: [PATCH 03/19] read elf header --- Gemfile | 1 + lib/caotral/linker.rb | 75 ++++++++++---------- lib/caotral/linker/elf.rb | 17 +++-- lib/caotral/linker/elf/header.rb | 108 ++++++++++++++++------------- lib/caotral/linker/reader.rb | 61 ++++++++++------ test/caotral/linker/reader_test.rb | 10 +++ 6 files changed, 159 insertions(+), 113 deletions(-) create mode 100644 test/caotral/linker/reader_test.rb diff --git a/Gemfile b/Gemfile index abd565b..79d82bb 100644 --- a/Gemfile +++ b/Gemfile @@ -7,5 +7,6 @@ gemspec gem "rake", "~> 13.0" gem "fiddle" +gem "irb" gem "steep" gem "test-unit" diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 80c2a3f..5294aa7 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -1,46 +1,49 @@ # frozen_string_literal: true -class Caotral::Linker - def self.link!(input:, output: "a.out", linker: "mold", debug: false, shared: false) = new(input:, output:, linker:, debug:, shared:).link +require_relative "linker/reader" +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 initialize(input:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false) - @input, @output, @linker = input, output, linker - @options = linker_options - @debug, @shared = debug, shared - end + def initialize(input:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false) + @input, @output, @linker = input, output, linker + @options = linker_options + @debug, @shared = debug, shared + end - def link(input: @input, output: @output, debug: @debug, shared: @shared) = IO.popen(link_command).close + def link(input: @input, output: @output, debug: @debug, shared: @shared) = IO.popen(link_command).close - def link_command(input: @input, output: @output, debug: @debug, shared: @shared) - ld_path = [] - return to_elf(input:, output:, debug:) if @linker == "self" + def link_command(input: @input, output: @output, debug: @debug, shared: @shared) + ld_path = [] + return to_elf(input:, output:, debug:) if @linker == "self" - if @shared - ld_path << "--shared" - ld_path << "#{libpath}/crti.o" - ld_path << "#{gcc_libpath}/crtbeginS.o" - ld_path << "#{gcc_libpath}/crtendS.o" - else - ld_path << "-dynamic-linker" - ld_path << "/lib64/ld-linux-x86-64.so.2" - ld_path << "#{libpath}/crt1.o" - ld_path << "#{libpath}/crti.o" - ld_path << "#{gcc_libpath}/crtbegin.o" - # for not static compile - ld_path << "#{gcc_libpath}/crtend.o" - end + if @shared + ld_path << "--shared" + ld_path << "#{libpath}/crti.o" + ld_path << "#{gcc_libpath}/crtbeginS.o" + ld_path << "#{gcc_libpath}/crtendS.o" + else + ld_path << "-dynamic-linker" + ld_path << "/lib64/ld-linux-x86-64.so.2" + ld_path << "#{libpath}/crt1.o" + ld_path << "#{libpath}/crti.o" + ld_path << "#{gcc_libpath}/crtbegin.o" + # for not static compile + ld_path << "#{gcc_libpath}/crtend.o" + end - ld_path << "#{libpath}/libc.so" - ld_path << "#{libpath}/crtn.o" - cmd = [@linker, "-o", @output, "-m", "elf_x86_64", *@options, *ld_path, @input].join(' ') - puts cmd if @debug - cmd - end + ld_path << "#{libpath}/libc.so" + ld_path << "#{libpath}/crtn.o" + cmd = [@linker, "-o", @output, "-m", "elf_x86_64", *@options, *ld_path, @input].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 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::Linker::Reader.new(input:, debug:).read - Caotral::Linker::Writer.new(elf_obj:, output:, debug:).write + def to_elf(input: @input, output: @output, debug: @debug) + elf_obj = Caotral::Linker::Reader.new(input:, debug:).read + Caotral::Linker::Writer.new(elf_obj:, output:, debug:).write + end end end diff --git a/lib/caotral/linker/elf.rb b/lib/caotral/linker/elf.rb index 75bcaae..0635e21 100644 --- a/lib/caotral/linker/elf.rb +++ b/lib/caotral/linker/elf.rb @@ -1,7 +1,14 @@ -class Caotral::Linker::ELF - attr_reader :sections, :header - def initialize - @sections = Caotral::Linker::ELF::Sections.new - @header = Caotral::Linker::ELF::Header.new +require_relative "elf/header" +require_relative "elf/sections" + +module Caotral + class Linker + class ELF + attr_reader :sections, :header + def initialize + @sections = Caotral::Linker::ELF::Sections.new + @header = Caotral::Linker::ELF::Header.new + end + end end end diff --git a/lib/caotral/linker/elf/header.rb b/lib/caotral/linker/elf/header.rb index ea1785f..4b4d167 100644 --- a/lib/caotral/linker/elf/header.rb +++ b/lib/caotral/linker/elf/header.rb @@ -1,60 +1,68 @@ -class Caotral::Linker::ELF::Header - include Caotral::Assembler::ELF::Utils - IDENT = [0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].freeze - ELF_FILE_TYPE = { NONE: 0, REL: 1, EXEC: 2, DYN: 3, CORE: 4 }.freeze +module Caotral + class Linker + class ELF + class Header + include Caotral::Assembler::ELF::Utils + attr_reader :entry, :phoffset, :shoffset, :shnum, :shstrndx + 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 - def initialize(endian: :little, type: :rel, arc: :amd64) - @ident = IDENT - @type = num2bytes(ELF_FILE_TYPE[elf(type)], 2) - @arch = arch(arc) - @version = num2bytes(1, 4) - @entry = num2bytes(0x00, 8) - @phoffset = num2bytes(0x00, 8) - @shoffset = num2bytes(0x00, 8) - @flags = num2bytes(0x00, 4) - @ehsize = num2bytes(0x40, 2) - @phsize = num2bytes(0x00, 2) - @phnum = num2bytes(0x00, 2) - @shentsize = num2bytes(0x40, 2) - @shnum = num2bytes(0x08, 2) - @shstrndx = num2bytes(0x07, 2) - end + def initialize(endian: :little, type: :rel, arc: :amd64) + @ident = IDENT + @type = num2bytes(ELF_FILE_TYPE[elf(type)], 2) + @arch = arch(arc) + @version = num2bytes(1, 4) + @entry = num2bytes(0x00, 8) + @phoffset = num2bytes(0x00, 8) + @shoffset = num2bytes(0x00, 8) + @flags = num2bytes(0x00, 4) + @ehsize = num2bytes(0x40, 2) + @phsize = num2bytes(0x00, 2) + @phnum = num2bytes(0x00, 2) + @shentsize = num2bytes(0x40, 2) + @shnum = num2bytes(0x08, 2) + @shstrndx = num2bytes(0x07, 2) + end - def build = bytes.flatten.pack("C*") + def build = bytes.flatten.pack("C*") - def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil) - @entry = num2bytes(entry, 8) if check(entry, 8) - @phoffset = num2bytes(phoffset, 8) if check(phoffset, 8) - @shoffset = num2bytes(shoffset, 8) if check(shoffset, 8) - @shnum = num2bytes(shnum, 2) if check(shnum, 2) - @shstrndx = num2bytes(shstrndx, 2) if check(shstrndx, 2) - end + def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil) + @entry = num2bytes(entry, 8) if check(entry, 8) + @phoffset = num2bytes(phoffset, 8) if check(phoffset, 8) + @shoffset = num2bytes(shoffset, 8) if check(shoffset, 8) + @shnum = num2bytes(shnum, 2) if check(shnum, 2) + @shstrndx = num2bytes(shstrndx, 2) if check(shstrndx, 2) + end - private + private - def bytes = [ - @ident, @type, @arch, @version, @entry, @phoffset, - @shoffset, @flags, @ehsize, @phsize, @phnum, @shentsize, - @shnum, @shstrndx - ] + 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 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 + 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 end diff --git a/lib/caotral/linker/reader.rb b/lib/caotral/linker/reader.rb index ec36eed..e56b668 100644 --- a/lib/caotral/linker/reader.rb +++ b/lib/caotral/linker/reader.rb @@ -1,29 +1,46 @@ -class Caotral::Linker::Reader - attr_reader :sections - def self.read!(input:, debug: false, linker_options: []) - new(input:, debug:, linker_options:).read - end +require "stringio" +require_relative "elf" +module Caotral + class Linker + class Reader + attr_reader :context + def self.read!(input:, debug: false, linker_options: []) + new(input:, debug:, linker_options:).read + end - def initialize(input:, debug: false, linker_options: []) - @input = decision(input) - @bin = @input.read - @context = Caotral::Linker::ELF.new - end + def initialize(input:, debug: false, linker_options: []) + @input = decision(input) + @bin = StringIO.new(@input.read) + @context = Caotral::Linker::ELF.new + end - def read - @context - ensure - @input.close - end + def read + header = @bin.read(0x40) + ident = header[0, 16] + raise "Not ELF file" unless ident == Caotral::Linker::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 + shnum = header[60, 2].unpack("S<").first + shstrndx = header[62, 2].unpack("S<").first + @context.header.set!(entry:, phoffset:, shoffset:, shnum:, shstrndx:) - private + @bin.pos = shoffset + @context + ensure + @input.close + end - def decision(input) - case input - when String, Pathname - File.open(File.expand_path(input.to_s), "rb") - else - raise ArgumentError, "wrong input type" + private + def decision(input) + case input + when String, Pathname + File.open(File.expand_path(input.to_s), "rb") + else + raise ArgumentError, "wrong input type" + end + end end end end diff --git a/test/caotral/linker/reader_test.rb b/test/caotral/linker/reader_test.rb new file mode 100644 index 0000000..e337887 --- /dev/null +++ b/test/caotral/linker/reader_test.rb @@ -0,0 +1,10 @@ +require "caotral" +require "test/unit" +class Caotral::Linker::ReaderTest < Test::Unit::TestCase + def setup = Caotral.assemble(input: "sample/assembler/plus.s", output: "plus.o", assembler: "self") + def teardown = File.delete("plus.o") if File.exist?("plus.o") + def test_read + elf_obj = Caotral::Linker::Reader.read!(input: "plus.o", debug: false) + assert_equal elf_obj.header.shoffset.pack("C*").unpack("Q<").first, 256 + end +end From 940424b638f858de01f55f2ec7c15dc8368de337 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 30 Dec 2025 17:25:57 +0900 Subject: [PATCH 04/19] parse elf section headers --- lib/caotral/linker/elf/section.rb | 4 +- lib/caotral/linker/elf/section_header.rb | 85 ++++++++++++++--------- lib/caotral/linker/elf/sections.rb | 8 +++ lib/caotral/linker/reader.rb | 28 +++++++- sig/caotral/linker/elf/section_header.rbs | 4 ++ test/caotral/linker/reader_test.rb | 2 + 6 files changed, 94 insertions(+), 37 deletions(-) create mode 100644 sig/caotral/linker/elf/section_header.rbs diff --git a/lib/caotral/linker/elf/section.rb b/lib/caotral/linker/elf/section.rb index d7b46cb..5f9f5a7 100644 --- a/lib/caotral/linker/elf/section.rb +++ b/lib/caotral/linker/elf/section.rb @@ -1,6 +1,6 @@ class Caotral::Linker::ELF::Section - attr_accessor :name - attr_reader :section_name, :header, :body + attr_accessor :name, :header + attr_reader :section_name, :body def initialize(type:, options: {}) type_string = type.to_s.capitalize type_string = type_string.upcase if type_string == "Bss" diff --git a/lib/caotral/linker/elf/section_header.rb b/lib/caotral/linker/elf/section_header.rb index 98223ba..e426818 100644 --- a/lib/caotral/linker/elf/section_header.rb +++ b/lib/caotral/linker/elf/section_header.rb @@ -1,37 +1,58 @@ -class Caotral::Linker::ELF::SectionHeader - include Caotral::Assembler::ELF::Utils - def initialize - @name = nil - @type = nil - @flags = nil - @addr = nil - @offset = nil - @size = nil - @link = nil - @info = nil - @addralign = nil - @entsize = nil - end +module Caotral + class Linker + class ELF + class SectionHeader + SHT = { + null: 0, + progbits: 1, + symtab: 2, + strtab: 3, + rela: 4, + hash: 5, + dynamic: 6, + note: 7, + nobits: 8, + rel: 9, + shlib: 10, + dynsym: 11, + }.freeze + SHT_BY_VALUE = SHT.invert.freeze + include Caotral::Assembler::ELF::Utils + def initialize + @name = nil + @type = nil + @flags = nil + @addr = nil + @offset = nil + @size = nil + @link = nil + @info = nil + @addralign = nil + @entsize = nil + end - def build = bytes.flatten.pack("C*") + def build = bytes.flatten.pack("C*") - def set!(name: nil, type: nil, flags: nil, addr: nil, - offset: nil, size: nil, link: nil, info: nil, - addralign: nil, entsize: nil) - @name = num2bytes(name, 4) if check(name, 4) - @type = num2bytes(type, 4) if check(type, 4) - @flags = num2bytes(flags, 8) if check(flags, 8) - @addr = num2bytes(addr, 8) if check(addr, 8) - @offset = num2bytes(offset, 8) if check(offset, 8) - @size = num2bytes(size, 8) if check(size, 8) - @link = num2bytes(link, 4) if check(link, 4) - @info = num2bytes(info, 4) if check(info, 4) - @addralign = num2bytes(addralign, 8) if check(addralign, 8) - @entsize = num2bytes(entsize, 8) if check(entsize, 8) - self - end + def set!(name: nil, type: nil, flags: nil, addr: nil, + offset: nil, size: nil, link: nil, info: nil, + addralign: nil, entsize: nil) + @name = num2bytes(name, 4) if check(name, 4) + @type = num2bytes(type, 4) if check(type, 4) + @flags = num2bytes(flags, 8) if check(flags, 8) + @addr = num2bytes(addr, 8) if check(addr, 8) + @offset = num2bytes(offset, 8) if check(offset, 8) + @size = num2bytes(size, 8) if check(size, 8) + @link = num2bytes(link, 4) if check(link, 4) + @info = num2bytes(info, 4) if check(info, 4) + @addralign = num2bytes(addralign, 8) if check(addralign, 8) + @entsize = num2bytes(entsize, 8) if check(entsize, 8) + self + end - def null! = set!(name: 0, type: 0, flags: 0, addr: 0, offset: 0, size: 0, link: 0, info: 0, addralign: 0, entsize: 0) + def null! = set!(name: 0, type: 0, flags: 0, addr: 0, offset: 0, size: 0, link: 0, info: 0, addralign: 0, entsize: 0) - private def bytes = [@name, @type, @flags, @addr, @offset, @size, @link, @info, @addralign, @entsize] + private def bytes = [@name, @type, @flags, @addr, @offset, @size, @link, @info, @addralign, @entsize] + end + end + end end diff --git a/lib/caotral/linker/elf/sections.rb b/lib/caotral/linker/elf/sections.rb index e84c4a4..36fc794 100644 --- a/lib/caotral/linker/elf/sections.rb +++ b/lib/caotral/linker/elf/sections.rb @@ -5,6 +5,14 @@ def initialize = @sections = [] def each(&block) = @sections.each(&block) def add(section) = @sections << section alias << add + def size = @sections.size + alias length size + def empty? = @sections.empty? + def count(&block) + return @sections.count(&block) if block_given? + @sections.size + end + def [](index) case index when Integer diff --git a/lib/caotral/linker/reader.rb b/lib/caotral/linker/reader.rb index e56b668..dd188c1 100644 --- a/lib/caotral/linker/reader.rb +++ b/lib/caotral/linker/reader.rb @@ -1,12 +1,13 @@ require "stringio" require_relative "elf" +require_relative "elf/section" +require_relative "elf/section_header" + module Caotral class Linker class Reader attr_reader :context - def self.read!(input:, debug: false, linker_options: []) - new(input:, debug:, linker_options:).read - end + def self.read!(input:, debug: false, linker_options: []) = new(input:, debug:, linker_options:).read def initialize(input:, debug: false, linker_options: []) @input = decision(input) @@ -22,11 +23,30 @@ def read 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:) @bin.pos = shoffset + shnum.times do + sh_entry = @bin.read(shentsize) + name = sh_entry[0, 4].unpack("L<").first + type = type(sh_entry[4, 4].unpack("L<").first) + flags = sh_entry[8, 8].unpack("Q<").first + addr = sh_entry[16, 8].unpack("Q<").first + offset = sh_entry[24, 8].unpack("Q<").first + size = sh_entry[32, 8].unpack("Q<").first + link = sh_entry[40, 4].unpack("L<").first + info = sh_entry[44, 4].unpack("L<").first + addralign = sh_entry[48, 8].unpack("Q<").first + entsize = sh_entry[56, 8].unpack("Q<").first + section_header = Caotral::Linker::ELF::SectionHeader.new + section_header.set!(name:, type:, flags:, addr:, offset:, size:, link:, info:, addralign:, entsize:) + section = Caotral::Linker::ELF::Section.new(type:) + section.header = section_header + @context.sections.add(section) + end @context ensure @input.close @@ -41,6 +61,8 @@ def decision(input) raise ArgumentError, "wrong input type" end end + + def type(num) = Caotral::Linker::ELF::SectionHeader::SHT_BY_VALUE.fetch(num, :unknown) end end end diff --git a/sig/caotral/linker/elf/section_header.rbs b/sig/caotral/linker/elf/section_header.rbs new file mode 100644 index 0000000..a96d8fb --- /dev/null +++ b/sig/caotral/linker/elf/section_header.rbs @@ -0,0 +1,4 @@ +class Caotral::Linker::ELF::SectionHeader + SHT: Hash[Symbol, Integer] + SHT_BY_VALUE: Hash[Integer, Symbol] +end diff --git a/test/caotral/linker/reader_test.rb b/test/caotral/linker/reader_test.rb index e337887..6c10f07 100644 --- a/test/caotral/linker/reader_test.rb +++ b/test/caotral/linker/reader_test.rb @@ -6,5 +6,7 @@ def teardown = File.delete("plus.o") if File.exist?("plus.o") def test_read elf_obj = Caotral::Linker::Reader.read!(input: "plus.o", debug: false) assert_equal elf_obj.header.shoffset.pack("C*").unpack("Q<").first, 256 + assert_equal elf_obj.sections.size, 8 + assert_equal elf_obj.sections[0].section_name, "null" end end From 858ceced3e4ead19e1aac49d4582990a4152655e Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Tue, 30 Dec 2025 23:17:47 +0900 Subject: [PATCH 05/19] parse shstrtab --- lib/caotral/linker/elf/header.rb | 26 +++++++++++++++++++++- lib/caotral/linker/elf/section.rb | 25 ++++++++++++--------- lib/caotral/linker/elf/section/shstrtab.rb | 6 ----- lib/caotral/linker/elf/section/strtab.rb | 17 ++++++++++---- lib/caotral/linker/elf/section_header.rb | 16 +++++++++++++ lib/caotral/linker/reader.rb | 12 ++++++++-- sig/caotral/linker/elf/section_header.rbs | 21 +++++++++++++++++ test/caotral/linker/reader_test.rb | 3 +++ 8 files changed, 102 insertions(+), 24 deletions(-) delete mode 100644 lib/caotral/linker/elf/section/shstrtab.rb diff --git a/lib/caotral/linker/elf/header.rb b/lib/caotral/linker/elf/header.rb index 4b4d167..cb61701 100644 --- a/lib/caotral/linker/elf/header.rb +++ b/lib/caotral/linker/elf/header.rb @@ -35,8 +35,20 @@ def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil) @shstrndx = num2bytes(shstrndx, 2) if check(shstrndx, 2) end - private + def ehsize = get(:ehsize) + def phsize = get(:phsize) + def phnum = get(:phnum) + def shentsize = get(:shentsize) + def shnum = get(:shnum) + def shstrndx = get(:shstrndx) + + LONG_TYPES = %w[entry phoffset shoffset].freeze + INT_TYPES = %w[type version].freeze + SHORT_TYPES = %w[ehsize phsize phnum shentsize shnum shstrndx].freeze + CHAR_TYPES = %w[arch flags].freeze + private_constant :LONG_TYPES, :INT_TYPES, :SHORT_TYPES, :CHAR_TYPES + private def bytes = [ @ident, @type, @arch, @version, @entry, @phoffset, @shoffset, @flags, @ehsize, @phsize, @phnum, @shentsize, @@ -62,6 +74,18 @@ def elf(type) :NONE end end + + def get(type) + val = instance_variable_get(:"@#{type.to_s}").pack("C*") + case type.to_s.downcase + when *LONG_TYPES; val.unpack("Q<") + when *INT_TYPES; val.unpack("L<") + when *SHORT_TYPES; val.unpack("S<") + when *CHAR_TYPES; val.unpack("C<") + else + raise "not specified: #{type}" + end.first + end end end end diff --git a/lib/caotral/linker/elf/section.rb b/lib/caotral/linker/elf/section.rb index 5f9f5a7..f61d663 100644 --- a/lib/caotral/linker/elf/section.rb +++ b/lib/caotral/linker/elf/section.rb @@ -1,13 +1,16 @@ -class Caotral::Linker::ELF::Section - attr_accessor :name, :header - attr_reader :section_name, :body - def initialize(type:, options: {}) - type_string = type.to_s.capitalize - type_string = type_string.upcase if type_string == "Bss" - # section_name is extra information about section type - @section_name = type_string.downcase - # name is used in section header string table in elf file - @name = section_name == "null" ? "" : "\0.#{section_name}" - @header, @body = nil, nil +class Caotral::Linker + class ELF + class Section + attr_reader :name, :section_name + attr_accessor :header, :body + def initialize(type:, section_name: nil, options: {}) + type_string = type.to_s.capitalize + type_string = type_string.upcase if type_string == "Bss" + @section_name = (section_name.nil? ? type_string : section_name).to_s.downcase + # name is used in section header string table in elf file + @name = @section_name == "null" ? "" : "\0.#{@section_name}" + @header, @body = nil, nil + end + end end end diff --git a/lib/caotral/linker/elf/section/shstrtab.rb b/lib/caotral/linker/elf/section/shstrtab.rb deleted file mode 100644 index 244e5e7..0000000 --- a/lib/caotral/linker/elf/section/shstrtab.rb +++ /dev/null @@ -1,6 +0,0 @@ -class Caotral::Linker::ELF::Section::Shstrtab - include Caotral::Assembler::ELF::Utils - def initialize(**opts) = @name = [] - def build = bytes.flatten.pack("C*") - def set!(name:) = (@name << name!(name); self) -end diff --git a/lib/caotral/linker/elf/section/strtab.rb b/lib/caotral/linker/elf/section/strtab.rb index f44e311..c456957 100644 --- a/lib/caotral/linker/elf/section/strtab.rb +++ b/lib/caotral/linker/elf/section/strtab.rb @@ -1,5 +1,14 @@ -class Caotral::Linker::ELF::Section::Strtab - include Caotral::Assembler::ELF::Utils - def initialize(names = "\0main\0", **opts) = @names = names - def build = @names.bytes.pack("C*") +module Caotral + class Linker + class ELF + class Section + class Strtab + include Caotral::Assembler::ELF::Utils + attr_reader :names + def initialize(names = "\0main\0", **opts) = @names = names + def build = @names.bytes.pack("C*") + end + end + end + end end diff --git a/lib/caotral/linker/elf/section_header.rb b/lib/caotral/linker/elf/section_header.rb index e426818..667d449 100644 --- a/lib/caotral/linker/elf/section_header.rb +++ b/lib/caotral/linker/elf/section_header.rb @@ -50,8 +50,24 @@ def set!(name: nil, type: nil, flags: nil, addr: nil, end def null! = set!(name: 0, type: 0, flags: 0, addr: 0, offset: 0, size: 0, link: 0, info: 0, addralign: 0, entsize: 0) + def name = get(:name) + def offset = get(:offset) + def size = get(:size) + LONG_TYPES = %w[flags addr offset size addralign entsize].freeze + INT_TYPES = %w[name type link info].freeze + + private_constant :LONG_TYPES, :INT_TYPES private def bytes = [@name, @type, @flags, @addr, @offset, @size, @link, @info, @addralign, @entsize] + private def get(type) + val = instance_variable_get("@#{type.to_s}").pack("C*") + case type.to_s + when *INT_TYPES; val.unpack("L<") + when *LONG_TYPES; val.unpack("Q<") + else + raise "not specified: #{type}" + end.first + end end end end diff --git a/lib/caotral/linker/reader.rb b/lib/caotral/linker/reader.rb index dd188c1..9127e4b 100644 --- a/lib/caotral/linker/reader.rb +++ b/lib/caotral/linker/reader.rb @@ -2,6 +2,7 @@ require_relative "elf" require_relative "elf/section" require_relative "elf/section_header" +require_relative "elf/section/strtab" module Caotral class Linker @@ -29,7 +30,7 @@ def read @context.header.set!(entry:, phoffset:, shoffset:, shnum:, shstrndx:) @bin.pos = shoffset - shnum.times do + shnum.times do |i| sh_entry = @bin.read(shentsize) name = sh_entry[0, 4].unpack("L<").first type = type(sh_entry[4, 4].unpack("L<").first) @@ -43,10 +44,17 @@ def read entsize = sh_entry[56, 8].unpack("Q<").first section_header = Caotral::Linker::ELF::SectionHeader.new section_header.set!(name:, type:, flags:, addr:, offset:, size:, link:, info:, addralign:, entsize:) - section = Caotral::Linker::ELF::Section.new(type:) + section_name = i == shstrndx ? "shstrtab" : nil + args = { type:, section_name: }.compact + section = Caotral::Linker::ELF::Section.new(**args) section.header = section_header @context.sections.add(section) end + @context.sections[shstrndx].tap do |shstrtab| + @bin.pos = shstrtab.header.offset + names = @bin.read(shstrtab.header.size) + shstrtab.body = Caotral::Linker::ELF::Section::Strtab.new(names) + end @context ensure @input.close diff --git a/sig/caotral/linker/elf/section_header.rbs b/sig/caotral/linker/elf/section_header.rbs index a96d8fb..1c229d1 100644 --- a/sig/caotral/linker/elf/section_header.rbs +++ b/sig/caotral/linker/elf/section_header.rbs @@ -1,4 +1,25 @@ class Caotral::Linker::ELF::SectionHeader SHT: Hash[Symbol, Integer] SHT_BY_VALUE: Hash[Integer, Symbol] + LONG_TYPES: Array[String] + INT_TYPES: Array[String] + + def initialize: () -> void + def build: () -> String + def set!: ( + ?name: Integer?, + ?type: Integer?, + ?flags: Integer?, + ?addr: Integer?, + ?offset: Integer?, + ?size: Integer?, + ?link: Integer?, + ?info: Integer?, + ?addralign: Integer?, + ?entsize: Integer? + ) -> self + def null!: () -> self + def name: () -> Integer + def offset: () -> Integer + def size: () -> Integer end diff --git a/test/caotral/linker/reader_test.rb b/test/caotral/linker/reader_test.rb index 6c10f07..990ce31 100644 --- a/test/caotral/linker/reader_test.rb +++ b/test/caotral/linker/reader_test.rb @@ -8,5 +8,8 @@ def test_read assert_equal elf_obj.header.shoffset.pack("C*").unpack("Q<").first, 256 assert_equal elf_obj.sections.size, 8 assert_equal elf_obj.sections[0].section_name, "null" + shstrtab = elf_obj.sections[elf_obj.header.shstrndx] + assert_equal shstrtab.section_name, "shstrtab" + assert_equal shstrtab.body.names, "\0.text\0.data\0.bss\0.note\0.symtab\0.strtab\0.shstrtab\0" end end From 2d7e77383faf684a9cfbc9b7b3ee6f8451ba6302 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 31 Dec 2025 20:39:03 +0900 Subject: [PATCH 06/19] resolve shstrtab names --- lib/caotral/linker/elf/section.rb | 7 +++---- lib/caotral/linker/reader.rb | 12 +++++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/caotral/linker/elf/section.rb b/lib/caotral/linker/elf/section.rb index f61d663..8130ba7 100644 --- a/lib/caotral/linker/elf/section.rb +++ b/lib/caotral/linker/elf/section.rb @@ -1,16 +1,15 @@ class Caotral::Linker class ELF class Section - attr_reader :name, :section_name - attr_accessor :header, :body + attr_accessor :header, :body, :section_name def initialize(type:, section_name: nil, options: {}) type_string = type.to_s.capitalize type_string = type_string.upcase if type_string == "Bss" @section_name = (section_name.nil? ? type_string : section_name).to_s.downcase - # name is used in section header string table in elf file - @name = @section_name == "null" ? "" : "\0.#{@section_name}" @header, @body = nil, nil end + + def name = @section_name == "null" ? "" : "\0.#{@section_name}" end end end diff --git a/lib/caotral/linker/reader.rb b/lib/caotral/linker/reader.rb index 9127e4b..a5100b4 100644 --- a/lib/caotral/linker/reader.rb +++ b/lib/caotral/linker/reader.rb @@ -50,11 +50,21 @@ def read section.header = section_header @context.sections.add(section) end - @context.sections[shstrndx].tap do |shstrtab| + shstrtab = @context.sections[shstrndx].tap do |shstrtab| @bin.pos = shstrtab.header.offset names = @bin.read(shstrtab.header.size) shstrtab.body = Caotral::Linker::ELF::Section::Strtab.new(names) + shstrtab end + + names = shstrtab.body.names + + @context.sections.each_with_index do |section, i| + next if i == shstrndx || section.section_name.empty? + offset = section.header.name + section.section_name = names.byteslice(offset..).split("\0", 2).first + end + @context ensure @input.close From 9a7f8ec893aeeba70ea160ec77e6a37b75379310 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 31 Dec 2025 21:21:03 +0900 Subject: [PATCH 07/19] read bodies --- lib/caotral/linker/elf/section_header.rb | 1 + lib/caotral/linker/reader.rb | 18 ++++++++++++++---- test/caotral/linker/reader_test.rb | 2 ++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/caotral/linker/elf/section_header.rb b/lib/caotral/linker/elf/section_header.rb index 667d449..fae0b56 100644 --- a/lib/caotral/linker/elf/section_header.rb +++ b/lib/caotral/linker/elf/section_header.rb @@ -53,6 +53,7 @@ def null! = set!(name: 0, type: 0, flags: 0, addr: 0, offset: 0, size: 0, link: def name = get(:name) def offset = get(:offset) def size = get(:size) + def type = SHT_BY_VALUE[@type.pack("C*").unpack("L<").first] LONG_TYPES = %w[flags addr offset size addralign entsize].freeze INT_TYPES = %w[name type link info].freeze diff --git a/lib/caotral/linker/reader.rb b/lib/caotral/linker/reader.rb index a5100b4..9fdfd79 100644 --- a/lib/caotral/linker/reader.rb +++ b/lib/caotral/linker/reader.rb @@ -33,7 +33,7 @@ def read shnum.times do |i| sh_entry = @bin.read(shentsize) name = sh_entry[0, 4].unpack("L<").first - type = type(sh_entry[4, 4].unpack("L<").first) + type_val = sh_entry[4, 4].unpack("L<").first flags = sh_entry[8, 8].unpack("Q<").first addr = sh_entry[16, 8].unpack("Q<").first offset = sh_entry[24, 8].unpack("Q<").first @@ -42,10 +42,11 @@ def read info = sh_entry[44, 4].unpack("L<").first addralign = sh_entry[48, 8].unpack("Q<").first entsize = sh_entry[56, 8].unpack("Q<").first + type_sym = type(type_val) section_header = Caotral::Linker::ELF::SectionHeader.new - section_header.set!(name:, type:, flags:, addr:, offset:, size:, link:, info:, addralign:, entsize:) + section_header.set!(name:, type: type_val, flags:, addr:, offset:, size:, link:, info:, addralign:, entsize:) section_name = i == shstrndx ? "shstrtab" : nil - args = { type:, section_name: }.compact + args = { type: type_sym, section_name: }.compact section = Caotral::Linker::ELF::Section.new(**args) section.header = section_header @context.sections.add(section) @@ -60,9 +61,18 @@ def read names = shstrtab.body.names @context.sections.each_with_index do |section, i| - next if i == shstrndx || section.section_name.empty? + next if i == shstrndx || section.header.name == 0 offset = section.header.name section.section_name = names.byteslice(offset..).split("\0", 2).first + type = section.header.type + @bin.pos = section.header.offset + body_bin = @bin.read(section.header.size) + section.body = case type + when :strtab + Caotral::Linker::ELF::Section::Strtab.new(body_bin) + when :progbits + body_bin + end end @context diff --git a/test/caotral/linker/reader_test.rb b/test/caotral/linker/reader_test.rb index 990ce31..13d1fa9 100644 --- a/test/caotral/linker/reader_test.rb +++ b/test/caotral/linker/reader_test.rb @@ -11,5 +11,7 @@ def test_read shstrtab = elf_obj.sections[elf_obj.header.shstrndx] assert_equal shstrtab.section_name, "shstrtab" assert_equal shstrtab.body.names, "\0.text\0.data\0.bss\0.note\0.symtab\0.strtab\0.shstrtab\0" + assert_equal elf_obj.sections[1].section_name, ".text" + assert_equal elf_obj.sections[1].header.size, 50 end end From a1e6bc070c35cb64c96600df3c8b0143d56bcdbb Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 31 Dec 2025 21:50:29 +0900 Subject: [PATCH 08/19] add writer options for linker --- lib/caotral/linker/writer.rb | 8 ++++---- sig/caotral/linker/writer.rbs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 689b84b..df557c2 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -1,8 +1,8 @@ class Caotral::Linker::Writer - attr_reader :elf_obj, :output, :debug - def self.write!(elf_obj:, output:, debug: false) - new(elf_obj:, output:, debug:).write + attr_reader :elf_obj, :output, :entry, :debug + def self.write!(elf_obj:, output:, entry: nil, debug: false) + new(elf_obj:, output:, entry:, debug:).write end - def initialize(elf_obj:, output:, debug: false) = @elf_obj, @output, @debug = elf_obj, output, debug + def initialize(elf_obj:, output:, entry: nil, debug: false) = @elf_obj, @output, @entry, @debug = elf_obj, output, entry, debug def write = output end diff --git a/sig/caotral/linker/writer.rbs b/sig/caotral/linker/writer.rbs index fdc3288..d4d5f9d 100644 --- a/sig/caotral/linker/writer.rbs +++ b/sig/caotral/linker/writer.rbs @@ -1,5 +1,5 @@ class Caotral::Linker::Writer - def self.write!: (elf_obj: Caotral::Linker::ELF, output: String, ?debug: bool) -> String - def initialize: (elf_obj: Caotral::Linker::ELF, output: String, ?debug: bool) -> void + def self.write!: (elf_obj: Caotral::Linker::ELF, output: String, ?entry: Integer, ?debug: bool) -> String + def initialize: (elf_obj: Caotral::Linker::ELF, output: String, ?entry: Integer, ?debug: bool) -> void def write: () -> String end From 46eb9b9c3b8c8283f3e0a2a6c35fc6ced051013c Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Wed, 31 Dec 2025 23:58:51 +0900 Subject: [PATCH 09/19] write program header and text section --- lib/caotral/linker.rb | 2 ++ lib/caotral/linker/elf/header.rb | 5 ++- lib/caotral/linker/elf/program_header.rb | 32 +++++++++++++++++ lib/caotral/linker/elf/sections.rb | 4 ++- lib/caotral/linker/writer.rb | 42 +++++++++++++++++++---- sig/caotral/linker/elf/program_header.rbs | 14 ++++++++ test/caotral/linker/writer_test.rb | 18 ++++++++++ 7 files changed, 109 insertions(+), 8 deletions(-) create mode 100644 lib/caotral/linker/elf/program_header.rb create mode 100644 sig/caotral/linker/elf/program_header.rbs create mode 100644 test/caotral/linker/writer_test.rb diff --git a/lib/caotral/linker.rb b/lib/caotral/linker.rb index 5294aa7..7521491 100644 --- a/lib/caotral/linker.rb +++ b/lib/caotral/linker.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true require_relative "linker/reader" +require_relative "linker/writer" + module Caotral class Linker def self.link!(input:, output: "a.out", linker: "mold", debug: false, shared: false) = new(input:, output:, linker:, debug:, shared:).link diff --git a/lib/caotral/linker/elf/header.rb b/lib/caotral/linker/elf/header.rb index cb61701..6b865a7 100644 --- a/lib/caotral/linker/elf/header.rb +++ b/lib/caotral/linker/elf/header.rb @@ -27,9 +27,12 @@ def initialize(endian: :little, type: :rel, arc: :amd64) def build = bytes.flatten.pack("C*") - def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil) + def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil, phsize: nil, phnum: nil, ehsize: nil) @entry = num2bytes(entry, 8) if check(entry, 8) @phoffset = num2bytes(phoffset, 8) if check(phoffset, 8) + @phsize = num2bytes(phsize, 2) if check(phsize, 2) + @phnum = num2bytes(phnum, 2) if check(phnum, 2) + @ehsize = num2bytes(ehsize, 2) if check(ehsize, 2) @shoffset = num2bytes(shoffset, 8) if check(shoffset, 8) @shnum = num2bytes(shnum, 2) if check(shnum, 2) @shstrndx = num2bytes(shstrndx, 2) if check(shstrndx, 2) diff --git a/lib/caotral/linker/elf/program_header.rb b/lib/caotral/linker/elf/program_header.rb new file mode 100644 index 0000000..a5177ec --- /dev/null +++ b/lib/caotral/linker/elf/program_header.rb @@ -0,0 +1,32 @@ +module Caotral + class Linker + class ELF + class ProgramHeader + include Caotral::Assembler::ELF::Utils + def initialize + @type = num2bytes(0, 4) + @flags = num2bytes(0, 4) + @offset = num2bytes(0, 8) + @vaddr = num2bytes(0, 8) + @paddr = num2bytes(0, 8) + @filesz = num2bytes(0, 8) + @memsz = num2bytes(0, 8) + @align = num2bytes(0, 8) + end + def build = bytes.flatten.pack("C*") + def set!(type: nil, flags: nil, offset: nil, vaddr: nil, paddr: nil, filesz: nil, memsz: nil, align: nil) + @type = num2bytes(type, 4) if check(type, 4) + @flags = num2bytes(flags, 4) if check(flags, 4) + @offset = num2bytes(offset, 8) if check(offset, 8) + @vaddr = num2bytes(vaddr, 8) if check(vaddr, 8) + @paddr = num2bytes(paddr, 8) if check(paddr, 8) + @filesz = num2bytes(filesz, 8) if check(filesz, 8) + @memsz = num2bytes(memsz, 8) if check(memsz, 8) + @align = num2bytes(align, 8) if check(align, 8) + self + end + private def bytes = [@type, @flags, @offset, @vaddr, @paddr, @filesz, @memsz, @align] + end + end + end +end diff --git a/lib/caotral/linker/elf/sections.rb b/lib/caotral/linker/elf/sections.rb index 36fc794..d58c90a 100644 --- a/lib/caotral/linker/elf/sections.rb +++ b/lib/caotral/linker/elf/sections.rb @@ -18,7 +18,9 @@ def [](index) when Integer @sections[index] when String, Symbol - @sections.find { _1.section_name.to_s == index.to_s } + index_string = index.to_s + index_string.unshift(".") unless index_string.start_with?(".") + @sections.find { it.section_name == index_string } else raise ArgumentError, "Invalid index type: #{index.class}" end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index df557c2..1135f21 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -1,8 +1,38 @@ -class Caotral::Linker::Writer - attr_reader :elf_obj, :output, :entry, :debug - def self.write!(elf_obj:, output:, entry: nil, debug: false) - new(elf_obj:, output:, entry:, debug:).write +require_relative "elf/program_header" +module Caotral + class Linker + class Writer + attr_reader :elf_obj, :output, :entry, :debug + def self.write!(elf_obj:, output:, entry: nil, debug: false) + new(elf_obj:, output:, entry:, debug:).write + end + def initialize(elf_obj:, output:, entry: nil, debug: false) + @elf_obj, @output, @entry, @debug = elf_obj, output, entry, debug + end + def write + f = File.open(@output, "wb") + phoffset, phnum, phsize, ehsize = 64, 1, 56, 64 + @elf_obj.header.set!(phoffset:, phnum:, phsize:, ehsize:) + ph = Caotral::Linker::ELF::ProgramHeader.new + text_section = @elf_obj.sections[".text"] + filesz = text_section.header.size + memsz = filesz + offset = text_offset = 0x1000 + base_addr = 0x400000 + align = 0x1000 + vaddr = base_addr + text_offset + paddr = base_addr + text_offset + type, flags = 1, 5 + ph.set!(type:, offset:, vaddr:, paddr:, filesz:, memsz:, flags:, align:) + f.write(@elf_obj.header.build) + f.write(ph.build) + gap = [text_offset - f.pos, 0].max + f.write("\0" * gap) + f.write(text_section.body) + output + ensure + f.close if f + end + end end - def initialize(elf_obj:, output:, entry: nil, debug: false) = @elf_obj, @output, @entry, @debug = elf_obj, output, entry, debug - def write = output end diff --git a/sig/caotral/linker/elf/program_header.rbs b/sig/caotral/linker/elf/program_header.rbs new file mode 100644 index 0000000..e8391bf --- /dev/null +++ b/sig/caotral/linker/elf/program_header.rbs @@ -0,0 +1,14 @@ +class Caotral::Linker::ELF::ProgramHeader + def initialize: () -> void + def build: () -> String + def set!: ( + ?type: Integer?, + ?flags: Integer?, + ?offset: Integer?, + ?vaddr: Integer?, + ?paddr: Integer?, + ?filesz: Integer?, + ?memsz: Integer?, + ?align: Integer? + ) -> self +end diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb new file mode 100644 index 0000000..a8f0e92 --- /dev/null +++ b/test/caotral/linker/writer_test.rb @@ -0,0 +1,18 @@ +require "caotral" +require "test/unit" +class Caotral::Linker::WriterTest < Test::Unit::TestCase + def setup + Caotral.assemble(input: "sample/assembler/plus.s", assembler: "self", output: "plus.o") + @elf_obj = Caotral::Linker::Reader.read!(input: "plus.o", debug: false) + end + def teardown + File.delete("plus.o") if File.exist?("plus.o") + File.delete("written.o") if File.exist?("written.o") + end + def test_write + written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "written.o", debug: false) + read_written_elf = Caotral::Linker::Reader.read!(input: written_output, debug: false) + assert_equal @elf_obj.header.shoffset.pack("C*").unpack("Q<").first, read_written_elf.header.shoffset.pack("C*").unpack("Q<").first + assert_equal @elf_obj.sections.size, read_written_elf.sections.size + end +end From 420424c91edb9d57d9e4b0674ec3746a8e79adaf Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 1 Jan 2026 00:43:11 +0900 Subject: [PATCH 10/19] output executable file --- lib/caotral/linker/elf/header.rb | 4 +++- lib/caotral/linker/writer.rb | 3 ++- sample/assembler/plus.s | 4 +++- sig/caotral/linker/elf/header.rbs | 13 +++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 sig/caotral/linker/elf/header.rbs diff --git a/lib/caotral/linker/elf/header.rb b/lib/caotral/linker/elf/header.rb index 6b865a7..5533506 100644 --- a/lib/caotral/linker/elf/header.rb +++ b/lib/caotral/linker/elf/header.rb @@ -27,7 +27,8 @@ def initialize(endian: :little, type: :rel, arc: :amd64) def build = bytes.flatten.pack("C*") - def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil, phsize: nil, phnum: nil, ehsize: nil) + def set!(type: 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) @entry = num2bytes(entry, 8) if check(entry, 8) @phoffset = num2bytes(phoffset, 8) if check(phoffset, 8) @phsize = num2bytes(phsize, 2) if check(phsize, 2) @@ -36,6 +37,7 @@ def set!(entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil, ph @shoffset = num2bytes(shoffset, 8) if check(shoffset, 8) @shnum = num2bytes(shnum, 2) if check(shnum, 2) @shstrndx = num2bytes(shstrndx, 2) if check(shstrndx, 2) + self end def ehsize = get(:ehsize) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 1135f21..c09d166 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -12,7 +12,7 @@ def initialize(elf_obj:, output:, entry: nil, debug: false) def write f = File.open(@output, "wb") phoffset, phnum, phsize, ehsize = 64, 1, 56, 64 - @elf_obj.header.set!(phoffset:, phnum:, phsize:, ehsize:) + header = @elf_obj.header.set!(type: 2, phoffset:, phnum:, phsize:, ehsize:) ph = Caotral::Linker::ELF::ProgramHeader.new text_section = @elf_obj.sections[".text"] filesz = text_section.header.size @@ -23,6 +23,7 @@ def write vaddr = base_addr + text_offset paddr = base_addr + text_offset type, flags = 1, 5 + header.set!(entry: @entry || base_addr + text_offset) ph.set!(type:, offset:, vaddr:, paddr:, filesz:, memsz:, flags:, align:) f.write(@elf_obj.header.build) f.write(ph.build) diff --git a/sample/assembler/plus.s b/sample/assembler/plus.s index 5a725d2..d846f2a 100644 --- a/sample/assembler/plus.s +++ b/sample/assembler/plus.s @@ -28,4 +28,6 @@ main: push rax mov rsp, rbp pop rbp - ret + mov rdi, rax + mov rax, 0x3c + syscall diff --git a/sig/caotral/linker/elf/header.rbs b/sig/caotral/linker/elf/header.rbs new file mode 100644 index 0000000..1593ffc --- /dev/null +++ b/sig/caotral/linker/elf/header.rbs @@ -0,0 +1,13 @@ +class Caotral::Linker::ELF::Header + def set!: ( + ?type: Integer?, + ?entry: Integer?, + ?phoffset: Integer?, + ?shoffset: Integer?, + ?shnum: Integer?, + ?shstrndx: Integer?, + ?phsize: Integer?, + ?phnum: Integer?, + ?ehsize: Integer? + ) -> self +end From f1b56c36ec1f0098e08452387fff5efaff48af75 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 1 Jan 2026 01:00:59 +0900 Subject: [PATCH 11/19] fix sample assembler in tests --- test/caotral/assembler_test.rb | 18 +++++++++--------- test/caotral/linker/reader_test.rb | 4 ++-- test/caotral/linker/writer_test.rb | 17 +++++++++++++++++ 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/test/caotral/assembler_test.rb b/test/caotral/assembler_test.rb index 780c0e2..5a27538 100644 --- a/test/caotral/assembler_test.rb +++ b/test/caotral/assembler_test.rb @@ -46,9 +46,9 @@ def test_assembler_command def dumped_references [ - [127, 69, 76, 70, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 62, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 64, 0, 8, 0, 7, 0], # elf header + [127, 69, 76, 70, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 62, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 64, 0, 8, 0, 7, 0], # elf header [], # null section - [85, 72, 137, 229, 72, 131, 236, 0, 106, 1, 106, 2, 95, 88, 72, 1, 248, 80, 106, 3, 95, 88, 72, 15, 175, 199, 80, 106,5, 106, 4, 95, 88, 72, 41, 248, 80, 95, 88, 72, 153, 72, 247, 255, 80, 72, 137, 236, 93, 195], # text section + [85, 72, 137, 229, 72, 131, 236, 0, 106, 1, 106, 2, 95, 88, 72, 1, 248, 80, 106, 3, 95, 88, 72, 15, 175, 199, 80, 106,5, 106, 4, 95, 88, 72, 41, 248, 80, 95, 88, 72, 153, 72, 247, 255, 80, 72, 137, 236, 93, 72, 137, 199, 72, 199, 192, 60, 0, 0], # text section [], # data section [], # bss section [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 78, 85, 76, 76, 0, 0, 0, 0],# @@ -58,13 +58,13 @@ def dumped_references [0, 46, 115, 121, 109, 116, 97, 98, 0, 46, 115, 116, 114, 116, 97, 98, 0, 46, 115, 104, 115, 116, 114, 116, 97, 98, 0,46, 116, 101, 120, 116, 0, 46, 100, 97, 116, 97, 0, 46, 98, 115, 115, 0, 46, 110, 111, 116, 101, 46, 103, 110, 117, 46, 112, 114, 111, 112, 101, 114, 116, 121, 0], # shstrtab section # section headers [0]*64, # null - [1, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # text - [7, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # data - [13, 0, 0, 0, 8, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # bss - [18, 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 120, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # note - [24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 140, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0], # symtab - [32, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 188, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # strtab - [40, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # shstrtab + [1, 0, 0, 0, 1, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 61, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # text + [7, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # data + [13, 0, 0, 0, 8, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 125, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # bss + [18, 0, 0, 0, 7, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # note + [24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 0, 0, 0, 0, 0, 0, 0, 48, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 24, 0, 0, 0, 0, 0, 0, 0], # symtab + [32, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 196, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # strtab + [40, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 202, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], # shstrtab [], ] end diff --git a/test/caotral/linker/reader_test.rb b/test/caotral/linker/reader_test.rb index 13d1fa9..73fd7bf 100644 --- a/test/caotral/linker/reader_test.rb +++ b/test/caotral/linker/reader_test.rb @@ -5,13 +5,13 @@ def setup = Caotral.assemble(input: "sample/assembler/plus.s", output: "plus.o", def teardown = File.delete("plus.o") if File.exist?("plus.o") def test_read elf_obj = Caotral::Linker::Reader.read!(input: "plus.o", debug: false) - assert_equal elf_obj.header.shoffset.pack("C*").unpack("Q<").first, 256 + assert_equal elf_obj.header.shoffset.pack("C*").unpack("Q<").first, 264 assert_equal elf_obj.sections.size, 8 assert_equal elf_obj.sections[0].section_name, "null" shstrtab = elf_obj.sections[elf_obj.header.shstrndx] assert_equal shstrtab.section_name, "shstrtab" assert_equal shstrtab.body.names, "\0.text\0.data\0.bss\0.note\0.symtab\0.strtab\0.shstrtab\0" assert_equal elf_obj.sections[1].section_name, ".text" - assert_equal elf_obj.sections[1].header.size, 50 + assert_equal elf_obj.sections[1].header.size, 61 end end diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index a8f0e92..21bddb7 100644 --- a/test/caotral/linker/writer_test.rb +++ b/test/caotral/linker/writer_test.rb @@ -8,11 +8,28 @@ def setup def teardown File.delete("plus.o") if File.exist?("plus.o") File.delete("written.o") if File.exist?("written.o") + File.delete("written_exec") if File.exist?("written_exec") end def test_write written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "written.o", debug: false) read_written_elf = Caotral::Linker::Reader.read!(input: written_output, debug: false) assert_equal @elf_obj.header.shoffset.pack("C*").unpack("Q<").first, read_written_elf.header.shoffset.pack("C*").unpack("Q<").first assert_equal @elf_obj.sections.size, read_written_elf.sections.size + assert_equal 0x401000, read_written_elf.header.entry.pack("C*").unpack("Q<").first + end + + def test_execute_written + written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "written_exec", debug: false) + File.chmod(0755, "./written_exec") + IO.popen("./written_exec").close + exit_code, handle_code = check_process($?.to_i) + assert_equal(9, exit_code) + assert_equal(0, handle_code) + end + + def check_process(status) + exit_code = status >> 8 + handle_code = status & 0x7f + [exit_code, handle_code] end end From bc42bfc8738e7efb21e5915fc2b9ff1de4536580 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 1 Jan 2026 16:36:40 +0900 Subject: [PATCH 12/19] write section names --- lib/caotral/linker/elf/sections.rb | 15 ++++++++++++--- lib/caotral/linker/reader.rb | 2 +- lib/caotral/linker/writer.rb | 31 ++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/lib/caotral/linker/elf/sections.rb b/lib/caotral/linker/elf/sections.rb index d58c90a..ec8444f 100644 --- a/lib/caotral/linker/elf/sections.rb +++ b/lib/caotral/linker/elf/sections.rb @@ -18,11 +18,20 @@ def [](index) when Integer @sections[index] when String, Symbol - index_string = index.to_s - index_string.unshift(".") unless index_string.start_with?(".") - @sections.find { it.section_name == index_string } + @sections.find { it.section_name == prepend_dot(index) } else raise ArgumentError, "Invalid index type: #{index.class}" end end + + def index(name) + name = prepend_dot(name) + @sections.each_with_index do |section, idx| + return idx if section.section_name == name + end + end + private def prepend_dot(name) + str = name.to_s + str.start_with?(".") ? str : ".#{str}" + end end diff --git a/lib/caotral/linker/reader.rb b/lib/caotral/linker/reader.rb index 9fdfd79..8bc4bc6 100644 --- a/lib/caotral/linker/reader.rb +++ b/lib/caotral/linker/reader.rb @@ -45,7 +45,7 @@ def read type_sym = type(type_val) section_header = Caotral::Linker::ELF::SectionHeader.new section_header.set!(name:, type: type_val, flags:, addr:, offset:, size:, link:, info:, addralign:, entsize:) - section_name = i == shstrndx ? "shstrtab" : nil + section_name = i == shstrndx ? ".shstrtab" : nil args = { type: type_sym, section_name: }.compact section = Caotral::Linker::ELF::Section.new(**args) section.header = section_header diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index c09d166..ec8bf3c 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -2,6 +2,7 @@ module Caotral class Linker class Writer + ALLOW_SECTIONS = %w(.text .strtab .shstrtab).freeze attr_reader :elf_obj, :output, :entry, :debug def self.write!(elf_obj:, output:, entry: nil, debug: false) new(elf_obj:, output:, entry:, debug:).write @@ -30,6 +31,36 @@ def write gap = [text_offset - f.pos, 0].max f.write("\0" * gap) f.write(text_section.body) + shstrtab = @elf_obj.sections[".shstrtab"] + shstrtab_offset = f.pos + f.write(shstrtab.body.names) + shstrtab.header.set!(offset: shstrtab_offset, size: shstrtab.body.names.bytesize) + write_sections = @elf_obj.sections.select { ALLOW_SECTIONS.include?(it.section_name) || it.name == "" } + shoffset = f.pos + shstrndx = write_sections.index { it.section_name == ".shstrtab" } + shnum = write_sections.size + @elf_obj.header.set!(shoffset:, shnum:, shstrndx:) + names = @elf_obj.sections[".shstrtab"].body.names + name_offsets = {} + idx = 0 + while idx < names.bytesize + if names.getbyte(idx) == 0 + idx += 1 + next + end + start_idx = idx + idx += 1 while idx < names.bytesize && names.getbyte(idx) != 0 + name = names[start_idx...idx] + name_offsets[name] = start_idx + end + + write_sections.each do |section| + name_offset = name_offsets.fetch(section.section_name, 0) + section.header.set!(name: name_offset) + f.write(section.header.build) + end + f.seek(0) + f.write(@elf_obj.header.build) output ensure f.close if f From 1e5f4fe5f8ac6a16a31e0f9f813a8434b55fa4ad Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 1 Jan 2026 18:16:36 +0900 Subject: [PATCH 13/19] use offset_of for calc name offset in strtab --- lib/caotral/linker/elf/section/strtab.rb | 8 ++++++++ lib/caotral/linker/writer.rb | 18 ++++-------------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/caotral/linker/elf/section/strtab.rb b/lib/caotral/linker/elf/section/strtab.rb index c456957..62d4644 100644 --- a/lib/caotral/linker/elf/section/strtab.rb +++ b/lib/caotral/linker/elf/section/strtab.rb @@ -7,6 +7,14 @@ class Strtab attr_reader :names def initialize(names = "\0main\0", **opts) = @names = names def build = @names.bytes.pack("C*") + def offset_of(name) + offset = 0 + @names.split("\0").each do |n| + return offset if n == name + offset += n.bytesize + 1 + end + nil + end end end end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index ec8bf3c..2f99347 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -40,22 +40,12 @@ def write shstrndx = write_sections.index { it.section_name == ".shstrtab" } shnum = write_sections.size @elf_obj.header.set!(shoffset:, shnum:, shstrndx:) - names = @elf_obj.sections[".shstrtab"].body.names - name_offsets = {} - idx = 0 - while idx < names.bytesize - if names.getbyte(idx) == 0 - idx += 1 - next - end - start_idx = idx - idx += 1 while idx < names.bytesize && names.getbyte(idx) != 0 - name = names[start_idx...idx] - name_offsets[name] = start_idx - end + names = @elf_obj.sections[".shstrtab"].body write_sections.each do |section| - name_offset = name_offsets.fetch(section.section_name, 0) + section_name = section.section_name == "null" ? "" : section.section_name + name_offset = names.offset_of(section_name) + raise "Section name #{section_name} not found in .shstrtab" unless name_offset section.header.set!(name: name_offset) f.write(section.header.build) end From e60eea83630ab50170923b687fa8b9c7f38adf70 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 1 Jan 2026 19:34:41 +0900 Subject: [PATCH 14/19] read symtab in reader --- lib/caotral/linker/elf/section/strtab.rb | 5 +++ lib/caotral/linker/elf/section/symtab.rb | 42 ++++++++++++++++++------ lib/caotral/linker/elf/section_header.rb | 1 + lib/caotral/linker/reader.rb | 22 +++++++++++++ 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/lib/caotral/linker/elf/section/strtab.rb b/lib/caotral/linker/elf/section/strtab.rb index 62d4644..310e35a 100644 --- a/lib/caotral/linker/elf/section/strtab.rb +++ b/lib/caotral/linker/elf/section/strtab.rb @@ -15,6 +15,11 @@ def offset_of(name) end nil end + + def lookup(offset) + return "" if offset == 0 + @names.byteslice(offset..).split("\0", 2).first + end end end end diff --git a/lib/caotral/linker/elf/section/symtab.rb b/lib/caotral/linker/elf/section/symtab.rb index de51dc8..5773a06 100644 --- a/lib/caotral/linker/elf/section/symtab.rb +++ b/lib/caotral/linker/elf/section/symtab.rb @@ -1,12 +1,34 @@ -class Caotral::Linker::ELF::Section::Symtab - include Caotral::Assembler::ELF::Utils - def initialize(**opts) - @entsize = [] - @name = num2bytes(0, 4) - @info = num2bytes(0, 1) - @other = num2bytes(0, 1) - @shndx = num2bytes(0, 2) - @value = num2bytes(0, 8) - @size = num2bytes(0, 8) +module Caotral + class Linker + class ELF + class Section + class Symtab + include Caotral::Assembler::ELF::Utils + attr_accessor :name_string + def initialize(**opts) + @entsize = [] + @name = num2bytes(0, 4) + @info = num2bytes(0, 1) + @other = num2bytes(0, 1) + @shndx = num2bytes(0, 2) + @value = num2bytes(0, 8) + @size = num2bytes(0, 8) + @name_string = "" + end + + def set!(name: nil, info: nil, other: nil, shndx: nil, value: nil, size: nil) + @name = num2bytes(name, 4) if check(name, 4) + @info = num2bytes(info, 1) if check(info, 1) + @other = num2bytes(other, 1) if check(other, 1) + @shndx = num2bytes(shndx, 2) if check(shndx, 2) + @value = num2bytes(value, 8) if check(value, 8) + @size = num2bytes(size, 8) if check(size, 8) + self + end + + def name_offset = @name.pack("C*").unpack1("L<") + end + end + end end end diff --git a/lib/caotral/linker/elf/section_header.rb b/lib/caotral/linker/elf/section_header.rb index fae0b56..9c08c37 100644 --- a/lib/caotral/linker/elf/section_header.rb +++ b/lib/caotral/linker/elf/section_header.rb @@ -52,6 +52,7 @@ def set!(name: nil, type: nil, flags: nil, addr: nil, def null! = set!(name: 0, type: 0, flags: 0, addr: 0, offset: 0, size: 0, link: 0, info: 0, addralign: 0, entsize: 0) def name = get(:name) def offset = get(:offset) + def entsize = get(:entsize) def size = get(:size) def type = SHT_BY_VALUE[@type.pack("C*").unpack("L<").first] LONG_TYPES = %w[flags addr offset size addralign entsize].freeze diff --git a/lib/caotral/linker/reader.rb b/lib/caotral/linker/reader.rb index 8bc4bc6..e9ccb8f 100644 --- a/lib/caotral/linker/reader.rb +++ b/lib/caotral/linker/reader.rb @@ -3,6 +3,7 @@ require_relative "elf/section" require_relative "elf/section_header" require_relative "elf/section/strtab" +require_relative "elf/section/symtab" module Caotral class Linker @@ -70,11 +71,32 @@ def read section.body = case type when :strtab Caotral::Linker::ELF::Section::Strtab.new(body_bin) + when :symtab + symtab_entsize = section.header.entsize + count = body_bin.bytesize / symtab_entsize + count.times.map do |i| + sym_bin = body_bin[i * symtab_entsize, symtab_entsize] + name = sym_bin[0, 4].unpack1("L<") + info = sym_bin[4, 1].unpack1("C") + other = sym_bin[5, 1].unpack1("C") + shndx = sym_bin[6, 2].unpack1("S<") + value = sym_bin[8, 8].unpack1("Q<") + size = sym_bin[16, 8].unpack1("Q<") + Caotral::Linker::ELF::Section::Symtab.new.set!(name:, info:, other:, shndx:, value:, size:) + end when :progbits body_bin end end + strtab = @context.sections[".strtab"] + @context.sections.select { it.header.type == :symtab }.each do |symtab| + symtab.body.each do |sym| + name_offset = sym.name_offset + sym.name_string = strtab.body.lookup(name_offset) + end + end + @context ensure @input.close From 06f41e3aaa9362ff43859ade443a0fc5478fe2ac Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 1 Jan 2026 20:38:43 +0900 Subject: [PATCH 15/19] support rel.text section --- lib/caotral/linker/elf/section.rb | 2 +- lib/caotral/linker/writer.rb | 7 +++---- sample/C/rel_text.c | 5 +++++ 3 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 sample/C/rel_text.c diff --git a/lib/caotral/linker/elf/section.rb b/lib/caotral/linker/elf/section.rb index 8130ba7..35249f7 100644 --- a/lib/caotral/linker/elf/section.rb +++ b/lib/caotral/linker/elf/section.rb @@ -9,7 +9,7 @@ def initialize(type:, section_name: nil, options: {}) @header, @body = nil, nil end - def name = @section_name == "null" ? "" : "\0.#{@section_name}" + def name = @section_name == "null" ? "" : "\0#{@section_name}" end end end diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 2f99347..9ffb27b 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -43,10 +43,9 @@ def write names = @elf_obj.sections[".shstrtab"].body write_sections.each do |section| - section_name = section.section_name == "null" ? "" : section.section_name - name_offset = names.offset_of(section_name) - raise "Section name #{section_name} not found in .shstrtab" unless name_offset - section.header.set!(name: name_offset) + lookup_name = section.name.sub(/\A\0/, "") + name_offset = names.offset_of(lookup_name) + section.header.set!(name: name_offset) if name_offset f.write(section.header.build) end f.seek(0) diff --git a/sample/C/rel_text.c b/sample/C/rel_text.c new file mode 100644 index 0000000..492e001 --- /dev/null +++ b/sample/C/rel_text.c @@ -0,0 +1,5 @@ +int ext_func(int); + +int call_ext(int x) { + return ext_func(x); +} From 2175ff46959735b8298477064b2d2a614de1bb00 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Thu, 1 Jan 2026 22:01:59 +0900 Subject: [PATCH 16/19] parse REL/RELA sections --- lib/caotral/linker/elf/section/rel.rb | 36 +++++++++++++++++++++++ lib/caotral/linker/reader.rb | 13 ++++++++ sig/caotral/linker/elf/section/rel.rbs | 12 ++++++++ sig/caotral/linker/elf/section/symtab.rbs | 14 +++++++++ sig/caotral/linker/elf/section_header.rbs | 1 + 5 files changed, 76 insertions(+) create mode 100644 lib/caotral/linker/elf/section/rel.rb create mode 100644 sig/caotral/linker/elf/section/rel.rbs create mode 100644 sig/caotral/linker/elf/section/symtab.rbs diff --git a/lib/caotral/linker/elf/section/rel.rb b/lib/caotral/linker/elf/section/rel.rb new file mode 100644 index 0000000..b6ecf62 --- /dev/null +++ b/lib/caotral/linker/elf/section/rel.rb @@ -0,0 +1,36 @@ +module Caotral + class Linker + class ELF + class Section + class Rel + include Caotral::Assembler::ELF::Utils + def initialize(addend: true) + @offset = num2bytes(0, 8) + @info = num2bytes(0, 8) + @addend = addend ? num2bytes(0, 8) : false + end + + def set!(offset: nil, info: nil, addend: nil) + @offset = num2bytes(offset, 8) if check(offset, 8) + @info = num2bytes(info, 8) if check(info, 8) + @addend = num2bytes(addend, 8) if check(addend, 8) + self + end + + def build = bytes.flatten.pack("C*") + def offset = @offset.pack("C*").unpack1("Q<") + def info = @info.pack("C*").unpack1("Q<") + def addend + raise "No addend field in this REL entry" unless addend? + @addend.pack("C*").unpack1("Q<") + end + def sym = @info.pack("C*").unpack1("Q<") >> 32 + def type = @info.pack("C*").unpack1("Q<") & 0xffffffff + def addend? = !!@addend + + private def bytes = addend? ? [@offset, @info, @addend] : [@offset, @info] + end + end + end + end +end diff --git a/lib/caotral/linker/reader.rb b/lib/caotral/linker/reader.rb index e9ccb8f..1d6c7c1 100644 --- a/lib/caotral/linker/reader.rb +++ b/lib/caotral/linker/reader.rb @@ -4,6 +4,7 @@ require_relative "elf/section_header" require_relative "elf/section/strtab" require_relative "elf/section/symtab" +require_relative "elf/section/rel" module Caotral class Linker @@ -84,6 +85,18 @@ def read size = sym_bin[16, 8].unpack1("Q<") Caotral::Linker::ELF::Section::Symtab.new.set!(name:, info:, other:, shndx:, value:, size:) end + when :rel, :rela + rela = type == :rela + rel_entsize = section.header.entsize + rel_entsize = rela ? 24 : 16 if rel_entsize == 0 + count = body_bin.bytesize / rel_entsize + count.times.map do |i| + rel_bin = body_bin.byteslice(i * rel_entsize, rel_entsize) + offset = rel_bin[0, 8].unpack1("Q<") + info = rel_bin[8, 8].unpack1("Q<") + addend = rela ? rel_bin[16, 8].unpack1("q<") : nil + Caotral::Linker::ELF::Section::Rel.new(addend: rela).set!(offset:, info:, addend:) + end when :progbits body_bin end diff --git a/sig/caotral/linker/elf/section/rel.rbs b/sig/caotral/linker/elf/section/rel.rbs new file mode 100644 index 0000000..6176c32 --- /dev/null +++ b/sig/caotral/linker/elf/section/rel.rbs @@ -0,0 +1,12 @@ +class Caotral::Linker::ELF::Section::Rel + def initialize: (?addend: bool) -> void + def set!: ( + ?offset: Integer?, + ?info: Integer?, + ?addend: Integer? + ) -> self + def build: () -> String + def offset: () -> Integer + def info: () -> Integer + def addend: () -> Integer +end diff --git a/sig/caotral/linker/elf/section/symtab.rbs b/sig/caotral/linker/elf/section/symtab.rbs new file mode 100644 index 0000000..3a8c272 --- /dev/null +++ b/sig/caotral/linker/elf/section/symtab.rbs @@ -0,0 +1,14 @@ +class Caotral::Linker::ELF::Section::Symtab + attr_accessor name_string: String + + def initialize: (?opts: untyped) -> void + def set!: ( + ?name: Integer?, + ?info: Integer?, + ?other: Integer?, + ?shndx: Integer?, + ?value: Integer?, + ?size: Integer? + ) -> self + def name_offset: () -> Integer +end diff --git a/sig/caotral/linker/elf/section_header.rbs b/sig/caotral/linker/elf/section_header.rbs index 1c229d1..faae6ca 100644 --- a/sig/caotral/linker/elf/section_header.rbs +++ b/sig/caotral/linker/elf/section_header.rbs @@ -21,5 +21,6 @@ class Caotral::Linker::ELF::SectionHeader def null!: () -> self def name: () -> Integer def offset: () -> Integer + def entsize: () -> Integer def size: () -> Integer end From b331544b565828b124f6dcfb7464e281bafd6fea Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Fri, 2 Jan 2026 15:34:03 +0900 Subject: [PATCH 17/19] apply R_X86_64_PC32 relocations --- lib/caotral/linker/elf/section/symtab.rb | 1 + lib/caotral/linker/elf/section_header.rb | 2 ++ lib/caotral/linker/writer.rb | 21 +++++++++++++++++++++ sample/C/rel_text.c | 11 ++++++++--- sig/caotral/linker/elf/section_header.rbs | 1 + 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/lib/caotral/linker/elf/section/symtab.rb b/lib/caotral/linker/elf/section/symtab.rb index 5773a06..a331418 100644 --- a/lib/caotral/linker/elf/section/symtab.rb +++ b/lib/caotral/linker/elf/section/symtab.rb @@ -27,6 +27,7 @@ def set!(name: nil, info: nil, other: nil, shndx: nil, value: nil, size: nil) end def name_offset = @name.pack("C*").unpack1("L<") + def value = @value.pack("C*").unpack1("Q<") end end end diff --git a/lib/caotral/linker/elf/section_header.rb b/lib/caotral/linker/elf/section_header.rb index 9c08c37..d3fcb04 100644 --- a/lib/caotral/linker/elf/section_header.rb +++ b/lib/caotral/linker/elf/section_header.rb @@ -55,6 +55,8 @@ def offset = get(:offset) def entsize = get(:entsize) def size = get(:size) def type = SHT_BY_VALUE[@type.pack("C*").unpack("L<").first] + def info = get(:info) + def addr = get(:addr) LONG_TYPES = %w[flags addr offset size addralign entsize].freeze INT_TYPES = %w[name type link info].freeze diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index 9ffb27b..deb0daf 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -3,6 +3,8 @@ module Caotral class Linker class Writer ALLOW_SECTIONS = %w(.text .strtab .shstrtab).freeze + R_X86_64_PC32 = 2 + RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze attr_reader :elf_obj, :output, :entry, :debug def self.write!(elf_obj:, output:, entry: nil, debug: false) new(elf_obj:, output:, entry:, debug:).write @@ -16,6 +18,23 @@ def write header = @elf_obj.header.set!(type: 2, phoffset:, phnum:, phsize:, ehsize:) ph = Caotral::Linker::ELF::ProgramHeader.new text_section = @elf_obj.sections[".text"] + rel_sections = @elf_obj.sections.select { RELOCATION_SECTION_NAMES.include?(it.section_name) } + symtab = @elf_obj.sections[".symtab"] + symtab_body = symtab.body + rel_sections.each do |rel| + target = @elf_obj.sections[rel.header.info] + bytes = target.body.dup + rel.body.each do |entry| + next unless entry.type == R_X86_64_PC32 + sym = symtab_body[entry.sym] + sym_addr = sym.value + target.header.addr + offset = entry.offset + addend = entry.addend? ? entry.addend : bytes[offset, 4].unpack1("l<") + value = sym_addr + addend - (target.header.addr + offset) + bytes[offset, 4] = [value].pack("l<") + end + target.body = bytes + end filesz = text_section.header.size memsz = filesz offset = text_offset = 0x1000 @@ -26,6 +45,7 @@ def write type, flags = 1, 5 header.set!(entry: @entry || base_addr + text_offset) ph.set!(type:, offset:, vaddr:, paddr:, filesz:, memsz:, flags:, align:) + text_section.header.set!(addr: vaddr, offset: text_offset) f.write(@elf_obj.header.build) f.write(ph.build) gap = [text_offset - f.pos, 0].max @@ -48,6 +68,7 @@ def write section.header.set!(name: name_offset) if name_offset f.write(section.header.build) end + f.seek(0) f.write(@elf_obj.header.build) output diff --git a/sample/C/rel_text.c b/sample/C/rel_text.c index 492e001..2dbd2be 100644 --- a/sample/C/rel_text.c +++ b/sample/C/rel_text.c @@ -1,5 +1,10 @@ -int ext_func(int); +extern int foo(int); +int foo(int x) { return x + 1; } -int call_ext(int x) { - return ext_func(x); +int main(void) { + asm volatile( + ".long foo-.-4\n" + ".reloc .-4, R_X86_64_PC32, foo" + ); + return 0; } diff --git a/sig/caotral/linker/elf/section_header.rbs b/sig/caotral/linker/elf/section_header.rbs index faae6ca..a6c9917 100644 --- a/sig/caotral/linker/elf/section_header.rbs +++ b/sig/caotral/linker/elf/section_header.rbs @@ -23,4 +23,5 @@ class Caotral::Linker::ELF::SectionHeader def offset: () -> Integer def entsize: () -> Integer def size: () -> Integer + def info: () -> Integer end From ef657fbbe743f6cdc6d95b4fc17a29ef1c89a955 Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Fri, 2 Jan 2026 15:37:32 +0900 Subject: [PATCH 18/19] add sig files for section and strtab section --- sig/caotral/linker/elf/section.rbs | 8 ++++++++ sig/caotral/linker/elf/section/strtab.rbs | 8 ++++++++ 2 files changed, 16 insertions(+) create mode 100644 sig/caotral/linker/elf/section.rbs create mode 100644 sig/caotral/linker/elf/section/strtab.rbs diff --git a/sig/caotral/linker/elf/section.rbs b/sig/caotral/linker/elf/section.rbs new file mode 100644 index 0000000..2d7b111 --- /dev/null +++ b/sig/caotral/linker/elf/section.rbs @@ -0,0 +1,8 @@ +class Caotral::Linker::ELF::Section + attr_accessor header: untyped + attr_accessor body: untyped + attr_accessor section_name: String + + def initialize: (type: Symbol, ?section_name: String?, ?options: Hash[Symbol, untyped]) -> void + def name: () -> String +end diff --git a/sig/caotral/linker/elf/section/strtab.rbs b/sig/caotral/linker/elf/section/strtab.rbs new file mode 100644 index 0000000..e5d3c8d --- /dev/null +++ b/sig/caotral/linker/elf/section/strtab.rbs @@ -0,0 +1,8 @@ +class Caotral::Linker::ELF::Section::Strtab + attr_reader names: String + + def initialize: (?String, **untyped) -> void + def build: () -> String + def offset_of: (String) -> Integer? + def lookup: (Integer) -> String +end From 16205d5a45e7fc493148ec7c638e221e96a9fbfd Mon Sep 17 00:00:00 2001 From: "MATSUMOTO, Katsuyoshi" Date: Sat, 3 Jan 2026 14:24:59 +0900 Subject: [PATCH 19/19] add _start stub and handle PC32/PLT32 relocations --- lib/caotral/linker/writer.rb | 48 ++++++++++++++++++++---------- sample/C/rel_text.c | 12 ++++---- test/caotral/linker/reader_test.rb | 2 +- test/caotral/linker/writer_test.rb | 27 ++++++++++++----- 4 files changed, 60 insertions(+), 29 deletions(-) diff --git a/lib/caotral/linker/writer.rb b/lib/caotral/linker/writer.rb index deb0daf..8ecddcc 100644 --- a/lib/caotral/linker/writer.rb +++ b/lib/caotral/linker/writer.rb @@ -1,9 +1,13 @@ require_relative "elf/program_header" + module Caotral class Linker class Writer + include Caotral::Assembler::ELF::Utils ALLOW_SECTIONS = %w(.text .strtab .shstrtab).freeze R_X86_64_PC32 = 2 + R_X86_64_PLT32 = 4 + ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze attr_reader :elf_obj, :output, :entry, :debug def self.write!(elf_obj:, output:, entry: nil, debug: false) @@ -19,33 +23,45 @@ def write ph = Caotral::Linker::ELF::ProgramHeader.new text_section = @elf_obj.sections[".text"] rel_sections = @elf_obj.sections.select { RELOCATION_SECTION_NAMES.include?(it.section_name) } + start_bytes = [0xe8, *[0] * 4, 0x48, 0x89, 0xc7, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05] symtab = @elf_obj.sections[".symtab"] symtab_body = symtab.body + old_text = text_section.body + main_sym = symtab_body.find { |sym| sym.name_string == "main" } + text_offset = 0x1000 + base_addr = 0x400000 + align = 0x1000 + vaddr = base_addr + text_offset + paddr = base_addr + text_offset + start_len = start_bytes.length + start_addr = base_addr + text_offset + main_offset = main_sym.value + start_len + start_bytes[1, 4] = num2bytes((main_offset - 5), 4) + start_bytestring = start_bytes.pack("C*") + text_section.body = start_bytestring + old_text + type, flags = 1, 5 + filesz = text_section.body.bytesize + memsz = filesz + rel_sections.each do |rel| target = @elf_obj.sections[rel.header.info] bytes = target.body.dup rel.body.each do |entry| - next unless entry.type == R_X86_64_PC32 + next unless ALLOW_RELOCATION_TYPES.include?(entry.type) + a = entry.type == R_X86_64_PC32 ? 4 : 0 sym = symtab_body[entry.sym] - sym_addr = sym.value + target.header.addr - offset = entry.offset - addend = entry.addend? ? entry.addend : bytes[offset, 4].unpack1("l<") - value = sym_addr + addend - (target.header.addr + offset) - bytes[offset, 4] = [value].pack("l<") + target_addr = target == text_section ? vaddr : target.header.addr + sym_addr = sym.value + target_addr + (target == text_section ? start_len : 0) + sym_offset = entry.offset + (target == text_section ? start_len : 0) + sym_addend = entry.addend? ? entry.addend : bytes[sym_offset, 4].unpack1("l<") + value = sym_addr + sym_addend - (target_addr + sym_offset + a) + bytes[sym_offset, 4] = [value].pack("l<") end target.body = bytes end - filesz = text_section.header.size - memsz = filesz - offset = text_offset = 0x1000 - base_addr = 0x400000 - align = 0x1000 - vaddr = base_addr + text_offset - paddr = base_addr + text_offset - type, flags = 1, 5 header.set!(entry: @entry || base_addr + text_offset) - ph.set!(type:, offset:, vaddr:, paddr:, filesz:, memsz:, flags:, align:) - text_section.header.set!(addr: vaddr, offset: text_offset) + ph.set!(type:, offset: text_offset, vaddr:, paddr:, filesz:, memsz:, flags:, align:) + text_section.header.set!(size: text_section.body.bytesize, addr: vaddr, offset: text_offset) f.write(@elf_obj.header.build) f.write(ph.build) gap = [text_offset - f.pos, 0].max diff --git a/sample/C/rel_text.c b/sample/C/rel_text.c index 2dbd2be..b34aba5 100644 --- a/sample/C/rel_text.c +++ b/sample/C/rel_text.c @@ -1,10 +1,12 @@ -extern int foo(int); -int foo(int x) { return x + 1; } +extern int foo(void); +int foo(void) { return 0; } int main(void) { asm volatile( - ".long foo-.-4\n" - ".reloc .-4, R_X86_64_PC32, foo" + "jmp 1f\n" + ".long 0\n" + ".reloc .-4, R_X86_64_PC32, foo\n" + "1:\n" ); - return 0; + return foo(); } diff --git a/test/caotral/linker/reader_test.rb b/test/caotral/linker/reader_test.rb index 73fd7bf..9e02530 100644 --- a/test/caotral/linker/reader_test.rb +++ b/test/caotral/linker/reader_test.rb @@ -9,7 +9,7 @@ def test_read assert_equal elf_obj.sections.size, 8 assert_equal elf_obj.sections[0].section_name, "null" shstrtab = elf_obj.sections[elf_obj.header.shstrndx] - assert_equal shstrtab.section_name, "shstrtab" + assert_equal shstrtab.section_name, ".shstrtab" assert_equal shstrtab.body.names, "\0.text\0.data\0.bss\0.note\0.symtab\0.strtab\0.shstrtab\0" assert_equal elf_obj.sections[1].section_name, ".text" assert_equal elf_obj.sections[1].header.size, 61 diff --git a/test/caotral/linker/writer_test.rb b/test/caotral/linker/writer_test.rb index 21bddb7..2c544bf 100644 --- a/test/caotral/linker/writer_test.rb +++ b/test/caotral/linker/writer_test.rb @@ -7,26 +7,39 @@ def setup end def teardown File.delete("plus.o") if File.exist?("plus.o") - File.delete("written.o") if File.exist?("written.o") - File.delete("written_exec") if File.exist?("written_exec") + File.delete("write.o") if File.exist?("write.o") + File.delete("write") if File.exist?("write") + File.delete("relocatable.o") if File.exist?("relocatable.o") + File.delete("relocated_exec") if File.exist?("relocated_exec") end def test_write - written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "written.o", debug: false) + written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "write.o", debug: false) read_written_elf = Caotral::Linker::Reader.read!(input: written_output, debug: false) assert_equal @elf_obj.header.shoffset.pack("C*").unpack("Q<").first, read_written_elf.header.shoffset.pack("C*").unpack("Q<").first - assert_equal @elf_obj.sections.size, read_written_elf.sections.size + assert_equal 4, read_written_elf.sections.size assert_equal 0x401000, read_written_elf.header.entry.pack("C*").unpack("Q<").first end def test_execute_written - written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "written_exec", debug: false) - File.chmod(0755, "./written_exec") - IO.popen("./written_exec").close + written_output = Caotral::Linker::Writer.write!(elf_obj: @elf_obj, output: "write", debug: false) + File.chmod(0755, "./write") + IO.popen("./write").close exit_code, handle_code = check_process($?.to_i) assert_equal(9, exit_code) assert_equal(0, handle_code) end + def test_relocation_write_and_execute + IO.popen("gcc -c -fno-pic -fno-pie -o relocatable.o sample/C/rel_text.c").close + elf_obj = Caotral::Linker::Reader.read!(input: "relocatable.o", debug: false) + Caotral::Linker::Writer.write!(elf_obj:, output: "relocated_exec", debug: false) + File.chmod(0755, "./relocated_exec") + IO.popen("./relocated_exec").close + exit_code, _handle_code = check_process($?.to_i) + assert_equal(0, exit_code) + end + + private def check_process(status) exit_code = status >> 8 handle_code = status & 0x7f