Skip to content

Commit 961b43a

Browse files
authored
Merge pull request #48 from katsyoshi/support-dlopen-in-ruby
Support dlopen in ruby
2 parents 3ae563b + ae3df61 commit 961b43a

9 files changed

Lines changed: 146 additions & 24 deletions

File tree

.github/workflows/main.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ jobs:
3131
run: bundle exec rake steep:check
3232
- name: Run the default task
3333
run: bundle exec rake
34+
- name: Run test using Ruby::Box
35+
run: RUBY_BOX=1 bundle exec rake test

Rakefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,13 @@ require "steep/cli"
77

88
task default: %i[test]
99

10+
def supported_ruby_box? = RUBY_VERSION >= "4.0.0" && ENV["RUBY_BOX"] == "1" && defined?(Ruby::Box)
11+
1012
Rake::TestTask.new do |t|
1113
t.test_files = FileList['test/**/*_test.rb']
14+
if supported_ruby_box?
15+
t.test_files = FileList['test/caotral/linker/fiddle_test.rb']
16+
end
1217
end
1318

1419
namespace :steep do

lib/caotral/binary/elf/reader.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ def read
8484
@bin.pos = section.header.offset
8585
body_bin = @bin.read(section.header.size)
8686
section.body = case type
87-
when :strtab
87+
when :strtab, :dynstr
8888
Caotral::Binary::ELF::Section::Strtab.new(body_bin)
89-
when :symtab
89+
when :symtab, :dynsym
9090
symtab_entsize = section.header.entsize
9191
count = body_bin.bytesize / symtab_entsize
9292
count.times.map do |i|
@@ -144,6 +144,7 @@ def validate_relocations
144144
pt_load = @context.program_headers.find { |ph| ph.type == :LOAD }
145145
dynamic = @context.sections.find { |section| section.section_name.to_s == ".dynamic" }
146146
rela_plt = @context.sections.find { |section| section.section_name.to_s == ".rela.plt" }
147+
rela_plt_exists = !rela_plt.body.empty?
147148
got_plt = @context.sections.find { |s| s.section_name.to_s == ".got.plt" }
148149
failed_messages = []
149150
unless rela_dyn && pt_load && dynamic
@@ -172,7 +173,7 @@ def validate_relocations
172173
failed_messages << "Relocation entries in .rela.dyn exceed LOAD segment range"
173174
end
174175

175-
if rela_plt
176+
if rela_plt && rela_plt_exists
176177
jump_rel = dynamic.body.find { |dt| dt.jmp_rel? }&.un == rela_plt.header.addr
177178
plt_rel_size = dynamic.body.find { |dt| dt.plt_rel_size? }&.un == rela_plt.header.size
178179
plt_rel = dynamic.body.find { |dt| dt.plt_rel? }&.un == 7

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class ELF
55
class Section
66
class Hash
77
include Caotral::Binary::ELF::Utils
8+
attr_reader :bucket, :chain
89
def initialize(nchain:, nbucket: 1)
910
@nbucket = num2bytes(nbucket, 4)
1011
@nchain = num2bytes(nchain, 4)

lib/caotral/linker/builder.rb

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class Linker
66
class Builder
77
include Caotral::Binary::ELF::Utils
88
REL_TYPES = Caotral::Binary::ELF::Section::Rel::TYPES
9+
DYNAMIC_TAGS = Caotral::Binary::ELF::Section::Dynamic::TAG_TYPES
910
SYMTAB_BIND = { locals: 0, globals: 1, weaks: 2, }.freeze
1011
BIND_BY_VALUE = SYMTAB_BIND.invert.freeze
1112
RELOCATION_SECTION_NAMES = [".rela.text", ".rel.text", ".rela.data", ".rel.data"].freeze
@@ -18,6 +19,11 @@ class Builder
1819
REL_TYPES[:AMD64_GOTPCRELX],
1920
REL_TYPES[:AMD64_REX_GOTPCRELX],
2021
].freeze
22+
REJECT_DYNAMIC_TAGS = [
23+
DYNAMIC_TAGS[:PLTRELSZ],
24+
DYNAMIC_TAGS[:PLTREL],
25+
DYNAMIC_TAGS[:JMPREL],
26+
].freeze
2127

2228
attr_reader :symbols
2329

@@ -176,22 +182,22 @@ def build
176182
first_insertion = got_plt_offsets[sym].nil?
177183
got_plt_offsets[sym] ||= got_plt_offset.tap { got_plt_offset += 8 }
178184
if dynamic? && undefined && first_insertion
179-
got_plt_section.body << [0].pack("Q<")
180-
rps = Caotral::Binary::ELF::Section::Rel.new.set!(
181-
offset: got_plt_offsets[sym],
182-
info: ((sym) << 32) | REL_TYPES[:AMD64_JUMP_SLOT]
185+
got_plt_section.body << [0].pack("Q<")
186+
rps = Caotral::Binary::ELF::Section::Rel.new.set!(
187+
offset: got_plt_offsets[sym],
188+
info: ((sym) << 32) | REL_TYPES[:AMD64_JUMP_SLOT]
189+
)
190+
name = symtab_section.body[sym].name_string
191+
dynstr_index = dynstr.body.offset_of(name)
192+
if dynstr_index.nil?
193+
dynstr.body.names += name + "\0"
194+
dynsym.body << Caotral::Binary::ELF::Section::Symtab.new.set!(
195+
name: dynstr.body.offset_of(name),
196+
info: (1 << 4) | 2,
183197
)
184-
name = symtab_section.body[sym].name_string
185-
dynstr_index = dynstr.body.offset_of(name)
186-
if dynstr_index.nil?
187-
dynstr.body.names += name + "\0"
188-
dynsym.body << Caotral::Binary::ELF::Section::Symtab.new.set!(
189-
name: dynstr.body.offset_of(name),
190-
info: (1 << 4) | 2,
191-
)
192-
end
193-
rela_plt_section.body << rps
194-
next
198+
end
199+
rela_plt_section.body << rps
200+
next
195201
end
196202
elsif UNSUPPORTED_REL_TYPES.include?(rel.type)
197203
raise Caotral::Binary::ELF::Error, "unsupported relocation type: #{rel.type_name}"
@@ -260,13 +266,40 @@ def build
260266
if dynamic?
261267
sections << dynstr
262268
sections << dynsym
263-
sections << build_hash_section if @pie
269+
hash_section = build_hash_section
270+
sections << hash_section
264271
sections << rela_dyn_section
265272
sections << rela_plt_section
266273
sym = sections.index(dynsym)
267274
rela_dyn_section.header.set!(link: sym, type: rel_type(rela_dyn_section), entsize: rel_entsize(rela_dyn_section))
268275
rela_plt_section.header.set!(link: sym, type: rel_type(rela_plt_section), info: ref_index(sections, got_plt_section.section_name))
269-
sections << build_dynamic_section
276+
symtab_section.body.each do |sym|
277+
next unless [SYMTAB_BIND[:globals], SYMTAB_BIND[:weaks]].include?(sym.bind)
278+
next if sym.shndx == 0
279+
copy_sym = sym.dup
280+
shndx = copy_sym.shndx
281+
name = dynstr.body.offset_of(sym.name_string)
282+
if name.nil?
283+
dynstr.body.names += copy_sym.name_string + "\0"
284+
name = dynstr.body.offset_of(copy_sym.name_string)
285+
end
286+
copy_sym.name_string = sym.name_string
287+
dynsym.body << copy_sym.set!(name:, shndx:, value: sym.value)
288+
end
289+
hash = Caotral::Binary::ELF::Section::Hash.new(nchain: dynsym.body.size)
290+
hash.bucket[0] = num2bytes(1, 4) if dynsym.body.size > 1
291+
dynsym.body.each_with_index do |sym, i|
292+
next if i == 0
293+
hash.chain[i] = num2bytes(0, 4)
294+
end
295+
hash_section.body = hash
296+
dynamic_section = build_dynamic_section
297+
if rela_plt_section.body.size == 0 && dynamic?
298+
bodies = dynamic_section.body.reject { |ent| REJECT_DYNAMIC_TAGS.include?(ent.tag) }
299+
dynamic_section.body = bodies
300+
end
301+
302+
sections << dynamic_section
270303
end
271304
sections << symtab_section
272305

lib/caotral/linker/writer.rb

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def write
8282
rel.header.set!(offset: rel_offset, size: rel_size, entsize:)
8383
end
8484

85-
patch_dynamic_sections(file: f)
85+
patch_dynamic_sections(file: f) if dynamic?
8686
patch_program_headers(file: f)
8787
write_program_headers(file: f)
8888

@@ -108,6 +108,21 @@ def patch_dynamic_sections(file:)
108108
dyn.header.set!(addr:)
109109
end
110110

111+
cur = file.pos
112+
file.seek(dynsym_section.header.offset)
113+
dynsym_section.body.each do |dynsym_body|
114+
if dynsym_body.shndx != 0
115+
value = dynsym_body.value
116+
secndx = @write_sections[dynsym_body.shndx]&.header&.addr
117+
unless secndx.nil?
118+
value += secndx
119+
dynsym_body.set!(value:)
120+
end
121+
end
122+
file.write(dynsym_body.build)
123+
end
124+
file.seek(cur)
125+
111126
if dynamic? && dynamic_section && rela_dyn_section
112127
rdsh = rela_dyn_section&.header
113128
bodies = dynamic_section.body
@@ -238,23 +253,27 @@ def write_shared_dynamic_sections(file:)
238253
interp_section.header.set!(offset: interp_offset, size:, addr: text_addr + (interp_offset - tsh.offset))
239254
end
240255

256+
pad_to_align(file:, align: dynstr_section.header.addralign)
241257
dynstr_offset = file.pos
242258
file.write(dynstr_section.body.build)
243259
size = file.pos - dynstr_offset
244260
dynstr_section.header.set!(offset: dynstr_offset, size:, addr: text_addr + (dynstr_offset - tsh.offset))
245261

262+
pad_to_align(file:, align: dynsym_section.header.addralign)
246263
dynsym_offset = file.pos
247264
dynsym_section.body.each { |dynsym| file.write(dynsym.build) }
248265
size = file.pos - dynsym_offset
249266
dynsym_section.header.set!(offset: dynsym_offset, size:, addr: text_addr + (dynsym_offset - tsh.offset))
250267

251-
if @pie
268+
if dynamic?
269+
pad_to_align(file:, align: hash_section.header.addralign)
252270
hash_offset = file.pos
253271
file.write(hash_section.body.build)
254272
size = file.pos - hash_offset
255273
hash_section.header.set!(offset: hash_offset, size:, addr: text_addr + (hash_offset - tsh.offset))
256274
end
257275

276+
pad_to_align(file:, align: dynamic_section.header.addralign)
258277
dynamic_offset = file.pos
259278
dynamic_section.body.each { |dynamic| file.write(dynamic.build) }
260279
size = file.pos - dynamic_offset
@@ -316,6 +335,12 @@ def write_section_headers(file:, shoffset:)
316335
file.write(@elf_obj.header.build)
317336
end
318337

338+
def pad_to_align(file:, align:)
339+
pos = file.pos
340+
padding = (align - (pos % align)) % align
341+
file.write("\0" * padding)
342+
end
343+
319344
def program_header_flags(flag) = Caotral::Binary::ELF::ProgramHeader::PF[flag.to_sym]
320345
def elf_type = Caotral::Binary::ELF::Header::TYPE[dynamic? ? :DYN : :EXEC]
321346

@@ -343,7 +368,12 @@ def program_headers
343368
pph = Caotral::Binary::ELF::ProgramHeader.new
344369
pph.set!(type: 6)
345370
end
346-
@program_headers = [pph, lph, iph, dph].compact
371+
# ruby's dlopen support
372+
if dynamic?
373+
gsph = Caotral::Binary::ELF::ProgramHeader.new
374+
gsph.set!(type: 0x6474e551, flags: program_header_flags(:RW))
375+
end
376+
@program_headers = [pph, lph, iph, dph, gsph].compact
347377
end
348378
def pie_program_header = @pie_program_header ||= program_headers.find { |ph| ph.type == :PHDR }
349379
def load_program_header = @load_program_header ||= program_headers.find { |ph| ph.type == :LOAD }
@@ -366,7 +396,7 @@ def plt_section = @plt_section ||= @write_sections.find { |s| ".plt" === s.secti
366396
def got_plt_section = @got_plt_section ||= @write_sections.find { |s| ".got.plt" === s.section_name.to_s }
367397
def rela_plt_section = @rela_plt_section ||= @write_sections.find { |s| ".rela.plt" === s.section_name.to_s }
368398

369-
def dynamic_sections = @dynamic_sections ||= [interp_section, dynstr_section, dynsym_section, dynamic_section, rela_dyn_section, rela_plt_section].compact
399+
def dynamic_sections = @dynamic_sections ||= [interp_section, dynstr_section, dynsym_section, hash_section, dynamic_section, rela_dyn_section, rela_plt_section].compact
370400
end
371401
end
372402
end

sample/C/add.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
int add(int x, int y) {
2+
return x + y;
3+
}

sample/fiddle_add.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
require "fiddle/import"
2+
3+
module X
4+
extend Fiddle::Importer
5+
dlload "./libtmp.so"
6+
extern "int add(int, int)"
7+
end

test/caotral/linker/fiddle_test.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
require_relative "../../test_suite"
2+
3+
class Caotral::Linker::FiddleMethodTest < Test::Unit::TestCase
4+
include TestProcessHelper
5+
def setup
6+
@generated = []
7+
omit("Ruby::Box is not supported in this environment") unless supported_ruby_box?
8+
end
9+
10+
def teardown
11+
@generated.each do |file|
12+
File.delete(file) if File.exist?(file)
13+
end
14+
end
15+
16+
def test_sample_call_add_method
17+
@generated = ["libtmp.so", "libtmp.so.o"]
18+
@file = "sample/C/add.c"
19+
IO.popen(["gcc", "-fPIC", "-c", "-o", "libtmp.so.o", "%s" % @file]).close
20+
Caotral::Linker.link!(inputs: ["libtmp.so.o"], output: "libtmp.so", linker: "self", shared: true, executable: false)
21+
elf = Caotral::Binary::ELF::Reader.read!(input: "./libtmp.so")
22+
box = Ruby::Box.new
23+
box.require("./sample/fiddle_add.rb")
24+
assert_equal(10, box::X.add(3, 7))
25+
dynsym = elf.find_by_name(".dynsym")
26+
rela_plt = elf.find_by_name(".rela.plt")
27+
dynamic = elf.find_by_name(".dynamic")
28+
dynstr = elf.find_by_name(".dynstr")
29+
dynstrs = dynstr.body.names.split("\x00")
30+
assert(dynstrs.include?("add"))
31+
assert_equal(2, dynsym.body.size)
32+
assert_equal("add", dynstr.body.lookup(dynsym.body[1].name_offset))
33+
assert_equal(0, rela_plt.body.size)
34+
assert_equal(nil, dynamic.body.find { |dt| dt.plt_rel? })
35+
assert_equal(nil, dynamic.body.find { |dt| dt.plt_rel_size? })
36+
assert_equal(nil, dynamic.body.find { |dt| dt.jmp_rel? })
37+
end
38+
39+
private def supported_ruby_box? = RUBY_VERSION >= "4.0.0" && ENV["RUBY_BOX"] == "1" && defined?(Ruby::Box)
40+
end

0 commit comments

Comments
 (0)