Skip to content

Instantly share code, notes, and snippets.

@msievers
Last active October 25, 2018 05:58
Show Gist options
  • Save msievers/e3d623119ead819b1da28f52e2b34958 to your computer and use it in GitHub Desktop.
Save msievers/e3d623119ead819b1da28f52e2b34958 to your computer and use it in GitHub Desktop.
Example for hijacking ruby via FFI and using native structs and functions
require "ffi"
module MRI
extend FFI::Library
ffi_lib [FFI::CURRENT_PROCESS, "ruby"]
attach_function :rb_str_resize, :rb_str_resize, [:pointer, :long], :pointer
def self.sizeof(type)
Class.new(FFI::Struct) do
layout(member: type)
end
.size
end
VALUE = typedef :pointer, :VALUE
class RBasic < FFI::Struct
layout(
flags: VALUE,
klass: VALUE
)
end
RSTRING_EMBED_LEN_MAX = ((sizeof(VALUE)*3) / sizeof(:char)) - 1
class RString < FFI::Struct
layout(
basic: RBasic,
as: Class.new(FFI::Union) do
layout(
heap: Class.new(FFI::Struct) do
layout(
len: :long,
ptr: :pointer,
aux: Class.new(FFI::Union) do
layout(
capa: :long,
shared: VALUE
)
end
)
end,
ary: [:char, RSTRING_EMBED_LEN_MAX]
)
end
)
end
end
require "ffo"
require_relative "./mri.rb"
# Notes
# - a strings memory address is defined by it's object_id shift left by 1, thatswhy "foo".object_id << 1
# - strings are embedded directly into the RString structure if they are less that 24 chars
# - for strings longer than 24 chars, their content is placed on the heap
short_string = "foobar"
short_rstring = Zstandard::MRI::RString.new(FFI::Pointer.new(short_string.object_id << 1))
short_rstring[:as][:ary].to_s
# => "foobar"
long_string = "foobar" * 4 # 24 chars is the threshold for string embedding
long_rstring = Zstandard::MRI::RString.new(FFI::Pointer.new(long_string.object_id << 1))
long_rstring[:as][:heap][:len]
# => 24
long_rstring[:as][:heap][:ptr].read_bytes(long_rstring[:as][:heap][:len])
# => "foobarfoobarfoobarfoobar"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment