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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/caotral.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def compile!(input:, assembler: "as", linker: "ld", output: "tmp", debug: false,
execf = "#{basename}#{File.extname(d)}"
compile(input:, output: basename+".s", debug:, shared:)
assemble(input: basename+".s", output: basename+".o", assembler:, debug:, shared:)
link(input: basename+".o", output: execf, linker:, debug:, shared:)
link(input: [basename+".o"], output: execf, linker:, debug:, shared:)
end
def compile(input:, output: "tmp.s", debug: false, shared: false)
Caotral::Compiler.compile!(input:, output:, debug:)
Expand All @@ -23,6 +23,7 @@ def assemble(input:, output: "tmp.o", debug: false, shared: false, assembler: "a
Caotral::Assembler.assemble!(input:, output:, debug:, assembler:, shared:)
end
def link(input:, output: "tmp", linker: "ld", debug: false, shared: false)
Caotral::Linker.link!(input:, output:, linker:, debug:, shared:)
inputs = Array === input ? input : [input]
Caotral::Linker.link!(inputs:, output:, linker:, debug:, shared:)
end
end
1 change: 1 addition & 0 deletions lib/caotral/binary/elf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def find_by_name(section_name) = @sections.find { |s| section_name == s.section_
def select_by_name(section_name) = @sections.select { |s| section_name == s.section_name }
def index(section_name) = @sections.index { |s| section_name == s.section_name }
def select_by_names(section_names) = @sections.select { |section| section_names.any? { |name| name === section.section_name.to_s } }
def without_sections(names) = @sections.reject { |s| names.any? { |name| name === s.section_name.to_s } }
end
end
end
1 change: 1 addition & 0 deletions lib/caotral/binary/elf/section/symtab.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def set!(name: nil, info: nil, other: nil, shndx: nil, value: nil, size: nil)
def name_offset = @name.pack("C*").unpack1("L<")
def value = @value.pack("C*").unpack1("Q<")
def info = @info.pack("C*").unpack1("C")
def shndx = @shndx.pack("C*").unpack1("S<")

private def bytes = [@name, @info, @other, @shndx, @value, @size]
end
Expand Down
21 changes: 11 additions & 10 deletions lib/caotral/linker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,21 @@

module Caotral
class Linker
def self.link!(input:, output: "a.out", linker: "mold", debug: false, shared: false) = new(input:, output:, linker:, debug:, shared:).link
def self.link!(inputs:, output: "a.out", linker: "mold", debug: false, shared: false) = new(inputs:, output:, linker:, debug:, shared:).link

def initialize(input:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false)
@input, @output, @linker = input, output, linker
def initialize(inputs:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false)
@inputs, @output, @linker = inputs, output, linker
@options = linker_options
@debug, @shared = debug, shared
end

def link(input: @input, output: @output, debug: @debug, shared: @shared)
return to_elf(input:, output:, debug:) if @linker == "self"
def link(inputs: @inputs, output: @output, debug: @debug, shared: @shared)
return to_elf(inputs:, output:, debug:) if @linker == "self"

IO.popen(link_command).close
end

def link_command(input: @input, output: @output, debug: @debug, shared: @shared)
def link_command(inputs: @inputs, output: @output)
ld_path = []

if @shared
Expand All @@ -39,18 +39,19 @@ def link_command(input: @input, output: @output, debug: @debug, shared: @shared)

ld_path << "#{libpath}/libc.so"
ld_path << "#{libpath}/crtn.o"
cmd = [@linker, "-o", @output, "-m", "elf_x86_64", *@options, *ld_path, @input].join(' ')
cmd = [@linker, "-o", @output, "-m", "elf_x86_64", *@options, *ld_path, *inputs].join(' ')
puts cmd if @debug
cmd
end

def libpath = @libpath ||= File.dirname(Dir.glob("/usr/lib*/**/crti.o").last)
def gcc_libpath = @gcc_libpath ||= File.dirname(Dir.glob("/usr/lib/gcc/x86_64-*/*/crtbegin.o").last)

def to_elf(input: @input, output: @output, debug: @debug)
elf_obj = Caotral::Binary::ELF::Reader.new(input:, debug:).read
builder = Caotral::Linker::Builder.new(elf_obj:)
def to_elf(inputs: @inputs, output: @output, debug: @debug)
elf_objs = inputs.map { |input| Caotral::Binary::ELF::Reader.new(input:, debug:).read }
builder = Caotral::Linker::Builder.new(elf_objs:)
builder.resolve_symbols
elf_obj = builder.build
Caotral::Linker::Writer.new(elf_obj:, output:, debug:).write
end
end
Expand Down
142 changes: 132 additions & 10 deletions lib/caotral/linker/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,147 @@ class Linker
class Builder
SYMTAB_BIND = { locals: 0, globals: 1, weaks: 2, }.freeze
BIND_BY_VALUE = SYMTAB_BIND.invert.freeze
RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze
attr_reader :symbols

def initialize(elf_obj:)
@elf_obj = elf_obj
def initialize(elf_objs:)
@elf_objs = elf_objs
@symbols = { locals: Set.new, globals: Set.new, weaks: Set.new }
end

def build
raise Caotral::Binary::ELF::Error, "no ELF objects to link" if @elf_objs.empty?
elf = Caotral::Binary::ELF.new
elf_obj = @elf_objs.first
first_text = elf_obj.find_by_name(".text")
null_section = Caotral::Binary::ELF::Section.new(
body: nil,
section_name: "",
header: Caotral::Binary::ELF::SectionHeader.new
)
text_section = Caotral::Binary::ELF::Section.new(
body: String.new,
section_name: ".text",
header: Caotral::Binary::ELF::SectionHeader.new
)
strtab_section = Caotral::Binary::ELF::Section.new(
body: Caotral::Binary::ELF::Section::Strtab.new("\0".b),
section_name: ".strtab",
header: Caotral::Binary::ELF::SectionHeader.new
)
symtab_section = Caotral::Binary::ELF::Section.new(
body: [],
section_name: ".symtab",
header: Caotral::Binary::ELF::SectionHeader.new
)
shstrtab_section = Caotral::Binary::ELF::Section.new(
body: Caotral::Binary::ELF::Section::Strtab.new("\0".b),
section_name: ".shstrtab",
header: Caotral::Binary::ELF::SectionHeader.new
)
rel_sections = []
elf.header = elf_obj.header.dup
strtab_names = []
@elf_objs.each do |elf_obj|
text = elf_obj.find_by_name(".text")
text_section.body << text.body unless text.nil?
strtab = elf_obj.find_by_name(".strtab")
unless strtab.nil?
strtab.body.names.split("\0").each { |name| strtab_names << name }
end
symtab = elf_obj.find_by_name(".symtab")
unless symtab.nil?
symtab.body.each do |st|
sym = Caotral::Binary::ELF::Section::Symtab.new
name, info, other, shndx, value, size = st.build.unpack("L<CCS<Q<Q<")
sym.set!(name:, info:, other:, shndx:, value:, size:)
sym.name_string = strtab.body.lookup(name) unless strtab.nil?
symtab_section.body << sym
end
end
rel_sections += elf_obj.select_by_names(RELOCATION_SECTION_NAMES)
end
strtab_section.body.names = strtab_names.to_a.sort.join("\0") + "\0"
elf.sections << null_section

text_section.header.set!(
type: 1,
flags: 6,
addralign: 16
)

elf.sections << text_section
strtab_section.header.set!(type: 3, flags: 0, addralign: 1, entsize: 0)
elf.sections << strtab_section
symtab_section.body.each do |sym|
next if sym.shndx == 0
name = strtab_section.body.offset_of(sym.name_string)
sym.set!(name:)
end

symtab_section.header.set!(
type: 2,
flags: 0,
link: elf.sections.index(strtab_section),
info: symbols.fetch(:locals, Set.new).size,
addralign: 8,
entsize: 24
)

elf.sections << symtab_section

rel_sections.each { |s| elf.sections << s.dup }

shstrtab_section.header.set!(
type: 3,
flags: 0,
addralign: 1,
entsize: 0
)
shstrtab_section_names = elf.sections.map(&:section_name).join("\0") + "\0"
shstrtab_section.body.names = shstrtab_section_names
elf.sections << shstrtab_section

@elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./]).each do |section|
elf.sections << section.dup
end
elf.select_by_names(RELOCATION_SECTION_NAMES).each do |rel_section|
rel_section.header.set!(
type: rel_type(rel_section),
flags: 0,
link: elf.sections.index(symtab_section),
info: ref_index(elf, rel_section.section_name),
addralign: 8,
entsize: rel_entsize(rel_section)
)
end

elf
end
def resolve_symbols
@elf_obj.find_by_name(".symtab").body.each do |symtab|
name = symtab.name_string
next if name.empty?
info = symtab.info
bind = BIND_BY_VALUE.fetch(info >> 4)
if bind == :globals && @symbols[bind].include?(name)
raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}"
@elf_objs.each do |elf_obj|
elf_obj.find_by_name(".symtab").body.each do |symtab|
name = symtab.name_string
next if name.empty?
info = symtab.info
bind = BIND_BY_VALUE.fetch(info >> 4)
if bind == :globals && @symbols[bind].include?(name) && symtab.shndx != 0
raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}"
end
@symbols[bind] << name
end
@symbols[bind] << name
end
@symbols
end

def ref_index(elf_obj, section_name)
raise Caotral::Binary::ELF::Error, "invalid section name: #{section_name}" if section_name.nil?
ref = elf_obj.select_by_names(section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.map { "." + it }).first
elf_obj.index(ref.section_name)
end

def rel_type(section) = section.section_name&.start_with?(".rela.") ? 4 : 9
def rel_entsize(section) = section.section_name&.start_with?(".rela.") ? 24 : 16
end
end
end
62 changes: 43 additions & 19 deletions lib/caotral/linker/writer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ def write
phoffset, phnum, phsize, ehsize = 64, 1, 56, 64
header = @elf_obj.header.set!(type: 2, phoffset:, phnum:, phsize:, ehsize:)
ph = Caotral::Binary::ELF::ProgramHeader.new
text_section = @elf_obj.find_by_name(".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.find_by_name(".symtab")
symtab = symtab_section
symtab_body = symtab.body
old_text = text_section.body
main_sym = symtab_body.find { |sym| sym.name_string == "main" }
Expand Down Expand Up @@ -61,36 +59,54 @@ def write

header.set!(entry: @entry || base_addr + 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)
text_section.header.set!(size: text_section.body.bytesize, addr: vaddr, offset: text_offset)
f.write(ph.build)
gap = [text_offset - f.pos, 0].max
f.write("\0" * gap)
f.write(text_section.body)
shstrtab = @elf_obj.find_by_name(".shstrtab")
shstrtab_offset = f.pos
f.write(shstrtab.body.names)
shstrtab.header.set!(offset: shstrtab_offset, size: shstrtab.body.names.bytesize)
symtab_offset = f.pos
symtab_section.body.each { |sym| f.write(sym.build) }
symtab_entsize = symtab_section.body.first&.build&.bytesize.to_i
symtab_size = f.pos - symtab_offset
symtab_section.header.set!(offset: symtab_offset, size: symtab_size, entsize: symtab_entsize)
strtab_offset = f.pos
f.write(strtab_section.body.build)
strtab_section.header.set!(offset: strtab_offset, size: strtab_section.body.names.bytesize)

rel_sections.each do |rel|
rel_offset = f.pos
rel.body.each { |entry| f.write(entry.build) }
rel_size = f.pos - rel_offset
entsize = rel.body.first&.build&.bytesize.to_i
rel.header.set!(offset: rel_offset, size: rel_size, entsize:)
end
offset = f.pos
names = @write_sections.map { |s| s.section_name.to_s }
if names.last != ".shstrtab"
raise Caotral::Binary::ELF::Error, "section header string table must be the last section"
end
shstrtab_section.body.names = names.uniq.join("\0") + "\0"
shstrtab_section.header.set!(offset:, size: shstrtab_section.body.names.bytesize)
f.write(shstrtab_section.body.names)
shoffset = f.pos
shstrndx = write_section_index(".shstrtab")
strtabndx = write_section_index(".strtab")
symtabndx = write_section_index(".symtab")
shnum = @write_sections.size
@elf_obj.header.set!(shoffset:, shnum:, shstrndx:)
names = @elf_obj.find_by_name(".shstrtab").body
names = shstrtab_section.body

@write_sections.each do |section|
header = section.header
lookup_name = section.section_name
name_offset = names.offset_of(lookup_name)
name, info, link, entsize = (name_offset.nil? ? 0 : name_offset), header.info, header.link, 0
if header.type == :symtab
info = section.body.size
link = strtabndx
entsize = header.entsize.nonzero? || 24
elsif [:rel, :rela].include?(header.type)
name, info, entsize = (name_offset.nil? ? 0 : name_offset), header.info, header.entsize
link = header.link
link = strtabndx if section.section_name == ".symtab"
if [:rel, :rela].include?(header.type)
link = symtabndx
info = ref_index(section)
info = ref_index(section.section_name)
end
header.set!(name:, info:, link:, entsize:)
f.write(section.header.build)
Expand All @@ -115,11 +131,19 @@ def write_order_sections
write_order.compact
end
def write_section_index(section_name) = @write_sections.index { it.section_name == section_name }
def ref_index(section)
section_name = section.section_name
ref = @elf_obj.select_by_names(section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.map { "." + it }).first
def ref_index(section_name)
ref_name = section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }
ref_name = "." + ref_name.join(".")
ref = @write_sections.find { |s| s.section_name == ref_name }
raise Caotral::Binary::ELF::Error, "cannot find reference section for #{section_name}" if ref.nil?
write_section_index(ref.section_name)
end

def text_section = @text_section ||= @write_sections.find { |s| ".text" === s.section_name.to_s }
def rel_sections = @rel_sections ||= @write_sections.select { RELOCATION_SECTION_NAMES.include?(it.section_name) }
def symtab_section = @symtab_section ||= @write_sections.find { |s| ".symtab" === s.section_name.to_s }
def strtab_section = @strtab_section ||= @write_sections.find { |s| ".strtab" === s.section_name.to_s }
def shstrtab_section = @shstrtab_section ||= @write_sections.find { |s| ".shstrtab" === s.section_name.to_s }
end
end
end
1 change: 1 addition & 0 deletions sig/caotral/binary/elf.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ class Caotral::Binary::ELF
def select_by_name: (String | Symbol) -> Array[Caotral::Binary::ELF::Section]
def index: (String | Symbol) -> Integer?
def select_by_names: (Array[String | Regexp]) -> Array[Caotral::Binary::ELF::Section]
def without_section: (String | Symbol) -> Array[Caotral::Binary::ELF::Section]
end
6 changes: 3 additions & 3 deletions sig/caotral/compiler/linker.rbs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
class Caotral::Compiler::Linker
@input: String
@inputs: Array[String]
@output: String
@linker: String
@options: Array[String]
@shared: bool
@debug: bool

def initialize: (input: String, ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool, ?debug: bool) -> void
def link: (input: String, ?output: String, ?shared: bool, ?debug: bool) -> void
def initialize: (inputs: Array[String], ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool, ?debug: bool) -> void
def link: (inputs: Array[String], ?output: String, ?shared: bool, ?debug: bool) -> void

def link_command: () -> String
def libpath: () -> String
Expand Down
12 changes: 6 additions & 6 deletions sig/caotral/linker.rbs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
class Caotral::Linker
@input: String
@inputs: Array[String]
@output: String
@linker: String
@options: Array[String]
@shared: bool
@debug: bool

def self.link!: (input: String, ?output: String, ?linker: String, ?debug: bool, ?shared: bool) -> void
def initialize: (input: String, ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool, ?debug: bool) -> void
def link: (input: String, ?output: String, ?shared: bool, ?debug: bool) -> void
def self.link!: (inputs: Array[String], ?output: String, ?linker: String, ?debug: bool, ?shared: bool) -> void
def initialize: (inputs: Array[String], ?output: String, ?linker: String, ?linker_options: Array[String], ?shared: bool, ?debug: bool) -> void
def link: (inputs: Array[String], ?output: String, ?shared: bool, ?debug: bool) -> void

def link_command: (input: String, ?output: String, ?shared: bool, ?debug: bool) -> String
def link_command: (inputs: Array[String], ?output: String, ?shared: bool, ?debug: bool) -> String
def libpath: () -> String
def gcc_libpath: () -> String
def to_elf: (input: String, ?output: String, ?debug: bool) -> String
def to_elf: (inputs: Array[String], ?output: String, ?debug: bool) -> String
end
Loading
Loading