Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile to elf #11

Open
wants to merge 58 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
6cda7d0
change compile method to to_elf method
katsyoshi May 20, 2023
35cb1a7
fix missreading comment
katsyoshi May 20, 2023
e0c1cb4
add interface for to_elf
katsyoshi May 22, 2023
75bea27
assemble class for assembling
katsyoshi Aug 7, 2023
4780a25
pass only sample/plus.rb
katsyoshi Aug 18, 2023
eccc7a9
refactoring assemble.rb
katsyoshi Aug 24, 2023
980de72
rename assemble to assembler
katsyoshi Aug 31, 2023
f37dfcf
add rbs for refactoringed compiler.rbs
katsyoshi Sep 1, 2023
654e55c
reduction nest
katsyoshi Sep 5, 2023
2f7fbf4
refactoring testing interface
katsyoshi Sep 5, 2023
51cfad0
use inner classes for compile
katsyoshi Sep 10, 2023
4b83552
link shared library linking object files
katsyoshi Sep 10, 2023
a68eb23
pass ci
katsyoshi Sep 13, 2023
a253c01
pass ci
katsyoshi Sep 15, 2023
71abafa
replaced class files for elf sections
katsyoshi Sep 18, 2023
3294c24
replace utils for common methods
katsyoshi Sep 23, 2023
32b9f23
fix name to Shstrtab
katsyoshi Oct 3, 2023
86cbe15
fix replace variable name and use text body
katsyoshi Oct 3, 2023
3399f55
add setter name for Shstrtab
katsyoshi Oct 3, 2023
87608ea
fill section headers binary
katsyoshi Oct 8, 2023
e26363a
use directive for Assembler::ELF
katsyoshi Oct 17, 2023
0b488d4
reset hex align 2 times for bytes
katsyoshi Oct 17, 2023
f6344f8
set class name for exceptions
katsyoshi Nov 12, 2023
1f30ecd
fix split to scan in hexas method
katsyoshi Nov 14, 2023
2be5249
set null for unused section's section header
katsyoshi Dec 29, 2023
02a4f18
add :BEGIN node for ruby 3.3
katsyoshi Jan 1, 2024
603d848
check debug log
katsyoshi Jan 3, 2024
95c3ee7
change default assembler gcc to (g)as
katsyoshi Jan 7, 2024
b8291c2
fix option description using = for args value
katsyoshi Jan 8, 2024
9c225d2
notify elf header fields error and add elf header test
katsyoshi Feb 11, 2024
4fbd0f8
add test for note section
katsyoshi Mar 2, 2024
de8705c
fix note implementation
katsyoshi Mar 2, 2024
dce57ef
add test for shstrtab and fix implementation
katsyoshi Mar 2, 2024
b0fe9f9
fix types for implementations
katsyoshi Mar 2, 2024
d17933e
add shstrtab for twice
katsyoshi Mar 2, 2024
ce320e5
don't understanding of elf
katsyoshi Mar 9, 2024
ea9d844
add test for null section symtab
katsyoshi Mar 11, 2024
b286897
update rbs collection
katsyoshi May 4, 2024
c33fe2f
set name bytes
katsyoshi May 4, 2024
150a767
forget adding & for method calling
katsyoshi Jul 21, 2024
7e38863
remove hand assemble sources from each
katsyoshi Jul 22, 2024
872a780
set keyword arguments for section classes
katsyoshi Aug 7, 2024
3b23c2e
add integration test
katsyoshi Aug 10, 2024
a10bd59
tmp
katsyoshi Aug 11, 2024
2d36811
check header from integration file
katsyoshi Aug 12, 2024
f523e05
create test assembler file
katsyoshi Aug 19, 2024
b53ea98
set section headers
katsyoshi Aug 24, 2024
ec65088
set paddings
katsyoshi Aug 25, 2024
87e299f
fix elf header for section header offset bytes
katsyoshi Aug 25, 2024
f06c56d
remove unnecessary test
katsyoshi Aug 31, 2024
50427a2
wrong setup and teadown
katsyoshi Aug 31, 2024
b397ffd
add using assembler option and commented default linker
katsyoshi Sep 12, 2024
f8476fe
fix error class for rbs
katsyoshi Sep 13, 2024
a8607b9
use self made note
katsyoshi Sep 15, 2024
c184dac
migrate building elf file to ELF class from Assembler class
katsyoshi Sep 15, 2024
384b7a1
add mov operation
katsyoshi Sep 19, 2024
ad77519
support if condition opecodes
katsyoshi Sep 19, 2024
eea7449
support else jump
katsyoshi Sep 19, 2024
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
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require "steep/cli"
task default: %i[test]

Rake::TestTask.new do |t|
t.test_files = FileList['test/test*.rb']
t.test_files = FileList['test/test*.rb', 'test/**/test*.rb']
end

namespace :steep do
Expand Down
8 changes: 5 additions & 3 deletions exe/vaporware
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ require "vaporware"
require "optparse"
opt = OptionParser.new
options = {}
opt.on("-c", "--compiler [VAL]", "this option is selecting compiler precompiled file, default: gcc") { |v| options[:compiler] = v }
opt.on("-c", "--compiler[=VAL]", "this option is selecting compiler precompiled file, default: \"self\"") { |v| options[:compiler] = v }
opt.on("-a", "--assembler[=VAL]", "this option is selecting assembler assembler file, default: \"as\"") { |v| options[:assembler] = v }
opt.on("-D", "--debug") { |v| options[:debug] = v }
opt.on("-o", "--objects [VAL]") { |v| options[:dest] = v }
opt.on("-o", "--objects[=VAL]") { |v| options[:dest] = v }
opt.on("--compiler-options[=VAL]", "compiler options") { |v| options[:compiler_options] = v.split(",") }
opt.on("-s", "--shared") { |v| options[:shared] = v }
opt.on("-s", "--shared-library") { |v| options[:shared] = v }
opt.on("-l", "--linker[=VAL]", "selecting linker: gold, lld, and mold, default: \"gold\".") { |v| options[:linker] = v }

begin
opt.parse!(ARGV)
Expand Down
56 changes: 18 additions & 38 deletions lib/vaporware/compiler.rb
Original file line number Diff line number Diff line change
@@ -1,46 +1,26 @@
# frozen_string_literal: true

require_relative "compiler/generator"
require_relative "compiler/assembler"
require_relative "compiler/linker"

module Vaporware
# Your code goes here...
class Compiler
def self.compile(source, compiler: "gcc", dest: "tmp", debug: false, compiler_options: ["-O0"], shared: false)
_precompile = "#{dest}.s"
s = new(source, _precompile: _precompile, debug:, shared:)
s.compile(compiler:, compiler_options:)
end
class Vaporware::Compiler
attr_reader *%i(generator assembler linker)

def initialize(source, _precompile: "tmp.s", debug: false, shared: false)
@generator = Vaporware::Compiler::Generator.new(source, precompile: _precompile, debug:, shared:)
end

def compile(compiler: "gcc", compiler_options: ["-O0"])
@generator.register_var_and_method(@generator.ast)
def self.compile(source, assembler: "as", linker: "ld", dest: "tmp", debug: false, compiler_options: ["-O0"], shared: false)
compiler = new(input: source, output: dest, debug:, shared:, linker:, assembler:)
compiler.compile(compiler_options:)
compiler.assemble(input: dest.to_s + ".s", assembler:, debug:)
compiler.link
end

output = File.open(@generator.precompile, "w")
# prologue
output.puts ".intel_syntax noprefix"
if @generator.defined_methods.empty?
@generator.main = true
output.puts ".globl main"
output.puts "main:"
output.puts " push rbp"
output.puts " mov rbp, rsp"
output.puts " sub rsp, #{@generator.defined_variables.size * 8}"
@generator.to_asm(@generator.ast, output)
# epilogue
@generator.epilogue(output)
else
@generator.prologue_methods(output)
output.puts ".globl main" unless @generator.shared
@generator.to_asm(@generator.ast, output)
# epilogue
@generator.epilogue(output)
end
output.close
compiler_options += @generator.compile_shared_option if @generator.shared
@generator.call_compiler(compiler:, compiler_options:)
end
def initialize(input:, output: File.basename(input, ".*"), linker: "ld", assembler: "as", debug: false, shared: false)
@generator = Vaporware::Compiler::Generator.new(input:, output: output + ".s", debug:, shared:)
@assembler = Vaporware::Compiler::Assembler.new(input: @generator.precompile, output: output + ".o", assembler:, debug:)
@linker = Vaporware::Compiler::Linker.new(input: @assembler.obj_file, output:, linker:, debug:, shared:)
end

def assemble(input:, output: File.basename(input, ".*") + ".o", assembler: "as", assembler_options: [], debug: false) = @assembler.assemble(input:, output:, assembler:, assembler_options:, debug:)
def link = @linker.link
def compile(compiler_options: ["-O0"]) = @generator.compile
end
28 changes: 28 additions & 0 deletions lib/vaporware/compiler/assembler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true
require_relative "assembler/elf"
require_relative "assembler/elf/utils"
require_relative "assembler/elf/header"
require_relative "assembler/elf/sections"
require_relative "assembler/elf/section_header"

class Vaporware::Compiler::Assembler
def self.assemble!(input, output = File.basename(input, ".*") + ".o") = new(input:, output:).assemble

def initialize(input:, output: File.basename(input, ".*") + ".o", assembler: "as", type: :relocatable, debug: false)
@input, @output = input, output
@elf = ELF.new(type:, input:, output:, debug:)
@assembler = assembler
@debug = debug
end

def assemble(assembler: @assembler, assembler_options: [], input: @input, output: @output, debug: false)
if ["gcc", "as"].include?(assembler)
IO.popen([assembler, *assembler_options, "-o", output, input].join(" ")).close
else
to_elf(input:, output:, debug:)
end
output
end
def obj_file = @output
def to_elf(input: @input, output: @output, debug: false) = @elf.build(input:, output:, debug:)
end
77 changes: 77 additions & 0 deletions lib/vaporware/compiler/assembler/elf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
class Vaporware::Compiler::Assembler
class ELF
class Error < StandardError; end
class Section; end
class SectionHeader; end
module Utils; end

def initialize(type:, input:, output:, debug:)
@input, @output = input, output
@header = Header.new(type:)
@sections = Sections.new
end

def build(input: @input, output: @output, debug: false)
program_size = 0
read!(input:)
init_assemble!

offset = 0x40
section_headers = []
names = []
bodies = {
null: nil,
text: nil,
data: nil,
bss: nil,
note: nil,
symtab: nil,
strtab: nil,
shstrtab: nil,
}
name_idx = 0
padding = nil
@sections.each do |section|
name = section.name
names << name
section.body.set!(name: names.join) if name == "\0.shstrtab"
bin = section.body.build
size = bin.bytesize
bin << "\0" until (bin.bytesize % 8) == 0 if ["\0.text", "\0.shstrtab"].include?(name)
bin << "\0" until ((bin.bytesize + offset) % 8) == 0 if ["\0.shstrtab"].include?(name)
bodies[section.section_name.to_sym] = bin
header = section.header
if offset > 0x40 && size > 0 && padding&.>(0)
offset += padding
padding = nil
end
padding = bin.size - size if size > 0
header.set!(name: name_idx, offset:, size:) unless name == ""
offset += size
section_headers << header.build
name_idx += name == "" ? 1 : name.size
end
@header.set!(shoffset: offset + padding)
w = File.open(output, "wb")
w.write([@header.build, *bodies.values, *section_headers].join)
w.close
[@header.build, *bodies.values, *section_headers]
end

private
def init_assemble! = (note!; symtab!)
def read!(input: @input, text: @sections.text.body)
read = { main: false }
File.open(input, "r") do |r|
r.each_line do |line|
read[:main] = line.match(/main:/) unless read[:main]
next unless read[:main] && !/main:/.match(line)
next if /\.L.+:/.match(line)
text.assemble!(line)
end
end
end
def note! = @sections.note.body.null!
def symtab! = @sections.symtab.body.set!(entsize: 0x18, name: 1, info: 0x10, other: 0, shndx: 1)
end
end
62 changes: 62 additions & 0 deletions lib/vaporware/compiler/assembler/elf/header.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
require_relative "../elf"

class Vaporware::Compiler::Assembler::ELF::Header
include Vaporware::Compiler::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: :littel, 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, 4) if check(shnum, 4)
@shstrndx = num2bytes(shstrndx, 4) if check(shstrndx, 4)
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
25 changes: 25 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require_relative "section/text"
require_relative "section/bss"
require_relative "section/data"
require_relative "section/note"
require_relative "section/null"
require_relative "section/symtab"
require_relative "section/strtab"
require_relative "section/shstrtab"
require_relative "section_header"

class Vaporware::Compiler::Assembler::ELF::Section
attr_reader :header, :body, :name, :section_name
def initialize(type:, options: {})
type_string = type.to_s.capitalize
type_string = type_string.upcase if type_string == "Bss"
@section_name = type_string.downcase
@name = section_name == "null" ? "" : "\0.#{section_name}"
@header = Vaporware::Compiler::Assembler::ELF::SectionHeader.new.send("#{@section_name}!")
@body = Module.const_get("Vaporware::Compiler::Assembler::ELF::Section::#{type_string}").new(**options)
end

def name=(name)
@name = name
end
end
10 changes: 10 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section/bss.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Vaporware::Compiler::Assembler::ELF::Section::BSS
include Vaporware::Compiler::Assembler::ELF::Utils
def initialize(**opts) = nil
def build = bytes.flatten.pack("C*")
def set! = self

private
def bytes = []
def check(val, bytes) = false
end
7 changes: 7 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section/data.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Vaporware::Compiler::Assembler::ELF::Section::Data
include Vaporware::Compiler::Assembler::ELF::Utils
def initialize(**opts) = nil
def build = bytes.flatten.pack("C*")
def set! = self
private def bytes = []
end
33 changes: 33 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section/note.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class Vaporware::Compiler::Assembler::ELF::Section::Note
include Vaporware::Compiler::Assembler::ELF::Utils

def self.gnu_property = new.gnu_property!.build
def self.null = new.null!.build

def initialize(type: nil)
@nsize = nil
@dsize = nil
@type = nil
@name = nil
@desc = nil
gnu_property! if type == :gnu
end

def set!(nsize: nil, dsize: nil, type: nil, name: nil, desc: nil)
@nsize = num2bytes(nsize, 4) if check(nsize, 4)
@dsize = num2bytes(dsize, 4) if check(dsize, 4)
@type = num2bytes(type, 4) if check(type, 4)
@name = name!(name) if name
@desc = desc!(desc) if desc
self
end

def gnu_property! = set!(nsize: 0x04, dsize: 0x20, type: 0x05, name: "GNU", desc: %w(02 00 01 c0 04 00 00 00 00 00 00 00 00 00 00 00 01 00 01 c0 04 00 00 00 01 00 00 00 00 00 00 00).map { |val| val.to_i(16) })
def null! = set!(nsize: 0, dsize: 0, type: 0, name: "NULL", desc: [0])

private

def name!(name) = align(@name = name.bytes, 4)
def desc!(desc) = align(@desc = desc.is_a?(Array) ? desc : desc.bytes, 4)
def bytes = [@nsize, @dsize, @type, @name, @desc]
end
7 changes: 7 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section/null.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Vaporware::Compiler::Assembler::ELF::Section::Null
include Vaporware::Compiler::Assembler::ELF::Utils
def initialize(**opts) = nil
def build = bytes.flatten.pack("C*")
def set! = self
private def bytes = []
end
22 changes: 22 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section/shstrtab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class Vaporware::Compiler::Assembler::ELF::Section::Shstrtab
include Vaporware::Compiler::Assembler::ELF::Utils
def initialize(**opts) = @name = []
def build = bytes.flatten.pack("C*")
def set!(name:) = (@name << name!(name); self)

private
def bytes = [@name, [0]]
def name!(name)
case name
when String
(name.match(/\A\0\..+\z/) ? name : "\0.#{name}").bytes
when Array
raise Vaporware::Compiler::Assembler::ELF::Error, "unaccepted type in Array" unless name.all? { |elem| elem.is_a?(Integer) }
n = name
n.unshift(0) && n.push(0) unless n.first == 0 && n.last == 0
n
else
raise Vaporware::Compiler::Assembler::ELF::Error, "unsupported type"
end
end
end
5 changes: 5 additions & 0 deletions lib/vaporware/compiler/assembler/elf/section/strtab.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Vaporware::Compiler::Assembler::ELF::Section::Strtab
include Vaporware::Compiler::Assembler::ELF::Utils
def initialize(names = "\0main\0", **opts) = @names = names
def build = @names.bytes.pack("C*")
end
Loading
Loading