Ruby: dln.c Source File

Ruby 4.0.5p0 (2026-05-20 revision 64336ffd0ee9e1f4c05891695a3d7b49cb709721)

dln.c
1/**********************************************************************
2
3 dln.c -
4
5 $Author$
6 created at: Tue Jan 18 17:05:06 JST 1994
7
8 Copyright (C) 1993-2007 Yukihiro Matsumoto
9
10**********************************************************************/
11
12#ifdef RUBY_EXPORT
13#include "ruby/ruby.h"
14#define dln_notimplement rb_notimplement
15#define dln_memerror rb_memerror
16#define dln_exit rb_exit
17#define dln_loaderror rb_loaderror
18#define dln_fatalerror rb_fatal
19#else
20#define dln_notimplement --->>> dln not implemented <<<---
21#define dln_memerror abort
22#define dln_exit exit
23static void dln_loaderror(const char *format, ...);
24#define dln_fatalerror dln_loaderror
25#endif
26#include "dln.h"
27#include "internal.h"
28#include "internal/box.h"
29#include "internal/compilers.h"
30
31#ifdef HAVE_STDLIB_H
32# include <stdlib.h>
33#endif
34
35#if defined(HAVE_ALLOCA_H)
36#include <alloca.h>
37#endif
38
39#ifdef HAVE_STRING_H
40# include <string.h>
41#else
42# include <strings.h>
43#endif
44
45#if defined __APPLE__
46# include <AvailabilityMacros.h>
47#endif
48
49#ifndef xmalloc
50void *xmalloc();
51void *xcalloc();
52void *xrealloc();
53#endif
54
55#undef free
56#define free(x) xfree(x)
57
58#include <stdio.h>
59#if defined(_WIN32)
60#include "missing/file.h"
61#endif
62#include <sys/types.h>
63#include <sys/stat.h>
64
65#ifndef S_ISDIR
66# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
67#endif
68
69#ifdef HAVE_SYS_PARAM_H
70# include <sys/param.h>
71#endif
72#ifndef MAXPATHLEN
73# define MAXPATHLEN 1024
74#endif
75
76#ifdef HAVE_UNISTD_H
77# include <unistd.h>
78#endif
79
80#ifndef dln_loaderror
81static void
82dln_loaderror(const char *format, ...)
83{
84 va_list ap;
85 va_start(ap, format);
86 vfprintf(stderr, format, ap);
87 va_end(ap);
88 abort();
89}
90#endif
91
92#if defined(HAVE_DLOPEN) && !defined(_AIX) && !defined(_UNICOSMP)
93/* dynamic load with dlopen() */
94# define USE_DLN_DLOPEN
95#endif
96
97#if defined(__hp9000s300) || ((defined(__NetBSD__) || defined(__FreeBSD__) || defined(__OpenBSD__)) && !defined(__ELF__)) || defined(NeXT)
98# define EXTERNAL_PREFIX "_"
99#else
100# define EXTERNAL_PREFIX ""
101#endif
102#define FUNCNAME_PREFIX EXTERNAL_PREFIX"Init_"
103
104#if defined __CYGWIN__ || defined DOSISH
105#define isdirsep(x) ((x) == '/' || (x) == '\\')
106#else
107#define isdirsep(x) ((x) == '/')
108#endif
109
110#if defined(_WIN32) || defined(USE_DLN_DLOPEN)
111struct string_part {
112 const char *ptr;
113 size_t len;
114};
115
116static struct string_part
117init_funcname_len(const char *file)
118{
119 const char *p = file, *base, *dot = NULL;
120
121 /* Load the file as an object one */
122 for (base = p; *p; p++) { /* Find position of last '/' */
123 if (*p == '.' && !dot) dot = p;
124 if (isdirsep(*p)) base = p+1, dot = NULL;
125 }
126 /* Delete suffix if it exists */
127 const size_t len = (dot ? dot : p) - base;
128 return (struct string_part){base, len};
129}
130
131static inline char *
132concat_funcname(char *buf, const char *prefix, size_t plen, const struct string_part base)
133{
134 if (!buf) {
135 dln_memerror();
136 }
137 memcpy(buf, prefix, plen);
138 memcpy(buf + plen, base.ptr, base.len);
139 buf[plen + base.len] = '\0';
140 return buf;
141}
142
143#define build_funcname(prefix, buf, file) do {\
144 const struct string_part f = init_funcname_len(file);\
145 const size_t plen = sizeof(prefix "") - 1;\
146 *(buf) = concat_funcname(ALLOCA_N(char, plen+f.len+1), prefix, plen, f);\
147} while (0)
148
149#define init_funcname(buf, file) build_funcname(FUNCNAME_PREFIX, buf, file)
150#endif
151
152#ifdef USE_DLN_DLOPEN
153# include <dlfcn.h>
154#endif
155
156#if defined(_AIX)
157#include <ctype.h> /* for isdigit() */
158#include <errno.h> /* for global errno */
159#include <sys/ldr.h>
160#endif
161
162#ifdef NeXT
163#if NS_TARGET_MAJOR < 4
164#include <mach-o/rld.h>
165#else
166#include <mach-o/dyld.h>
167#ifndef NSLINKMODULE_OPTION_BINDNOW
168#define NSLINKMODULE_OPTION_BINDNOW 1
169#endif
170#endif
171#endif
172
173#ifdef _WIN32
174#include <windows.h>
175#include <imagehlp.h>
176#endif
177
178#ifdef _WIN32
179static const char *
180dln_strerror(char *message, size_t size)
181{
182 int error = GetLastError();
183 char *p = message;
184 size_t len = snprintf(message, size, "%d: ", error);
185
186#define format_message(sublang) FormatMessage(\
187 FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, \
188 NULL, error, MAKELANGID(LANG_NEUTRAL, (sublang)), \
189 message + len, size - len, NULL)
190 if (format_message(SUBLANG_ENGLISH_US) == 0)
191 format_message(SUBLANG_DEFAULT);
192 for (p = message + len; *p; p++) {
193 if (*p == '\n' || *p == '\r')
194 *p = ' ';
195 }
196 return message;
197}
198#define dln_strerror() dln_strerror(message, sizeof message)
199#elif defined USE_DLN_DLOPEN
200static const char *
201dln_strerror(void)
202{
203 return (char*)dlerror();
204}
205#endif
206
207#if defined(_AIX)
208static void
209aix_loaderror(const char *pathname)
210{
211 char *message[1024], errbuf[1024];
212 int i;
213#define ERRBUF_APPEND(s) strlcat(errbuf, (s), sizeof(errbuf))
214 snprintf(errbuf, sizeof(errbuf), "load failed - %s. ", pathname);
215
216 if (loadquery(L_GETMESSAGES, &message[0], sizeof(message)) != -1) {
217 ERRBUF_APPEND("Please issue below command for detailed reasons:\n\t");
218 ERRBUF_APPEND("/usr/sbin/execerror ruby ");
219 for (i=0; message[i]; i++) {
220 ERRBUF_APPEND("\"");
221 ERRBUF_APPEND(message[i]);
222 ERRBUF_APPEND("\" ");
223 }
224 ERRBUF_APPEND("\n");
225 }
226 else {
227 ERRBUF_APPEND(strerror(errno));
228 ERRBUF_APPEND("[loadquery failed]");
229 }
230 dln_loaderror("%s", errbuf);
231}
232#endif
233
234#if defined _WIN32 && defined RUBY_EXPORT
235HANDLE rb_libruby_handle(void);
236
237static int
238rb_w32_check_imported(HMODULE ext, HMODULE mine)
239{
240 ULONG size;
241 const IMAGE_IMPORT_DESCRIPTOR *desc;
242
243 desc = ImageDirectoryEntryToData(ext, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size);
244 if (!desc) return 0;
245 while (desc->Name) {
246 PIMAGE_THUNK_DATA pint = (PIMAGE_THUNK_DATA)((char *)ext + desc->Characteristics);
247 PIMAGE_THUNK_DATA piat = (PIMAGE_THUNK_DATA)((char *)ext + desc->FirstThunk);
248 for (; piat->u1.Function; piat++, pint++) {
249 static const char prefix[] = "rb_";
250 PIMAGE_IMPORT_BY_NAME pii;
251 const char *name;
252
253 if (IMAGE_SNAP_BY_ORDINAL(pint->u1.Ordinal)) continue;
254 pii = (PIMAGE_IMPORT_BY_NAME)((char *)ext + (size_t)pint->u1.AddressOfData);
255 name = (const char *)pii->Name;
256 if (strncmp(name, prefix, sizeof(prefix) - 1) == 0) {
257 FARPROC addr = GetProcAddress(mine, name);
258 if (addr) return (FARPROC)piat->u1.Function == addr;
259 }
260 }
261 desc++;
262 }
263 return 1;
264}
265#endif
266
267#if defined(DLN_NEEDS_ALT_SEPARATOR) && DLN_NEEDS_ALT_SEPARATOR
268#define translit_separator(src) do { \
269 char *tmp = ALLOCA_N(char, strlen(src) + 1), *p = tmp, c; \
270 do { \
271 *p++ = ((c = *file++) == '/') ? DLN_NEEDS_ALT_SEPARATOR : c; \
272 } while (c); \
273 (src) = tmp; \
274 } while (0)
275#else
276#define translit_separator(str) (void)(str)
277#endif
278
279#ifdef USE_DLN_DLOPEN
280# include "ruby/internal/stdbool.h"
281# include "internal/warnings.h"
282static bool
283dln_incompatible_func(void *handle, const char *funcname, void *const fp, const char **libname)
284{
285 void *ex = dlsym(handle, funcname);
286 if (!ex) return false;
287 if (ex == fp) return false;
288# if defined(HAVE_DLADDR) && !defined(__CYGWIN__)
289 Dl_info dli;
290 if (dladdr(ex, &dli)) {
291 *libname = dli.dli_fname;
292 }
293# endif
294 return true;
295}
296
297COMPILER_WARNING_PUSH
298#if defined(__clang__) || GCC_VERSION_SINCE(4, 2, 0)
299COMPILER_WARNING_IGNORED(-Wpedantic)
300#endif
301static bool
302dln_incompatible_library_p(void *handle, const char **libname)
303{
304#define check_func(func) \
305 if (dln_incompatible_func(handle, EXTERNAL_PREFIX #func, (void *)&func, libname)) \
306 return true
307 check_func(ruby_xmalloc);
308 return false;
309}
310COMPILER_WARNING_POP
311#endif
312
313#if !defined(MAC_OS_X_VERSION_MIN_REQUIRED)
314/* assume others than old Mac OS X have no problem */
315# define dln_disable_dlclose() false
316
317#elif !defined(MAC_OS_X_VERSION_10_11) || \
318 (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11)
319/* targeting older versions only */
320# define dln_disable_dlclose() true
321
322#elif MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_11
323/* targeting newer versions only */
324# define dln_disable_dlclose() false
325
326#else
327/* support both versions, and check at runtime */
328# include <sys/sysctl.h>
329
330static bool
331dln_disable_dlclose(void)
332{
333 int mib[] = {CTL_KERN, KERN_OSREV};
334 int32_t rev;
335 size_t size = sizeof(rev);
336 if (sysctl(mib, numberof(mib), &rev, &size, NULL, 0)) return true;
337 if (rev < MAC_OS_X_VERSION_10_11) return true;
338 return false;
339}
340#endif
341
342#if defined(_WIN32) || defined(USE_DLN_DLOPEN)
343static void *
344dln_open(const char *file)
345{
346 static const char incompatible[] = "incompatible library version";
347 const char *error = NULL;
348 void *handle;
349
350#if defined(_WIN32)
351# define DLN_DEFINED
352 char message[1024];
353
354 /* Convert the file path to wide char */
355 WCHAR *winfile = rb_w32_mbstr_to_wstr(CP_UTF8, file, -1, NULL);
356 if (!winfile) {
357 dln_memerror();
358 }
359
360 /* Load file */
361 handle = LoadLibraryW(winfile);
362 free(winfile);
363
364 if (!handle) {
365 error = dln_strerror();
366 goto failed;
367 }
368
369# if defined(RUBY_EXPORT)
370 if (!rb_w32_check_imported(handle, rb_libruby_handle())) {
371 FreeLibrary(handle);
372 error = incompatible;
373 goto failed;
374 }
375# endif
376
377#elif defined(USE_DLN_DLOPEN)
378# define DLN_DEFINED
379
380# ifndef RTLD_LAZY
381# define RTLD_LAZY 1
382# endif
383# ifdef __INTERIX
384# undef RTLD_GLOBAL
385# endif
386# ifndef RTLD_GLOBAL
387# define RTLD_GLOBAL 0
388# endif
389# ifndef RTLD_LOCAL
390# define RTLD_LOCAL 0 /* TODO: 0??? some systems (including libc) use 0x00100 for RTLD_GLOBAL, 0x00000 for RTLD_LOCAL */
391# endif
392
393 /* Load file */
394 int mode = rb_box_available() ? RTLD_LAZY|RTLD_LOCAL : RTLD_LAZY|RTLD_GLOBAL;
395 handle = dlopen(file, mode);
396 if (handle == NULL) {
397 error = dln_strerror();
398 goto failed;
399 }
400
401# if defined(RUBY_EXPORT)
402 {
403 const char *libruby_name = NULL;
404 if (dln_incompatible_library_p(handle, &libruby_name)) {
405 if (dln_disable_dlclose()) {
406 /* dlclose() segfaults */
407 if (libruby_name) {
408 dln_fatalerror("linked to incompatible %s - %s", libruby_name, file);
409 }
410 dln_fatalerror("%s - %s", incompatible, file);
411 }
412 else {
413 if (libruby_name) {
414 const size_t len = strlen(libruby_name);
415 char *const tmp = ALLOCA_N(char, len + 1);
416 if (tmp) memcpy(tmp, libruby_name, len + 1);
417 libruby_name = tmp;
418 }
419 dlclose(handle);
420 if (libruby_name) {
421 dln_loaderror("linked to incompatible %s - %s", libruby_name, file);
422 }
423 error = incompatible;
424 goto failed;
425 }
426 }
427 }
428# endif
429#endif
430
431 return handle;
432
433 failed:
434 dln_loaderror("%s - %s", error, file);
435}
436
437static void *
438dln_sym(void *handle, const char *symbol)
439{
440#if defined(_WIN32)
441 return GetProcAddress(handle, symbol);
442#elif defined(USE_DLN_DLOPEN)
443 return dlsym(handle, symbol);
444#endif
445}
446
447static uintptr_t
448dln_sym_func(void *handle, const char *symbol)
449{
450 void *func = dln_sym(handle, symbol);
451
452 if (func == NULL) {
453 const char *error;
454#if defined(_WIN32)
455 char message[1024];
456 error = dln_strerror();
457#elif defined(USE_DLN_DLOPEN)
458 const size_t errlen = strlen(error = dln_strerror()) + 1;
459 error = memcpy(ALLOCA_N(char, errlen), error, errlen);
460#endif
461 dln_loaderror("%s - %s", error, symbol);
462 }
463 return (uintptr_t)func;
464}
465
466#define dln_sym_callable(rettype, argtype, handle, symbol) \
467 (*(rettype (*)argtype)dln_sym_func(handle, symbol))
468#endif
469
470void *
471dln_symbol(void *handle, const char *symbol)
472{
473#if defined(_WIN32) || defined(USE_DLN_DLOPEN)
474 if (EXTERNAL_PREFIX[0]) {
475 const size_t symlen = strlen(symbol);
476 char *const tmp = ALLOCA_N(char, symlen + sizeof(EXTERNAL_PREFIX));
477 if (!tmp) dln_memerror();
478 memcpy(tmp, EXTERNAL_PREFIX, sizeof(EXTERNAL_PREFIX) - 1);
479 memcpy(tmp + sizeof(EXTERNAL_PREFIX) - 1, symbol, symlen + 1);
480 symbol = tmp;
481 }
482 if (handle == NULL) {
483# if defined(USE_DLN_DLOPEN)
484 handle = dlopen(NULL, RTLD_LAZY | RTLD_GLOBAL);
485# elif defined(_WIN32)
486 handle = rb_libruby_handle();
487# else
488 return NULL;
489# endif
490 }
491 return dln_sym(handle, symbol);
492#else
493 return NULL;
494#endif
495}
496
497
498#if defined(RUBY_DLN_CHECK_ABI) && defined(USE_DLN_DLOPEN)
499static bool
500abi_check_enabled_p(void)
501{
502 const char *val = getenv("RUBY_ABI_CHECK");
503 return val == NULL || !(val[0] == '0' && val[1] == '\0');
504}
505#endif
506
507static void *
508dln_load_and_init(const char *file, const char *init_fct_name)
509{
510#if defined(DLN_DEFINED)
511 void *handle = dln_open(file);
512
513#ifdef RUBY_DLN_CHECK_ABI
514 typedef unsigned long long abi_version_number;
515 abi_version_number binary_abi_version =
516 dln_sym_callable(abi_version_number, (void), handle, EXTERNAL_PREFIX "ruby_abi_version")();
517 if (binary_abi_version != RUBY_ABI_VERSION && abi_check_enabled_p()) {
518 dln_loaderror("incompatible ABI version of binary - %s", file);
519 }
520#endif
521
522 /* Call the init code */
523 dln_sym_callable(void, (void), handle, init_fct_name)();
524
525 return handle;
526
527#elif defined(_AIX)
528# define DLN_DEFINED
529 {
530 void (*init_fct)(void);
531
532 /* TODO: check - AIX's load system call will return the first/last symbol/function? */
533 init_fct = (void(*)(void))load((char*)file, 1, 0);
534 if (init_fct == NULL) {
535 aix_loaderror(file);
536 }
537 if (loadbind(0, (void*)dln_load, (void*)init_fct) == -1) {
538 aix_loaderror(file);
539 }
540 (*init_fct)();
541 return (void*)init_fct;
542 }
543#else
544 dln_notimplement();
545#endif
546
547 return 0; /* dummy return */
548}
549
550void *
551dln_load(const char *file)
552{
553#if defined(_WIN32) || defined(USE_DLN_DLOPEN)
554 char *init_fct_name;
555 init_funcname(&init_fct_name, file);
556 return dln_load_and_init(file, init_fct_name);
557#else
558 dln_notimplement();
559 return 0;
560#endif
561}
562
563void *
564dln_load_feature(const char *file, const char *fname)
565{
566#if defined(DLN_DEFINED)
567 char *init_fct_name;
568 init_funcname(&init_fct_name, fname);
569 return dln_load_and_init(file, init_fct_name);
570#else
571 dln_notimplement();
572 return 0;
573#endif
574}
xrealloc
#define xrealloc
Old name of ruby_xrealloc.
Definition xmalloc.h:56
xmalloc
#define xmalloc
Old name of ruby_xmalloc.
Definition xmalloc.h:53
xcalloc
#define xcalloc
Old name of ruby_xcalloc.
Definition xmalloc.h:55
len
int len
Length of the buffer.
Definition io.h:8
ALLOCA_N
#define ALLOCA_N(type, n)
Definition memory.h:292
ruby.h
errno
#define errno
Ractor-aware version of errno.
Definition ruby.h:388
stdbool.h
C99 shim for <stdbool.h>.
string_part
Definition process.c:2445