Ruby: dln.c Source File

Ruby 3.4.5p51 (2025-07-16 revision 20cda200d3ce092571d0b5d342dadca69636cb0f)

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