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