mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
307388ea19
* Add a "pinning" reference A `Fiddle::Pinned` objects will prevent the objects they point to from moving. This is useful in the case where you need to pass a reference to a C extension that keeps the address in a global and needs the address to be stable. For example: ```ruby class Foo A = "hi" # this is an embedded string some_c_function A # A might move! end ``` If `A` moves, then the underlying string buffer may also move. `Fiddle::Pinned` will prevent the object from moving: ```ruby class Foo A = "hi" # this is an embedded string A_pinner = Fiddle::Pinned.new(A) # :nodoc: some_c_function A # A can't move because of `Fiddle::Pinned` end ``` This is a similar strategy to what Graal uses: https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/PinnedObject.html#getObject-- * rename global to match exception name * Introduce generic Fiddle::Error and rearrange error classes Fiddle::Error is the generic exception base class for Fiddle exceptions. This commit introduces the class and rearranges Fiddle exceptions to inherit from it. https://github.com/ruby/fiddle/commit/ac52d00223
477 lines
12 KiB
C
477 lines
12 KiB
C
#include <ruby.h>
|
|
#include <fiddle.h>
|
|
|
|
VALUE rb_cHandle;
|
|
|
|
struct dl_handle {
|
|
void *ptr;
|
|
int open;
|
|
int enable_close;
|
|
};
|
|
|
|
#ifdef _WIN32
|
|
# ifndef _WIN32_WCE
|
|
static void *
|
|
w32_coredll(void)
|
|
{
|
|
MEMORY_BASIC_INFORMATION m;
|
|
memset(&m, 0, sizeof(m));
|
|
if( !VirtualQuery(_errno, &m, sizeof(m)) ) return NULL;
|
|
return m.AllocationBase;
|
|
}
|
|
# endif
|
|
|
|
static int
|
|
w32_dlclose(void *ptr)
|
|
{
|
|
# ifndef _WIN32_WCE
|
|
if( ptr == w32_coredll() ) return 0;
|
|
# endif
|
|
if( FreeLibrary((HMODULE)ptr) ) return 0;
|
|
return errno = rb_w32_map_errno(GetLastError());
|
|
}
|
|
#define dlclose(ptr) w32_dlclose(ptr)
|
|
#endif
|
|
|
|
static void
|
|
fiddle_handle_free(void *ptr)
|
|
{
|
|
struct dl_handle *fiddle_handle = ptr;
|
|
if( fiddle_handle->ptr && fiddle_handle->open && fiddle_handle->enable_close ){
|
|
dlclose(fiddle_handle->ptr);
|
|
}
|
|
xfree(ptr);
|
|
}
|
|
|
|
static size_t
|
|
fiddle_handle_memsize(const void *ptr)
|
|
{
|
|
return sizeof(struct dl_handle);
|
|
}
|
|
|
|
static const rb_data_type_t fiddle_handle_data_type = {
|
|
"fiddle/handle",
|
|
{0, fiddle_handle_free, fiddle_handle_memsize,},
|
|
};
|
|
|
|
/*
|
|
* call-seq: close
|
|
*
|
|
* Close this handle.
|
|
*
|
|
* Calling close more than once will raise a Fiddle::DLError exception.
|
|
*/
|
|
static VALUE
|
|
rb_fiddle_handle_close(VALUE self)
|
|
{
|
|
struct dl_handle *fiddle_handle;
|
|
|
|
TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle);
|
|
if(fiddle_handle->open) {
|
|
int ret = dlclose(fiddle_handle->ptr);
|
|
fiddle_handle->open = 0;
|
|
|
|
/* Check dlclose for successful return value */
|
|
if(ret) {
|
|
#if defined(HAVE_DLERROR)
|
|
rb_raise(rb_eFiddleDLError, "%s", dlerror());
|
|
#else
|
|
rb_raise(rb_eFiddleDLError, "could not close handle");
|
|
#endif
|
|
}
|
|
return INT2NUM(ret);
|
|
}
|
|
rb_raise(rb_eFiddleDLError, "dlclose() called too many times");
|
|
|
|
UNREACHABLE;
|
|
}
|
|
|
|
static VALUE
|
|
rb_fiddle_handle_s_allocate(VALUE klass)
|
|
{
|
|
VALUE obj;
|
|
struct dl_handle *fiddle_handle;
|
|
|
|
obj = TypedData_Make_Struct(rb_cHandle, struct dl_handle, &fiddle_handle_data_type, fiddle_handle);
|
|
fiddle_handle->ptr = 0;
|
|
fiddle_handle->open = 0;
|
|
fiddle_handle->enable_close = 0;
|
|
|
|
return obj;
|
|
}
|
|
|
|
static VALUE
|
|
predefined_fiddle_handle(void *handle)
|
|
{
|
|
VALUE obj = rb_fiddle_handle_s_allocate(rb_cHandle);
|
|
struct dl_handle *fiddle_handle = DATA_PTR(obj);
|
|
|
|
fiddle_handle->ptr = handle;
|
|
fiddle_handle->open = 1;
|
|
OBJ_FREEZE(obj);
|
|
return obj;
|
|
}
|
|
|
|
/*
|
|
* call-seq:
|
|
* new(library = nil, flags = Fiddle::RTLD_LAZY | Fiddle::RTLD_GLOBAL)
|
|
*
|
|
* Create a new handler that opens +library+ with +flags+.
|
|
*
|
|
* If no +library+ is specified or +nil+ is given, DEFAULT is used, which is
|
|
* the equivalent to RTLD_DEFAULT. See <code>man 3 dlopen</code> for more.
|
|
*
|
|
* lib = Fiddle::Handle.new
|
|
*
|
|
* The default is dependent on OS, and provide a handle for all libraries
|
|
* already loaded. For example, in most cases you can use this to access +libc+
|
|
* functions, or ruby functions like +rb_str_new+.
|
|
*/
|
|
static VALUE
|
|
rb_fiddle_handle_initialize(int argc, VALUE argv[], VALUE self)
|
|
{
|
|
void *ptr;
|
|
struct dl_handle *fiddle_handle;
|
|
VALUE lib, flag;
|
|
char *clib;
|
|
int cflag;
|
|
const char *err;
|
|
|
|
switch( rb_scan_args(argc, argv, "02", &lib, &flag) ){
|
|
case 0:
|
|
clib = NULL;
|
|
cflag = RTLD_LAZY | RTLD_GLOBAL;
|
|
break;
|
|
case 1:
|
|
clib = NIL_P(lib) ? NULL : StringValueCStr(lib);
|
|
cflag = RTLD_LAZY | RTLD_GLOBAL;
|
|
break;
|
|
case 2:
|
|
clib = NIL_P(lib) ? NULL : StringValueCStr(lib);
|
|
cflag = NUM2INT(flag);
|
|
break;
|
|
default:
|
|
rb_bug("rb_fiddle_handle_new");
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
if( !clib ){
|
|
HANDLE rb_libruby_handle(void);
|
|
ptr = rb_libruby_handle();
|
|
}
|
|
else if( STRCASECMP(clib, "libc") == 0
|
|
# ifdef RUBY_COREDLL
|
|
|| STRCASECMP(clib, RUBY_COREDLL) == 0
|
|
|| STRCASECMP(clib, RUBY_COREDLL".dll") == 0
|
|
# endif
|
|
){
|
|
# ifdef _WIN32_WCE
|
|
ptr = dlopen("coredll.dll", cflag);
|
|
# else
|
|
(void)cflag;
|
|
ptr = w32_coredll();
|
|
# endif
|
|
}
|
|
else
|
|
#endif
|
|
ptr = dlopen(clib, cflag);
|
|
#if defined(HAVE_DLERROR)
|
|
if( !ptr && (err = dlerror()) ){
|
|
rb_raise(rb_eFiddleDLError, "%s", err);
|
|
}
|
|
#else
|
|
if( !ptr ){
|
|
err = dlerror();
|
|
rb_raise(rb_eFiddleDLError, "%s", err);
|
|
}
|
|
#endif
|
|
TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle);
|
|
if( fiddle_handle->ptr && fiddle_handle->open && fiddle_handle->enable_close ){
|
|
dlclose(fiddle_handle->ptr);
|
|
}
|
|
fiddle_handle->ptr = ptr;
|
|
fiddle_handle->open = 1;
|
|
fiddle_handle->enable_close = 0;
|
|
|
|
if( rb_block_given_p() ){
|
|
rb_ensure(rb_yield, self, rb_fiddle_handle_close, self);
|
|
}
|
|
|
|
return Qnil;
|
|
}
|
|
|
|
/*
|
|
* call-seq: enable_close
|
|
*
|
|
* Enable a call to dlclose() when this handle is garbage collected.
|
|
*/
|
|
static VALUE
|
|
rb_fiddle_handle_enable_close(VALUE self)
|
|
{
|
|
struct dl_handle *fiddle_handle;
|
|
|
|
TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle);
|
|
fiddle_handle->enable_close = 1;
|
|
return Qnil;
|
|
}
|
|
|
|
/*
|
|
* call-seq: disable_close
|
|
*
|
|
* Disable a call to dlclose() when this handle is garbage collected.
|
|
*/
|
|
static VALUE
|
|
rb_fiddle_handle_disable_close(VALUE self)
|
|
{
|
|
struct dl_handle *fiddle_handle;
|
|
|
|
TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle);
|
|
fiddle_handle->enable_close = 0;
|
|
return Qnil;
|
|
}
|
|
|
|
/*
|
|
* call-seq: close_enabled?
|
|
*
|
|
* Returns +true+ if dlclose() will be called when this handle is garbage collected.
|
|
*
|
|
* See man(3) dlclose() for more info.
|
|
*/
|
|
static VALUE
|
|
rb_fiddle_handle_close_enabled_p(VALUE self)
|
|
{
|
|
struct dl_handle *fiddle_handle;
|
|
|
|
TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle);
|
|
|
|
if(fiddle_handle->enable_close) return Qtrue;
|
|
return Qfalse;
|
|
}
|
|
|
|
/*
|
|
* call-seq: to_i
|
|
*
|
|
* Returns the memory address for this handle.
|
|
*/
|
|
static VALUE
|
|
rb_fiddle_handle_to_i(VALUE self)
|
|
{
|
|
struct dl_handle *fiddle_handle;
|
|
|
|
TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle);
|
|
return PTR2NUM(fiddle_handle);
|
|
}
|
|
|
|
static VALUE fiddle_handle_sym(void *handle, VALUE symbol);
|
|
|
|
/*
|
|
* Document-method: sym
|
|
*
|
|
* call-seq: sym(name)
|
|
*
|
|
* Get the address as an Integer for the function named +name+.
|
|
*/
|
|
static VALUE
|
|
rb_fiddle_handle_sym(VALUE self, VALUE sym)
|
|
{
|
|
struct dl_handle *fiddle_handle;
|
|
|
|
TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle);
|
|
if( ! fiddle_handle->open ){
|
|
rb_raise(rb_eFiddleDLError, "closed handle");
|
|
}
|
|
|
|
return fiddle_handle_sym(fiddle_handle->ptr, sym);
|
|
}
|
|
|
|
#ifndef RTLD_NEXT
|
|
#define RTLD_NEXT NULL
|
|
#endif
|
|
#ifndef RTLD_DEFAULT
|
|
#define RTLD_DEFAULT NULL
|
|
#endif
|
|
|
|
/*
|
|
* Document-method: sym
|
|
*
|
|
* call-seq: sym(name)
|
|
*
|
|
* Get the address as an Integer for the function named +name+. The function
|
|
* is searched via dlsym on RTLD_NEXT.
|
|
*
|
|
* See man(3) dlsym() for more info.
|
|
*/
|
|
static VALUE
|
|
rb_fiddle_handle_s_sym(VALUE self, VALUE sym)
|
|
{
|
|
return fiddle_handle_sym(RTLD_NEXT, sym);
|
|
}
|
|
|
|
static VALUE
|
|
fiddle_handle_sym(void *handle, VALUE symbol)
|
|
{
|
|
#if defined(HAVE_DLERROR)
|
|
const char *err;
|
|
# define CHECK_DLERROR if ((err = dlerror()) != 0) { func = 0; }
|
|
#else
|
|
# define CHECK_DLERROR
|
|
#endif
|
|
void (*func)();
|
|
const char *name = StringValueCStr(symbol);
|
|
|
|
#ifdef HAVE_DLERROR
|
|
dlerror();
|
|
#endif
|
|
func = (void (*)())(VALUE)dlsym(handle, name);
|
|
CHECK_DLERROR;
|
|
#if defined(FUNC_STDCALL)
|
|
if( !func ){
|
|
int i;
|
|
int len = (int)strlen(name);
|
|
char *name_n;
|
|
#if defined(__CYGWIN__) || defined(_WIN32) || defined(__MINGW32__)
|
|
{
|
|
char *name_a = (char*)xmalloc(len+2);
|
|
strcpy(name_a, name);
|
|
name_n = name_a;
|
|
name_a[len] = 'A';
|
|
name_a[len+1] = '\0';
|
|
func = dlsym(handle, name_a);
|
|
CHECK_DLERROR;
|
|
if( func ) goto found;
|
|
name_n = xrealloc(name_a, len+6);
|
|
}
|
|
#else
|
|
name_n = (char*)xmalloc(len+6);
|
|
#endif
|
|
memcpy(name_n, name, len);
|
|
name_n[len++] = '@';
|
|
for( i = 0; i < 256; i += 4 ){
|
|
sprintf(name_n + len, "%d", i);
|
|
func = dlsym(handle, name_n);
|
|
CHECK_DLERROR;
|
|
if( func ) break;
|
|
}
|
|
if( func ) goto found;
|
|
name_n[len-1] = 'A';
|
|
name_n[len++] = '@';
|
|
for( i = 0; i < 256; i += 4 ){
|
|
sprintf(name_n + len, "%d", i);
|
|
func = dlsym(handle, name_n);
|
|
CHECK_DLERROR;
|
|
if( func ) break;
|
|
}
|
|
found:
|
|
xfree(name_n);
|
|
}
|
|
#endif
|
|
if( !func ){
|
|
rb_raise(rb_eFiddleDLError, "unknown symbol \"%"PRIsVALUE"\"", symbol);
|
|
}
|
|
|
|
return PTR2NUM(func);
|
|
}
|
|
|
|
void
|
|
Init_fiddle_handle(void)
|
|
{
|
|
/*
|
|
* Document-class: Fiddle::Handle
|
|
*
|
|
* The Fiddle::Handle is the manner to access the dynamic library
|
|
*
|
|
* == Example
|
|
*
|
|
* === Setup
|
|
*
|
|
* libc_so = "/lib64/libc.so.6"
|
|
* => "/lib64/libc.so.6"
|
|
* @handle = Fiddle::Handle.new(libc_so)
|
|
* => #<Fiddle::Handle:0x00000000d69ef8>
|
|
*
|
|
* === Setup, with flags
|
|
*
|
|
* libc_so = "/lib64/libc.so.6"
|
|
* => "/lib64/libc.so.6"
|
|
* @handle = Fiddle::Handle.new(libc_so, Fiddle::RTLD_LAZY | Fiddle::RTLD_GLOBAL)
|
|
* => #<Fiddle::Handle:0x00000000d69ef8>
|
|
*
|
|
* See RTLD_LAZY and RTLD_GLOBAL
|
|
*
|
|
* === Addresses to symbols
|
|
*
|
|
* strcpy_addr = @handle['strcpy']
|
|
* => 140062278451968
|
|
*
|
|
* or
|
|
*
|
|
* strcpy_addr = @handle.sym('strcpy')
|
|
* => 140062278451968
|
|
*
|
|
*/
|
|
rb_cHandle = rb_define_class_under(mFiddle, "Handle", rb_cObject);
|
|
rb_define_alloc_func(rb_cHandle, rb_fiddle_handle_s_allocate);
|
|
rb_define_singleton_method(rb_cHandle, "sym", rb_fiddle_handle_s_sym, 1);
|
|
rb_define_singleton_method(rb_cHandle, "[]", rb_fiddle_handle_s_sym, 1);
|
|
|
|
/* Document-const: NEXT
|
|
*
|
|
* A predefined pseudo-handle of RTLD_NEXT
|
|
*
|
|
* Which will find the next occurrence of a function in the search order
|
|
* after the current library.
|
|
*/
|
|
rb_define_const(rb_cHandle, "NEXT", predefined_fiddle_handle(RTLD_NEXT));
|
|
|
|
/* Document-const: DEFAULT
|
|
*
|
|
* A predefined pseudo-handle of RTLD_DEFAULT
|
|
*
|
|
* Which will find the first occurrence of the desired symbol using the
|
|
* default library search order
|
|
*/
|
|
rb_define_const(rb_cHandle, "DEFAULT", predefined_fiddle_handle(RTLD_DEFAULT));
|
|
|
|
/* Document-const: RTLD_GLOBAL
|
|
*
|
|
* rtld Fiddle::Handle flag.
|
|
*
|
|
* The symbols defined by this library will be made available for symbol
|
|
* resolution of subsequently loaded libraries.
|
|
*/
|
|
rb_define_const(rb_cHandle, "RTLD_GLOBAL", INT2NUM(RTLD_GLOBAL));
|
|
|
|
/* Document-const: RTLD_LAZY
|
|
*
|
|
* rtld Fiddle::Handle flag.
|
|
*
|
|
* Perform lazy binding. Only resolve symbols as the code that references
|
|
* them is executed. If the symbol is never referenced, then it is never
|
|
* resolved. (Lazy binding is only performed for function references;
|
|
* references to variables are always immediately bound when the library
|
|
* is loaded.)
|
|
*/
|
|
rb_define_const(rb_cHandle, "RTLD_LAZY", INT2NUM(RTLD_LAZY));
|
|
|
|
/* Document-const: RTLD_NOW
|
|
*
|
|
* rtld Fiddle::Handle flag.
|
|
*
|
|
* If this value is specified or the environment variable LD_BIND_NOW is
|
|
* set to a nonempty string, all undefined symbols in the library are
|
|
* resolved before Fiddle.dlopen returns. If this cannot be done an error
|
|
* is returned.
|
|
*/
|
|
rb_define_const(rb_cHandle, "RTLD_NOW", INT2NUM(RTLD_NOW));
|
|
|
|
rb_define_method(rb_cHandle, "initialize", rb_fiddle_handle_initialize, -1);
|
|
rb_define_method(rb_cHandle, "to_i", rb_fiddle_handle_to_i, 0);
|
|
rb_define_method(rb_cHandle, "close", rb_fiddle_handle_close, 0);
|
|
rb_define_method(rb_cHandle, "sym", rb_fiddle_handle_sym, 1);
|
|
rb_define_method(rb_cHandle, "[]", rb_fiddle_handle_sym, 1);
|
|
rb_define_method(rb_cHandle, "disable_close", rb_fiddle_handle_disable_close, 0);
|
|
rb_define_method(rb_cHandle, "enable_close", rb_fiddle_handle_enable_close, 0);
|
|
rb_define_method(rb_cHandle, "close_enabled?", rb_fiddle_handle_close_enabled_p, 0);
|
|
}
|
|
|
|
/* vim: set noet sws=4 sw=4: */
|