Skip to content

Commit baa48da

Browse files
authored
Merge pull request #27 from katsyoshi/implement-linker
implement a linker
2 parents b467dc6 + 16205d5 commit baa48da

31 files changed

+865
-42
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/coverage/
55
/doc/
66
/pkg/
7+
/AGENTS.md
78
/spec/reports/
89
/tmp/
910
/Gemfile.lock
@@ -13,3 +14,5 @@
1314
/.gem_rbs_collection
1415
/node_modules
1516
/package*.json
17+
/*.md
18+
!/README.md

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ gemspec
77

88
gem "rake", "~> 13.0"
99
gem "fiddle"
10+
gem "irb"
1011
gem "steep"
1112
gem "test-unit"

lib/caotral/linker.rb

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,51 @@
11
# frozen_string_literal: true
2-
class Caotral::Linker
3-
def self.link!(input:, output: "a.out", linker: "mold", debug: false, shared: false) = new(input:, output:, linker:, debug:, shared:).link
2+
require_relative "linker/reader"
3+
require_relative "linker/writer"
44

5-
def initialize(input:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false)
6-
@input, @output, @linker = input, output, linker
7-
@options = linker_options
8-
@debug, @shared = debug, shared
9-
end
5+
module Caotral
6+
class Linker
7+
def self.link!(input:, output: "a.out", linker: "mold", debug: false, shared: false) = new(input:, output:, linker:, debug:, shared:).link
108

11-
def link(input: @input, output: @output, debug: @debug, shared: @shared) = IO.popen(link_command).close
12-
13-
def link_command(input: @input, output: @output, debug: @debug, shared: @shared)
14-
ld_path = []
15-
if @shared
16-
ld_path << "--shared"
17-
ld_path << "#{libpath}/crti.o"
18-
ld_path << "#{gcc_libpath}/crtbeginS.o"
19-
ld_path << "#{gcc_libpath}/crtendS.o"
20-
else
21-
ld_path << "-dynamic-linker"
22-
ld_path << "/lib64/ld-linux-x86-64.so.2"
23-
ld_path << "#{libpath}/crt1.o"
24-
ld_path << "#{libpath}/crti.o"
25-
ld_path << "#{gcc_libpath}/crtbegin.o"
26-
# for not static compile
27-
ld_path << "#{gcc_libpath}/crtend.o"
9+
def initialize(input:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false)
10+
@input, @output, @linker = input, output, linker
11+
@options = linker_options
12+
@debug, @shared = debug, shared
2813
end
2914

30-
ld_path << "#{libpath}/libc.so"
31-
ld_path << "#{libpath}/crtn.o"
32-
cmd = [@linker, "-o", @output, "-m", "elf_x86_64", *@options, *ld_path, @input].join(' ')
33-
puts cmd if @debug
34-
cmd
35-
end
15+
def link(input: @input, output: @output, debug: @debug, shared: @shared) = IO.popen(link_command).close
16+
17+
def link_command(input: @input, output: @output, debug: @debug, shared: @shared)
18+
ld_path = []
19+
return to_elf(input:, output:, debug:) if @linker == "self"
3620

37-
def libpath = @libpath ||= File.dirname(Dir.glob("/usr/lib*/**/crti.o").last)
38-
def gcc_libpath = @gcc_libpath ||= File.dirname(Dir.glob("/usr/lib/gcc/x86_64-*/*/crtbegin.o").last)
21+
if @shared
22+
ld_path << "--shared"
23+
ld_path << "#{libpath}/crti.o"
24+
ld_path << "#{gcc_libpath}/crtbeginS.o"
25+
ld_path << "#{gcc_libpath}/crtendS.o"
26+
else
27+
ld_path << "-dynamic-linker"
28+
ld_path << "/lib64/ld-linux-x86-64.so.2"
29+
ld_path << "#{libpath}/crt1.o"
30+
ld_path << "#{libpath}/crti.o"
31+
ld_path << "#{gcc_libpath}/crtbegin.o"
32+
# for not static compile
33+
ld_path << "#{gcc_libpath}/crtend.o"
34+
end
35+
36+
ld_path << "#{libpath}/libc.so"
37+
ld_path << "#{libpath}/crtn.o"
38+
cmd = [@linker, "-o", @output, "-m", "elf_x86_64", *@options, *ld_path, @input].join(' ')
39+
puts cmd if @debug
40+
cmd
41+
end
42+
43+
def libpath = @libpath ||= File.dirname(Dir.glob("/usr/lib*/**/crti.o").last)
44+
def gcc_libpath = @gcc_libpath ||= File.dirname(Dir.glob("/usr/lib/gcc/x86_64-*/*/crtbegin.o").last)
45+
46+
def to_elf(input: @input, output: @output, debug: @debug)
47+
elf_obj = Caotral::Linker::Reader.new(input:, debug:).read
48+
Caotral::Linker::Writer.new(elf_obj:, output:, debug:).write
49+
end
50+
end
3951
end

lib/caotral/linker/elf.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
require_relative "elf/header"
2+
require_relative "elf/sections"
3+
4+
module Caotral
5+
class Linker
6+
class ELF
7+
attr_reader :sections, :header
8+
def initialize
9+
@sections = Caotral::Linker::ELF::Sections.new
10+
@header = Caotral::Linker::ELF::Header.new
11+
end
12+
end
13+
end
14+
end

lib/caotral/linker/elf/header.rb

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
module Caotral
2+
class Linker
3+
class ELF
4+
class Header
5+
include Caotral::Assembler::ELF::Utils
6+
attr_reader :entry, :phoffset, :shoffset, :shnum, :shstrndx
7+
IDENT = [0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00].freeze
8+
IDENT_STR = IDENT.pack("C*").freeze
9+
ELF_FILE_TYPE = { NONE: 0, REL: 1, EXEC: 2, DYN: 3, CORE: 4 }.freeze
10+
11+
def initialize(endian: :little, type: :rel, arc: :amd64)
12+
@ident = IDENT
13+
@type = num2bytes(ELF_FILE_TYPE[elf(type)], 2)
14+
@arch = arch(arc)
15+
@version = num2bytes(1, 4)
16+
@entry = num2bytes(0x00, 8)
17+
@phoffset = num2bytes(0x00, 8)
18+
@shoffset = num2bytes(0x00, 8)
19+
@flags = num2bytes(0x00, 4)
20+
@ehsize = num2bytes(0x40, 2)
21+
@phsize = num2bytes(0x00, 2)
22+
@phnum = num2bytes(0x00, 2)
23+
@shentsize = num2bytes(0x40, 2)
24+
@shnum = num2bytes(0x08, 2)
25+
@shstrndx = num2bytes(0x07, 2)
26+
end
27+
28+
def build = bytes.flatten.pack("C*")
29+
30+
def set!(type: nil, entry: nil, phoffset: nil, shoffset: nil, shnum: nil, shstrndx: nil, phsize: nil, phnum: nil, ehsize: nil)
31+
@type = num2bytes(type, 2) if check(type, 2)
32+
@entry = num2bytes(entry, 8) if check(entry, 8)
33+
@phoffset = num2bytes(phoffset, 8) if check(phoffset, 8)
34+
@phsize = num2bytes(phsize, 2) if check(phsize, 2)
35+
@phnum = num2bytes(phnum, 2) if check(phnum, 2)
36+
@ehsize = num2bytes(ehsize, 2) if check(ehsize, 2)
37+
@shoffset = num2bytes(shoffset, 8) if check(shoffset, 8)
38+
@shnum = num2bytes(shnum, 2) if check(shnum, 2)
39+
@shstrndx = num2bytes(shstrndx, 2) if check(shstrndx, 2)
40+
self
41+
end
42+
43+
def ehsize = get(:ehsize)
44+
def phsize = get(:phsize)
45+
def phnum = get(:phnum)
46+
def shentsize = get(:shentsize)
47+
def shnum = get(:shnum)
48+
def shstrndx = get(:shstrndx)
49+
50+
LONG_TYPES = %w[entry phoffset shoffset].freeze
51+
INT_TYPES = %w[type version].freeze
52+
SHORT_TYPES = %w[ehsize phsize phnum shentsize shnum shstrndx].freeze
53+
CHAR_TYPES = %w[arch flags].freeze
54+
private_constant :LONG_TYPES, :INT_TYPES, :SHORT_TYPES, :CHAR_TYPES
55+
56+
private
57+
def bytes = [
58+
@ident, @type, @arch, @version, @entry, @phoffset,
59+
@shoffset, @flags, @ehsize, @phsize, @phnum, @shentsize,
60+
@shnum, @shstrndx
61+
]
62+
63+
def arch(machine)
64+
case machine.to_s
65+
in "amd64" | "x86_64" | "x64"
66+
[0x3e, 0x00]
67+
end
68+
end
69+
70+
def elf(type)
71+
case type.to_s
72+
in "relocatable" | "rel"
73+
:REL
74+
in "exe" | "ex" | "exec"
75+
:EXEC
76+
in "shared" | "share" | "dynamic" | "dyn"
77+
:DYN
78+
else
79+
:NONE
80+
end
81+
end
82+
83+
def get(type)
84+
val = instance_variable_get(:"@#{type.to_s}").pack("C*")
85+
case type.to_s.downcase
86+
when *LONG_TYPES; val.unpack("Q<")
87+
when *INT_TYPES; val.unpack("L<")
88+
when *SHORT_TYPES; val.unpack("S<")
89+
when *CHAR_TYPES; val.unpack("C<")
90+
else
91+
raise "not specified: #{type}"
92+
end.first
93+
end
94+
end
95+
end
96+
end
97+
end
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
module Caotral
2+
class Linker
3+
class ELF
4+
class ProgramHeader
5+
include Caotral::Assembler::ELF::Utils
6+
def initialize
7+
@type = num2bytes(0, 4)
8+
@flags = num2bytes(0, 4)
9+
@offset = num2bytes(0, 8)
10+
@vaddr = num2bytes(0, 8)
11+
@paddr = num2bytes(0, 8)
12+
@filesz = num2bytes(0, 8)
13+
@memsz = num2bytes(0, 8)
14+
@align = num2bytes(0, 8)
15+
end
16+
def build = bytes.flatten.pack("C*")
17+
def set!(type: nil, flags: nil, offset: nil, vaddr: nil, paddr: nil, filesz: nil, memsz: nil, align: nil)
18+
@type = num2bytes(type, 4) if check(type, 4)
19+
@flags = num2bytes(flags, 4) if check(flags, 4)
20+
@offset = num2bytes(offset, 8) if check(offset, 8)
21+
@vaddr = num2bytes(vaddr, 8) if check(vaddr, 8)
22+
@paddr = num2bytes(paddr, 8) if check(paddr, 8)
23+
@filesz = num2bytes(filesz, 8) if check(filesz, 8)
24+
@memsz = num2bytes(memsz, 8) if check(memsz, 8)
25+
@align = num2bytes(align, 8) if check(align, 8)
26+
self
27+
end
28+
private def bytes = [@type, @flags, @offset, @vaddr, @paddr, @filesz, @memsz, @align]
29+
end
30+
end
31+
end
32+
end

lib/caotral/linker/elf/section.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class Caotral::Linker
2+
class ELF
3+
class Section
4+
attr_accessor :header, :body, :section_name
5+
def initialize(type:, section_name: nil, options: {})
6+
type_string = type.to_s.capitalize
7+
type_string = type_string.upcase if type_string == "Bss"
8+
@section_name = (section_name.nil? ? type_string : section_name).to_s.downcase
9+
@header, @body = nil, nil
10+
end
11+
12+
def name = @section_name == "null" ? "" : "\0#{@section_name}"
13+
end
14+
end
15+
end
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module Caotral
2+
class Linker
3+
class ELF
4+
class Section
5+
class Rel
6+
include Caotral::Assembler::ELF::Utils
7+
def initialize(addend: true)
8+
@offset = num2bytes(0, 8)
9+
@info = num2bytes(0, 8)
10+
@addend = addend ? num2bytes(0, 8) : false
11+
end
12+
13+
def set!(offset: nil, info: nil, addend: nil)
14+
@offset = num2bytes(offset, 8) if check(offset, 8)
15+
@info = num2bytes(info, 8) if check(info, 8)
16+
@addend = num2bytes(addend, 8) if check(addend, 8)
17+
self
18+
end
19+
20+
def build = bytes.flatten.pack("C*")
21+
def offset = @offset.pack("C*").unpack1("Q<")
22+
def info = @info.pack("C*").unpack1("Q<")
23+
def addend
24+
raise "No addend field in this REL entry" unless addend?
25+
@addend.pack("C*").unpack1("Q<")
26+
end
27+
def sym = @info.pack("C*").unpack1("Q<") >> 32
28+
def type = @info.pack("C*").unpack1("Q<") & 0xffffffff
29+
def addend? = !!@addend
30+
31+
private def bytes = addend? ? [@offset, @info, @addend] : [@offset, @info]
32+
end
33+
end
34+
end
35+
end
36+
end
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module Caotral
2+
class Linker
3+
class ELF
4+
class Section
5+
class Strtab
6+
include Caotral::Assembler::ELF::Utils
7+
attr_reader :names
8+
def initialize(names = "\0main\0", **opts) = @names = names
9+
def build = @names.bytes.pack("C*")
10+
def offset_of(name)
11+
offset = 0
12+
@names.split("\0").each do |n|
13+
return offset if n == name
14+
offset += n.bytesize + 1
15+
end
16+
nil
17+
end
18+
19+
def lookup(offset)
20+
return "" if offset == 0
21+
@names.byteslice(offset..).split("\0", 2).first
22+
end
23+
end
24+
end
25+
end
26+
end
27+
end
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
module Caotral
2+
class Linker
3+
class ELF
4+
class Section
5+
class Symtab
6+
include Caotral::Assembler::ELF::Utils
7+
attr_accessor :name_string
8+
def initialize(**opts)
9+
@entsize = []
10+
@name = num2bytes(0, 4)
11+
@info = num2bytes(0, 1)
12+
@other = num2bytes(0, 1)
13+
@shndx = num2bytes(0, 2)
14+
@value = num2bytes(0, 8)
15+
@size = num2bytes(0, 8)
16+
@name_string = ""
17+
end
18+
19+
def set!(name: nil, info: nil, other: nil, shndx: nil, value: nil, size: nil)
20+
@name = num2bytes(name, 4) if check(name, 4)
21+
@info = num2bytes(info, 1) if check(info, 1)
22+
@other = num2bytes(other, 1) if check(other, 1)
23+
@shndx = num2bytes(shndx, 2) if check(shndx, 2)
24+
@value = num2bytes(value, 8) if check(value, 8)
25+
@size = num2bytes(size, 8) if check(size, 8)
26+
self
27+
end
28+
29+
def name_offset = @name.pack("C*").unpack1("L<")
30+
def value = @value.pack("C*").unpack1("Q<")
31+
end
32+
end
33+
end
34+
end
35+
end

0 commit comments

Comments
 (0)