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