Thursday, April 28, 2011

At home he's a tourist: FFI for Ruby internals

FFI stands for "foreign function interface". That describes the most common use case: Creating Ruby bindings to external native libraries. But it's also possible to use FFI to spelunk Ruby's internals. Consider:

require 'ffi'
module RubyInternals
extend FFI::Library
ffi_lib(FFI::CURRENT_PROCESS)
end
view raw ffi1.rb hosted with ❤ by GitHub

Foreign? Far from it. You've now got access to all of C-ruby's innards. For example, if you're using Ruby 1.9:

module RubyInternals
attach_variable :ptr_vm_core, :rb_mRubyVMFrozenCore, :pointer
end
view raw ffi2.rb hosted with ❤ by GitHub

With this you've got a handle on the otherwise inaccessible RubyVM core object. As a pointer, it's not all that useful. But with a little more mischief...

module RubyInternals
attach_function :const_def_pointer, :rb_define_const,
[:pointer, :string, :pointer], :void
attach_variable :ptr_object_class, :rb_cObject, :pointer
const_def_pointer(ptr_object_class, "RubyCore", ptr_vm_core)
end
view raw ffi3.rb hosted with ❤ by GitHub

... now you can treat the RubyVM core like any other object. One interesting (if unwieldly-named) method that it offers is "core#define_singleton_method". It accepts an InstructionSequence -- compiled YARV bytecode -- and uses it to set up a singleton method in the object where it is invoked. Let's make it a little easier to use:

module RubyInternals
def define_iseq_singleton_method(mod,sel,iseq)
mod.module_eval do
RubyCore.send("core#define_singleton_method",mod,sel,iseq)
end
end
end
view raw ffi4.rb hosted with ❤ by GitHub

Now it is easy to create methods using bytecode sequences:

module Greeter
extend RubyInternals
iseq = RubyVM::InstructionSequence.compile <<-RUBY
puts "Hello, world."
RUBY
define_iseq_singleton_method(self, :greet, iseq)
end
Greeter.greet
view raw ffi5.rb hosted with ❤ by GitHub

No comments:

Post a Comment