Skip to content

Commit e0e5ed0

Browse files
authored
Merge pull request #32 from katsyoshi/support-multi-files-for-linker
Support multi files for linker
2 parents 8ba9a55 + 90467c1 commit e0e5ed0

17 files changed

Lines changed: 372 additions & 89 deletions

File tree

lib/caotral.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def compile!(input:, assembler: "as", linker: "ld", output: "tmp", debug: false,
1414
execf = "#{basename}#{File.extname(d)}"
1515
compile(input:, output: basename+".s", debug:, shared:)
1616
assemble(input: basename+".s", output: basename+".o", assembler:, debug:, shared:)
17-
link(input: basename+".o", output: execf, linker:, debug:, shared:)
17+
link(input: [basename+".o"], output: execf, linker:, debug:, shared:)
1818
end
1919
def compile(input:, output: "tmp.s", debug: false, shared: false)
2020
Caotral::Compiler.compile!(input:, output:, debug:)
@@ -23,6 +23,7 @@ def assemble(input:, output: "tmp.o", debug: false, shared: false, assembler: "a
2323
Caotral::Assembler.assemble!(input:, output:, debug:, assembler:, shared:)
2424
end
2525
def link(input:, output: "tmp", linker: "ld", debug: false, shared: false)
26-
Caotral::Linker.link!(input:, output:, linker:, debug:, shared:)
26+
inputs = Array === input ? input : [input]
27+
Caotral::Linker.link!(inputs:, output:, linker:, debug:, shared:)
2728
end
2829
end

lib/caotral/binary/elf.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def find_by_name(section_name) = @sections.find { |s| section_name == s.section_
2525
def select_by_name(section_name) = @sections.select { |s| section_name == s.section_name }
2626
def index(section_name) = @sections.index { |s| section_name == s.section_name }
2727
def select_by_names(section_names) = @sections.select { |section| section_names.any? { |name| name === section.section_name.to_s } }
28+
def without_sections(names) = @sections.reject { |s| names.any? { |name| name === s.section_name.to_s } }
2829
end
2930
end
3031
end

lib/caotral/binary/elf/section/rel.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def initialize(addend: true)
1414
def set!(offset: nil, info: nil, addend: nil)
1515
@offset = num2bytes(offset, 8) if check(offset, 8)
1616
@info = num2bytes(info, 8) if check(info, 8)
17-
@addend = num2bytes(addend, 8) if check(addend, 8)
17+
@addend = [addend].pack("q<").unpack("C*") if check(addend, 8)
1818
self
1919
end
2020

@@ -23,7 +23,7 @@ def offset = @offset.pack("C*").unpack1("Q<")
2323
def info = @info.pack("C*").unpack1("Q<")
2424
def addend
2525
raise "No addend field in this REL entry" unless addend?
26-
@addend.pack("C*").unpack1("Q<")
26+
@addend.pack("C*").unpack1("q<")
2727
end
2828
def sym = @info.pack("C*").unpack1("Q<") >> 32
2929
def type = @info.pack("C*").unpack1("Q<") & 0xffffffff

lib/caotral/binary/elf/section/symtab.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ def set!(name: nil, info: nil, other: nil, shndx: nil, value: nil, size: nil)
3232
def name_offset = @name.pack("C*").unpack1("L<")
3333
def value = @value.pack("C*").unpack1("Q<")
3434
def info = @info.pack("C*").unpack1("C")
35+
def shndx = @shndx.pack("C*").unpack1("S<")
36+
def bind = info >> 4
37+
def type = info & 0x0f
3538

3639
private def bytes = [@name, @info, @other, @shndx, @value, @size]
3740
end

lib/caotral/linker.rb

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@
55

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

10-
def initialize(input:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false)
11-
@input, @output, @linker = input, output, linker
10+
def initialize(inputs:, output: "a.out", linker: "mold", linker_options: [], shared: false, debug: false)
11+
@inputs, @output, @linker = inputs, output, linker
1212
@options = linker_options
1313
@debug, @shared = debug, shared
1414
end
1515

16-
def link(input: @input, output: @output, debug: @debug, shared: @shared)
17-
return to_elf(input:, output:, debug:) if @linker == "self"
16+
def link(inputs: @inputs, output: @output, debug: @debug, shared: @shared)
17+
return to_elf(inputs:, output:, debug:) if @linker == "self"
1818

1919
IO.popen(link_command).close
2020
end
2121

22-
def link_command(input: @input, output: @output, debug: @debug, shared: @shared)
22+
def link_command(inputs: @inputs, output: @output)
2323
ld_path = []
2424

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

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

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

50-
def to_elf(input: @input, output: @output, debug: @debug)
51-
elf_obj = Caotral::Binary::ELF::Reader.new(input:, debug:).read
52-
builder = Caotral::Linker::Builder.new(elf_obj:)
50+
def to_elf(inputs: @inputs, output: @output, debug: @debug)
51+
elf_objs = inputs.map { |input| Caotral::Binary::ELF::Reader.new(input:, debug:).read }
52+
builder = Caotral::Linker::Builder.new(elf_objs:)
5353
builder.resolve_symbols
54+
elf_obj = builder.build
5455
Caotral::Linker::Writer.new(elf_obj:, output:, debug:).write
5556
end
5657
end

lib/caotral/linker/builder.rb

Lines changed: 244 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,260 @@
44
module Caotral
55
class Linker
66
class Builder
7+
include Caotral::Binary::ELF::Utils
8+
R_X86_64_PC32 = 2
9+
R_X86_64_PLT32 = 4
710
SYMTAB_BIND = { locals: 0, globals: 1, weaks: 2, }.freeze
811
BIND_BY_VALUE = SYMTAB_BIND.invert.freeze
9-
attr_reader :symbols
12+
RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text"].freeze
13+
ALLOW_RELOCATION_TYPES = [R_X86_64_PC32, R_X86_64_PLT32].freeze
1014

11-
def initialize(elf_obj:)
12-
@elf_obj = elf_obj
15+
attr_reader :symbols, :executable, :debug
16+
17+
def initialize(elf_objs:, executable: true, debug: false)
18+
@elf_objs = elf_objs
1319
@symbols = { locals: Set.new, globals: Set.new, weaks: Set.new }
20+
@executable, @debug = executable, debug
1421
end
22+
23+
def build
24+
raise Caotral::Binary::ELF::Error, "no ELF objects to link" if @elf_objs.empty?
25+
elf = Caotral::Binary::ELF.new
26+
elf_obj = @elf_objs.first
27+
null_section = Caotral::Binary::ELF::Section.new(
28+
body: nil,
29+
section_name: "",
30+
header: Caotral::Binary::ELF::SectionHeader.new
31+
)
32+
text_section = Caotral::Binary::ELF::Section.new(
33+
body: String.new,
34+
section_name: ".text",
35+
header: Caotral::Binary::ELF::SectionHeader.new
36+
)
37+
strtab_section = Caotral::Binary::ELF::Section.new(
38+
body: Caotral::Binary::ELF::Section::Strtab.new("\0".b),
39+
section_name: ".strtab",
40+
header: Caotral::Binary::ELF::SectionHeader.new
41+
)
42+
symtab_section = Caotral::Binary::ELF::Section.new(
43+
body: [],
44+
section_name: ".symtab",
45+
header: Caotral::Binary::ELF::SectionHeader.new
46+
)
47+
shstrtab_section = Caotral::Binary::ELF::Section.new(
48+
body: Caotral::Binary::ELF::Section::Strtab.new("\0".b),
49+
section_name: ".shstrtab",
50+
header: Caotral::Binary::ELF::SectionHeader.new
51+
)
52+
start_bytes = [0xe8, *[0] * 4, 0x48, 0x89, 0xc7, 0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00, 0x0f, 0x05]
53+
exec_text_offset = 0x1000
54+
base_addr = 0x400000
55+
vaddr = base_addr + exec_text_offset
56+
start_len = start_bytes.length
57+
sections = []
58+
rel_sections = []
59+
elf.header = elf_obj.header.dup
60+
strtab_names = []
61+
text_offsets = {}
62+
text_offset = 0
63+
sym_by_elf = Hash.new { |h, k| h[k] = [] }
64+
@elf_objs.each do |elf_obj|
65+
text = elf_obj.find_by_name(".text")
66+
unless text.nil?
67+
text_section.body << text.body
68+
text_offsets[elf_obj.object_id] = text_offset
69+
size = text.body.bytesize
70+
text_offset += size
71+
end
72+
strtab = elf_obj.find_by_name(".strtab")
73+
strtab.body.names.split("\0").each { |name| strtab_names << name } unless strtab.nil?
74+
symtab = elf_obj.find_by_name(".symtab")
75+
base_index = nil
76+
unless symtab.nil?
77+
base_index = symtab_section.body.size
78+
symtab.body.each_with_index do |st, index|
79+
sym = Caotral::Binary::ELF::Section::Symtab.new
80+
name, info, other, shndx, value, size = st.build.unpack("L<CCS<Q<Q<")
81+
sym_by_elf[elf_obj] << sym
82+
value += text_offsets.fetch(elf_obj.object_id, 0) if shndx != 0
83+
sym.set!(name:, info:, other:, shndx:, value:, size:)
84+
sym.name_string = strtab.body.lookup(name) unless strtab.nil?
85+
symtab_section.body << sym
86+
end
87+
end
88+
rels = elf_obj.select_by_names(RELOCATION_SECTION_NAMES).map do |section|
89+
rel_section = Caotral::Binary::ELF::Section.new(
90+
body: [],
91+
section_name: section.section_name,
92+
header: Caotral::Binary::ELF::SectionHeader.new
93+
)
94+
section.body.each do |rel|
95+
offset = rel.offset + text_offsets.fetch(elf_obj.object_id, 0)
96+
addend = rel.addend? ? rel.addend : nil
97+
new_rel = Caotral::Binary::ELF::Section::Rel.new(addend: rel.addend?)
98+
sym = base_index.nil? ? rel.sym : base_index + rel.sym
99+
info = (sym << 32) | rel.type
100+
new_rel.set!(offset:, info:, addend:)
101+
rel_section.body << new_rel
102+
end
103+
rel_section
104+
end
105+
rel_sections += rels
106+
end
107+
strtab_section.body.names = strtab_names.to_a.sort.join("\0") + "\0"
108+
sections << null_section
109+
110+
main_sym = symtab_section.body.find { |sym| sym.name_string == "main" }
111+
raise Caotral::Binary::ELF::Error, "main function not found" if executable && main_sym.nil?
112+
main_offset = main_sym.nil? ? 0 : main_sym.value + start_len
113+
start_bytes[1, 4] = num2bytes((main_offset - 5), 4)
114+
text_section.body.prepend(start_bytes.pack("C*"))
115+
116+
text_section.header.set!(
117+
type: 1,
118+
flags: 6,
119+
addr: vaddr,
120+
offset: exec_text_offset,
121+
size: text_section.body.bytesize,
122+
addralign: 16
123+
)
124+
125+
sections << text_section
126+
strtab_section.header.set!(type: 3, flags: 0, addralign: 1, entsize: 0)
127+
sections << strtab_section
128+
symtab_section.body.each do |sym|
129+
next if sym.shndx == 0
130+
name = strtab_section.body.offset_of(sym.name_string)
131+
value = sym.value + start_len
132+
sym.set!(name:, value:)
133+
end
134+
135+
old_syms = symtab_section.body.dup
136+
symtab_section.body.sort_by! { |sym| sym.info >> 4 }
137+
local_count = symtab_section.body.count { |sym| (sym.info >> 4) == SYMTAB_BIND[:locals] }
138+
139+
symtab_section.header.set!(
140+
type: 2,
141+
flags: 0,
142+
link: elf.sections.index(strtab_section),
143+
info: local_count,
144+
addralign: 8,
145+
entsize: 24
146+
)
147+
148+
sections << symtab_section
149+
150+
rel_sections.each { |s| sections << s.dup }
151+
152+
shstrtab_section.header.set!(
153+
type: 3,
154+
flags: 0,
155+
addralign: 1,
156+
entsize: 0
157+
)
158+
159+
@elf_objs.first.without_sections([".text", ".strtab", ".symtab", ".shstrtab", /\.rela?\./]).each do |section|
160+
sections << section.dup
161+
end
162+
163+
sections << shstrtab_section
164+
165+
shstrtab_section_names = [*sections.map(&:section_name), "\0"].join("\0")
166+
shstrtab_section.body.names = shstrtab_section_names
167+
168+
section_map = Hash.new { |h, k| h[k] = {} }
169+
@elf_objs.each do |elf_obj|
170+
elf_obj.sections.each_with_index do |section, index|
171+
newndx = sections.index { |s| s.section_name == section.section_name }
172+
section_map[elf_obj][index] = newndx unless newndx.nil?
173+
end
174+
end
175+
176+
resolved_index = {}
177+
symtab_section.body.each_with_index do |sym, index|
178+
name = sym.name_string
179+
next if name.empty? || sym.shndx == 0 || sym.bind != 1
180+
resolved_index[name] ||= index
181+
end
182+
183+
sym_by_elf.each do |elf_obj, syms|
184+
syms.each do |sym|
185+
next if sym.shndx == 0
186+
shndx = section_map[elf_obj][sym.shndx]
187+
sym.set!(shndx:)
188+
end
189+
end
190+
191+
rel_sections.each do |rel_section|
192+
rel_section.body.each do |rel|
193+
orig_sym = old_syms[rel.sym]
194+
next if orig_sym.nil?
195+
name = orig_sym.name_string
196+
new_index = resolved_index[name]
197+
next if new_index.nil?
198+
rel.set!(info: (new_index << 32) | rel.type)
199+
end
200+
201+
rel_section.header.set!(
202+
type: rel_type(rel_section),
203+
flags: 0,
204+
link: sections.index(symtab_section),
205+
info: ref_index(sections, rel_section.section_name),
206+
addralign: 8,
207+
entsize: rel_entsize(rel_section)
208+
)
209+
end
210+
211+
rel_sections.each do |rel|
212+
target = sections[rel.header.info]
213+
bytes = target.body.dup
214+
symtab_body = symtab_section.body
215+
rel.body.each do |entry|
216+
next unless ALLOW_RELOCATION_TYPES.include?(entry.type)
217+
sym = symtab_body[entry.sym]
218+
next if sym.nil? || sym.shndx == 0
219+
target_addr = target == text_section ? vaddr : target.header.addr
220+
sym_addr = sym.shndx >= 0xff00 ? sym.value : sections[sym.shndx].then { |st| st.header.addr + sym.value }
221+
sym_offset = entry.offset + start_len
222+
sym_addend = entry.addend? ? entry.addend : bytes[sym_offset, 4].unpack1("l<")
223+
value = sym_addr + sym_addend - (target_addr + sym_offset)
224+
bytes[sym_offset, 4] = [value].pack("l<")
225+
end
226+
target.body = bytes
227+
end
228+
229+
sections = sections.reject { |section| RELOCATION_SECTION_NAMES.any? { |name| name === section.section_name.to_s } } if executable
230+
sections.each { |section| elf.sections << section }
231+
232+
elf
233+
end
234+
15235
def resolve_symbols
16-
@elf_obj.find_by_name(".symtab").body.each do |symtab|
17-
name = symtab.name_string
18-
next if name.empty?
19-
info = symtab.info
20-
bind = BIND_BY_VALUE.fetch(info >> 4)
21-
if bind == :globals && @symbols[bind].include?(name)
22-
raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}"
236+
@elf_objs.each do |elf_obj|
237+
elf_obj.find_by_name(".symtab").body.each do |symtab|
238+
name = symtab.name_string
239+
next if name.empty?
240+
info = symtab.info
241+
bind = BIND_BY_VALUE.fetch(info >> 4)
242+
if bind == :globals && @symbols[bind].include?(name) && symtab.shndx != 0
243+
raise Caotral::Binary::ELF::Error,"cannot add into globals: #{name}"
244+
end
245+
@symbols[bind] << name
23246
end
24-
@symbols[bind] << name
25247
end
26248
@symbols
27249
end
250+
251+
private
252+
def ref_index(sections, section_name)
253+
raise Caotral::Binary::ELF::Error, "invalid section name: #{section_name}" if section_name.nil?
254+
ref_names = "." + section_name.split(".").filter { |sn| !sn.empty? && sn != "rel" && sn != "rela" }.join(".")
255+
ref = sections.find { |s| ref_names === s.section_name.to_s }
256+
sections.index(ref)
257+
end
258+
259+
def rel_type(section) = section.section_name&.start_with?(".rela.") ? 4 : 9
260+
def rel_entsize(section) = section.section_name&.start_with?(".rela.") ? 24 : 16
28261
end
29262
end
30263
end

0 commit comments

Comments
 (0)