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