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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
14 changes: 14 additions & 0 deletions lib/caotral/linker/elf/section/strtab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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
12 changes: 12 additions & 0 deletions lib/caotral/linker/elf/section/symtab.rb
Original file line number Diff line number Diff line change
@@ -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
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
75 changes: 75 additions & 0 deletions lib/caotral/linker/elf/section_header.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
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 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 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

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
end
Loading