From c1fb6a82dcfbd308390d380d62c7127635fc66db Mon Sep 17 00:00:00 2001 From: tenderlove Date: Tue, 27 Nov 2012 19:54:50 +0000 Subject: * ext/fiddle/handle.c: Make Fiddle independent of DL, copy DL::Handle to Fiddle::Handle. * ext/fiddle/pointer.c: Make Fiddle independent of DL, copy DL::Pointer to Fiddle::Pointer. * test/fiddle/test_func.rb: relevent tests * test/fiddle/test_handle.rb: ditto * test/fiddle/test_pointer.rb: ditto * ext/dl/lib/dl/struct.rb: use Fiddle::Pointer if available * ext/fiddle/extconf.rb: check for dlfcn.h * ext/fiddle/fiddle.c: add constants for sizeof() things * ext/fiddle/fiddle.h: include dlfcn.h * ext/fiddle/function.c: expose a C function for creating new Fiddle::Function objects. * ext/fiddle/lib/fiddle.rb: include constants for dl backwards compat * ext/fiddle/lib/fiddle/function.rb: read the pointer from the function for dl backwards compat. * test/dl/test_callback.rb: check the addresses of the pointers rather than their types. * test/fiddle/helper.rb: remove dependency on dl * test/fiddle/test_closure.rb: ditto * test/fiddle/test_fiddle.rb: ditto git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37907 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ext/fiddle/handle.c | 465 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 465 insertions(+) create mode 100644 ext/fiddle/handle.c (limited to 'ext/fiddle/handle.c') diff --git a/ext/fiddle/handle.c b/ext/fiddle/handle.c new file mode 100644 index 0000000000..346635f6e5 --- /dev/null +++ b/ext/fiddle/handle.c @@ -0,0 +1,465 @@ +#include +#include + +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); + } +} + +static size_t +fiddle_handle_memsize(const void *ptr) +{ + return ptr ? sizeof(struct dl_handle) : 0; +} + +static const rb_data_type_t fiddle_handle_data_type = { + "fiddle/handle", + {0, fiddle_handle_free, fiddle_handle_memsize,}, +}; + +/* + * call-seq: close + * + * Close this Fiddle::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_eFiddleError, "%s", dlerror()); +#else + rb_raise(rb_eFiddleError, "could not close handle"); +#endif + } + return INT2NUM(ret); + } + rb_raise(rb_eFiddleError, "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: + * initialize(lib = nil, flags = Fiddle::RTLD_LAZY | Fiddle::RTLD_GLOBAL) + * + * Create a new handler that opens library named +lib+ with +flags+. If no + * library is specified, RTLD_DEFAULT is used. + */ +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 : StringValuePtr(lib); + cflag = RTLD_LAZY | RTLD_GLOBAL; + break; + case 2: + clib = NIL_P(lib) ? NULL : StringValuePtr(lib); + cflag = NUM2INT(flag); + break; + default: + rb_bug("rb_fiddle_handle_new"); + } + + rb_secure(2); + +#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 + ptr = w32_coredll(); +# endif + } + else +#endif + ptr = dlopen(clib, cflag); +#if defined(HAVE_DLERROR) + if( !ptr && (err = dlerror()) ){ + rb_raise(rb_eFiddleError, "%s", err); + } +#else + if( !ptr ){ + err = dlerror(); + rb_raise(rb_eFiddleError, "%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 Fiddle::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 Fiddle::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 Fiddle::Handle is + * garbage collected. + */ +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, const char *symbol); + +/* + * Document-method: sym + * Document-method: [] + * + * 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_eFiddleError, "closed handle"); + } + + return fiddle_handle_sym(fiddle_handle->ptr, StringValueCStr(sym)); +} + +#ifndef RTLD_NEXT +#define RTLD_NEXT NULL +#endif +#ifndef RTLD_DEFAULT +#define RTLD_DEFAULT NULL +#endif + +/* + * Document-method: sym + * Document-method: [] + * + * 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, StringValueCStr(sym)); +} + +static VALUE +fiddle_handle_sym(void *handle, const char *name) +{ +#if defined(HAVE_DLERROR) + const char *err; +# define CHECK_DLERROR if( err = dlerror() ){ func = 0; } +#else +# define CHECK_DLERROR +#endif + void (*func)(); + + rb_secure(2); +#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_eFiddleError, "unknown symbol \"%s\"", name); + } + + 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) + * => # + * + * === 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) + * => # + * + * === 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 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: */ -- cgit v1.2.3