Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/coverage/
/doc/
/pkg/
/AGENTS.md
/spec/reports/
/tmp/
/Gemfile.lock
Expand All @@ -13,3 +14,5 @@
/.gem_rbs_collection
/node_modules
/package*.json
/*.md
!/README.md
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ gemspec

gem "rake", "~> 13.0"
gem "fiddle"
gem "irb"
gem "steep"
gem "test-unit"
76 changes: 44 additions & 32 deletions lib/caotral/linker.rb
Original file line number Diff line number Diff line change
@@ -1,39 +1,51 @@
# 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"
require_relative "linker/writer"

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
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 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 = []
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"
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

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 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 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)
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

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
end
14 changes: 14 additions & 0 deletions lib/caotral/linker/elf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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
97 changes: 97 additions & 0 deletions lib/caotral/linker/elf/header.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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 build = bytes.flatten.pack("C*")

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)
@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)
self
end

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,
@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

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
end
32 changes: 32 additions & 0 deletions lib/caotral/linker/elf/program_header.rb
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions lib/caotral/linker/elf/section.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Caotral::Linker
class ELF
class Section
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
@header, @body = nil, nil
end

def name = @section_name == "null" ? "" : "\0#{@section_name}"
end
end
end
36 changes: 36 additions & 0 deletions lib/caotral/linker/elf/section/rel.rb
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions lib/caotral/linker/elf/section/strtab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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*")
def offset_of(name)
offset = 0
@names.split("\0").each do |n|
return offset if n == name
offset += n.bytesize + 1
end
nil
end

def lookup(offset)
return "" if offset == 0
@names.byteslice(offset..).split("\0", 2).first
end
end
end
end
end
end
35 changes: 35 additions & 0 deletions lib/caotral/linker/elf/section/symtab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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<")
def value = @value.pack("C*").unpack1("Q<")
end
end
end
end
end
4 changes: 4 additions & 0 deletions lib/caotral/linker/elf/section/text.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Caotral::Linker::ELF::Section::Text
def initialize = @bytes = []
def build = @bytes.flatten.pack("C*")
end
Loading