4 * Core implementation for (approximately) POSIX conforming dlopen(),
5 * dlsym(), dlclose(), and dlerror() API functions.
9 * Written by Keith Marshall <keith@users.osdn.me>
10 * Copyright (C) 2014, 2021, 2022, MinGW.OSDN Project
13 * Permission is hereby granted, free of charge, to any person obtaining a
14 * copy of this software and associated documentation files (the "Software"),
15 * to deal in the Software without restriction, including without limitation
16 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 * and/or sell copies of the Software, and to permit persons to whom the
18 * Software is furnished to do so, subject to the following conditions:
20 * The above copyright notice, this permission notice, and the following
21 * disclaimer shall be included in all copies or substantial portions of
24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
25 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
27 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
29 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OF OR OTHER
30 * DEALINGS IN THE SOFTWARE.
33 * This source file implements the core functionality for each of the POSIX
34 * dynamic loader API functions, dlopen(), dlsym(), dlclose(), and dlerror();
35 * each is implemented such that it is conveniently accessed via a macro, or
36 * an inline function representation, by way of a publicly visible table of
37 * entry point vectors (i.e. function pointers).
47 /* In addition to normal C runtime services, this implementation requires
48 * the use of the MS-Windows API; the light-weight subset will suffice...
50 #define WIN32_LEAN_AND_MEAN
52 * ...while accessing it throught its standard interface.
56 /* In addition to the POSIX constants which are defined in dlfcn.h,
57 * we also define some private manifest constants, which POSIX does
58 * not specify, but which facilitate our implementation:
60 * RTLD_EXPLICIT, used to qualify RTLD_GLOBAL modules which have
61 * been explicitly loaded by dlopen(), so an RTLD_DEFAULT search
62 * need not consider them.
64 # define RTLD_EXPLICIT 16
66 * RTLD_ALL_GLOBAL, returned by dlopen() when the module name is
67 * given as a NULL pointer; this is a fake module handle, similar
68 * to RTLD_DEFAULT and RTLD_NEXT, representing the corpus of all
69 * RTLD_GLOBAL modules, both implicitly and explicitly loaded.
71 # define RTLD_ALL_GLOBAL (void *)(-5)
73 /* Before anything else, ensure that the dlerror() implementation
74 * is in place, so that other components may access it freely.
76 * We provide TWO reference pointers for error message buffering, so
77 * that we may continue to hold a reference to allocated memory, even
78 * after dlerror() has discarded the pointer to a pending message;
79 * initially, we mark both as unassigned.
81 static char *dlfcn_error_pending = NULL;
82 static char *dlfcn_error_message = NULL;
84 static void dlfcn_store_error_message( const char *fmt, ... )
86 /* This private function provides printf() style formatting for
87 * dlfcn error messages, storing them into dynamically allocated
88 * memory, and updating both reference pointers for subsequent
89 * retrieval by the dlerror() accessor function.
93 va_start( argv, fmt );
94 msglen = 1 + __mingw_vsnprintf( NULL, 0, fmt, argv );
95 if( (dlfcn_error_pending = realloc( dlfcn_error_message, msglen )) != NULL )
97 * Store message, only if a buffer was successfully allocated.
99 __mingw_vsnprintf( dlfcn_error_pending, msglen, fmt, argv );
100 dlfcn_error_message = dlfcn_error_pending;
104 static char *dlfcn_strerror( int errcode )
106 /* This private function emulates strerror(), but substitutes an
107 * errcode obtained from GetLastError() for errno.
110 uint32_t description = FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER
111 | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
112 errcode, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (char *)(&text),
117 /* We successfully obtained an error description; POSIX insists
118 * that it must not be encoded with any terminating newline.
120 char *tmp = text + strlen( text );
121 while( (*--tmp == '\n') || (*tmp == '\r') ) *tmp = '\0';
123 /* Ensure that we consistently return the error description in
124 * a buffer which may be released by the free() function, (since
125 * we cannot be sure that LocalFree() is compatible).
127 text = strdup( tmp = text );
131 { /* We were unable to find an error description; substitute a
132 * formatted reference to the unknown error code.
134 char *fmt = "Unknown error %d";
135 char tmp[1 + __mingw_snprintf( NULL, 0, fmt, errcode )];
136 __mingw_snprintf( tmp, sizeof( tmp ), fmt, errcode );
137 text = strdup( tmp );
139 /* However we derived it, the error description is now available
140 * in a dynamically allocated text buffer which may be released
146 static char *dlerror_internal( void )
148 /* This is the internal implementation of the public dlerror() API.
149 * POSIX does not require this to be thread safe, so we take no care
152 if( (dlfcn_error_pending == NULL) && (dlfcn_error_message != NULL) )
154 /* There is no pending message, but a buffer remains allocated
155 * to one which has already been retrieved; we may release it.
157 free( dlfcn_error_message );
158 dlfcn_error_message = NULL;
160 /* Mark any pending error message as "retrieved"...
162 dlfcn_error_pending = NULL;
166 return dlfcn_error_message;
170 /* A pointer type, representing a reference to a function for
171 * retrieving exported symbol references from any loaded module.
173 int WINAPI (*lookup_fn)( HANDLE, HMODULE *, uint32_t, uint32_t * );
175 /* POSIX requires that dlsym() be able to look up symbol references
176 * for any dynamically loaded module within the address space of the
177 * calling process, including those which have been implicitly loaded
178 * at process start-up. To facilitate this, we will use Microsoft's
179 * PSAPI.DLL services, to enumerate such implicitly loaded modules,
180 * before we explicitly load any using dlopen(), or we perform any
181 * look-up using dlsym() itself.
184 { /* Structure providing a convenient encapsulation of the PSAPI.DLL
185 * interface, which may itself need to be explicitly loaded.
188 const char *dll_name;
189 const char *lookup_fn_name;
192 psapi = { NULL, "psapi.dll", "EnumProcessModules", NULL };
194 /* The two NULL fields, within the preceding structure, must be
195 * initialized before use; the following pair of inline functions
196 * facilitate this, with the first looking up the API entry...
198 static __inline__ lookup_fn psapi_lookup_fn( HMODULE provider )
199 { return (lookup_fn)(GetProcAddress( provider, psapi.lookup_fn_name )); }
201 /* ...when called from within the second, which both ensures that
202 * PSAPI.DLL is loaded, and assigns the requisite field values.
204 static __inline__ lookup_fn psapi_lookup_fn_init( void )
205 { return psapi.lookup = ((psapi.dll = LoadLibrary( psapi.dll_name )) != NULL)
206 ? psapi_lookup_fn( psapi.dll ) : NULL;
209 /* After the PSAPI.DLL interface has been initialized, by calling
210 * the preceding function, we may call the following inline helper
211 * to retrieve a list of module handles...
213 static __inline__ unsigned int psapi_enum_modules( HMODULE **modules )
215 /* ...focussing on modules which are currently loaded within
216 * the address space of the current process.
218 HANDLE me = GetCurrentProcess();
220 /* We will dynamically allocate memory to store the handles;
221 * initially we request zero bytes of storage to receive them,
222 * then we use a while loop, which should execute exactly twice,
223 * to retrieve them; on the first pass, we determine the actual
224 * memory requirement and allocate it; on the second, we store
225 * the list of module handles into it...
227 uint32_t wanted, request = 0;
228 while( psapi.lookup( me, *modules, request, &wanted ) && (wanted > request) )
229 if( (*modules = realloc( *modules, request = wanted )) == NULL )
231 /* ...trying to record an appropriate diagnostic message on
232 * failure, (but noting that failure is likely to result from
233 * insufficient memory, so there may not be enough to record
234 * the message either)...
236 char *reason = dlfcn_strerror( ERROR_OUTOFMEMORY );
237 dlfcn_store_error_message( "dlfcn_init:enum_modules: %s", reason );
240 /* ...before ultimately returning the number retrieved.
242 return wanted / sizeof( HMODULE );
247 /* Structure used to map module handle references, and associated
248 * status, into the global modules list.
251 unsigned char *flags;
255 /* The global modules list itself, initially empty, but suitable
256 * for reallocation on the heap.
258 static dltab rtld = { 0, NULL, NULL };
260 /* Microsoft's LoadLibrary() API is explicitly documented as being
261 * unable to handle regular slashes as directory separators in module
262 * path names, (in spite of their validity elsewhere), thus...
264 static __inline__ wchar_t *normalized_form( wchar_t *pathname )
266 /* ...we provide this helper, to replace them with backslashes.
268 wchar_t *scan = pathname;
269 do { if( *scan == L'/' ) *scan = L'\\'; } while( *scan++ != L'\0' );
273 static void dlopen_store_error_message( const char *name, unsigned status )
275 /* A convenience helper, to record diagnostic messages to explain
276 * causes of failure encountered when calling dlopen().
278 char *reason = dlfcn_strerror( status );
279 dlfcn_store_error_message( "dlopen:'%s': %s", name, reason );
283 static void *dlopen_internal( const char *name, int mode )
285 /* This is the formal implementation of the public dlopen() function;
286 * note that this (currently) ignores the RTLD_LAZY loading option, and
287 * processes each request as if specified as RTLD_NOW.
290 /* POSIX specifies this as a special case, requiring us to return
291 * a handle, through which all symbols exported by all modules which
292 * are currently loaded with the RTLD_GLOBAL attribute, (explicitly
293 * or implicitly), would be accessible.
295 return RTLD_ALL_GLOBAL;
297 /* We will use LoadLibrary() to obtain a handle for the dlopen()ed
298 * module; Microsoft advise us that we should ensure that we always
299 * use backslashes, and to avoid use of POSIX compatible slashes, in
300 * any path name passed to this function. To ensure this, the name
301 * argument should be normalized, (working in the UTF-16LE domain,
302 * to avoid inadvertent transformation of trail bytes within MBC
303 * sequences), to transform slashes accordingly.
305 size_t buflen; wchar_t internal_name[buflen = 1 + strlen( name )];
306 MultiByteToWideChar( CP_ACP, 0, name, -1, internal_name, buflen );
308 /* Now, we may safely call LoadLibrary(), to obtain a module handle.
310 void *module = LoadLibraryW( normalized_form( internal_name ) );
313 /* The named module could not be opened; record an appropriate
314 * error message for retrieval by dlerror().
316 dlopen_store_error_message( name, GetLastError() );
319 { /* We got a handle for the requested module; we need to ensure
320 * that it is allocated a slot in our global symbol table, but we
321 * must first check that it isn't already present.
323 int index, insertion_point = rtld.slots;
324 for( index = 0; index < rtld.slots; index++ )
326 /* As we scan the list of already loaded modules, check for any
327 * existing slot which may be vacant...
329 if( rtld.flags[index] == 0 )
331 /* ...marking the first available, if any, as a candidate for
332 * possible insertion of a new entry.
334 if( index < insertion_point )
335 insertion_point = index;
337 else if( module == rtld.modules[index] )
339 /* The requested module appears to be loaded already; calling
340 * LoadLibrary() will have increased its reference count, but
341 * our management strategy doesn't require this; reduce it...
343 FreeLibrary( module );
345 * ...but promote its existing status to RTLD_GLOBAL, if that
346 * is indicated as required by the requested mode...
348 rtld.flags[index] |= mode & RTLD_GLOBAL;
350 * ...and immediately return the module handle.
355 /* If we get to here, there is no entry for the requested module,
356 * within the global modules list; we must add it now, either using
357 * an existing vacant slot, if there is one...
359 if( insertion_point < rtld.slots )
361 /* ...but noting that we cannot simply insert a new reference
362 * within it, since that would disrupt the order in which modules
363 * are subsequently searched, and POSIX requires that this is to
364 * preserve loading order, (strictly, symbol resolution order),
365 * when searching RTLD_GLOBAL modules, (and since any module may
366 * be promoted to RTLD_GLOBAL status, even after it was originally
367 * loaded as RTLD_LOCAL, this means that we must preserve loading
368 * order for ALL active modules). Thus, we must pack the list of
369 * active modules after the nominated insertion point...
371 index = insertion_point;
373 /* ...after which, the first vacant slot will have been relocated
374 * to follow all active slots, and we may adjust the nominated
375 * insertion point accordingly.
377 do { /* First, we identify the first slot following the nominated
378 * insertion point, which is NOT vacant.
380 while( (index < rtld.slots) && (rtld.flags[index] == 0) )
383 /* Now, we move that non-vacant slot, and any which follow
384 * it, upwards in the list, to fill vacant slots...
386 while( (index < rtld.slots) && (rtld.flags[index] != 0) )
388 /* ...by simply copying content from the non-vacant slots
389 * to overwrite content in the preceding slots...
391 rtld.modules[insertion_point] = rtld.modules[index];
392 rtld.flags[insertion_point++] = rtld.flags[index++];
394 /* ...repeating the entire procedure, until all vacant slots
395 * have been filled, and the nominated insertion point has
396 * been moved to follow the last relocated non-vacant entry.
398 } while( index < rtld.slots );
400 /* After packing, any already allocated slots after and including
401 * the relocated insertion point MUST be vacant; ensure that they
402 * are marked accordingly.
404 for( index = insertion_point; index < rtld.slots; index++ )
405 rtld.flags[index] = 0;
408 { /* There is no vacant slot: we must expand the allocated memory
409 * pool to create one; first increment the modules list size...
411 size_t slots = 1 + rtld.slots;
412 HMODULE *modules = rtld.modules;
413 if( (modules = realloc( modules, sizeof( HMODULE ) * slots )) != NULL )
415 /* ...and, having sucessfully increased the modules list memory
416 * allocation, do likewise for the associated flags...
418 unsigned char *flags = rtld.flags;
419 if( (flags = realloc( flags, slots )) != NULL )
421 * ...initializing the new flags register, and incrementing
422 * the slots count, when that is also successful.
424 (rtld.flags = flags)[rtld.slots++] = 0;
426 /* Regardless of the success, or otherwise, of the flags memory
427 * adjustment, the modules list was reallocated, so we need to
428 * adjust its reference pointer accordingly.
430 rtld.modules = modules;
432 /* Before proceeding further, verify that the new slot has been
433 * fully created, and is ready to store the module data...
435 if( insertion_point == rtld.slots )
437 /* ...but if allocation failed, the nominated insertion point
438 * will lie beyond the available space, so we MUST fail, after
439 * discarding the now unreferenced module handle, and trying to
440 * record an appropriate diagnostic message, (but we note that
441 * this may also fail, due to insufficient memory).
443 FreeLibrary( module );
444 dlopen_store_error_message( name, ERROR_OUTOFMEMORY );
448 /* When we get to here, we now have a suitable slot in which to add
449 * the reference data for the newly dlopen()ed module, (either by
450 * allocation of a new slot, or by relocation of an existing vacant
451 * slot); we may safely store the appropriate reference data.
453 rtld.flags[insertion_point] = RTLD_EXPLICIT | mode;
454 rtld.modules[insertion_point] = module;
456 /* Finally, we return whatever module handle we got from LoadLibrary(),
457 * (which may be NULL, if this failed).
462 static void *dlsym_internal( void *module, const char *name )
464 /* This is the formal implementation of the public dlsym() function.
466 static unsigned int index = 0;
467 static const char *last_named_symbol = NULL;
468 static unsigned char rtld_exclude = 0;
471 if( module == RTLD_NEXT )
473 /* NOTE: We MUST identify this special case BEFORE any other!
475 * POSIX doesn't require this to be supported yet, but reserves it
476 * for future use; it should cause dlsym() to repeat its search for
477 * the named symbol, (provided it remains the same as last named in
478 * a global search), continuing from the next module in the current
479 * search order, following that in which the symbol was previously
480 * found; (this permits us to locate symbols which may defined in
481 * more than one loaded module).
483 index = (name == last_named_symbol) ? index + 1 : 0;
484 module = RTLD_ALL_GLOBAL;
486 else if( module == RTLD_DEFAULT )
488 /* NOTE: We MUST keep this AFTER the check for RTLD_NEXT!
490 * Once again, POSIX doesn't require this to be supported yet, but
491 * reserves it for future use to search for symbols which could have
492 * been found within the process address space prior to any explicit
493 * dlopen() call; this capability may be supported by searching all
494 * modules in the address space, (i.e. equivalent to a search with
495 * module == RTLD_ALL_GLOBAL), excluding those which have been
496 * explicitly loaded since process start-up.
498 module = RTLD_ALL_GLOBAL;
499 rtld_exclude = RTLD_EXPLICIT;
503 /* Neither RTLD_DEFAULT, nor RTLD_NEXT was specified; we must reset
504 * the RTLD_GLOBAL search index, and cancel all search exclusions.
506 index = rtld_exclude = 0;
508 if( module == RTLD_ALL_GLOBAL )
510 /* The RTLD_ALL_GLOBAL module reference represents a request to
511 * perform an in-order traversal of all modules within the process
512 * address space, either implicitly loaded, or explicitly loaded
513 * with the RTLD_GLOBAL mode attribute, either until the named
514 * symbol is found, or all such modules have been searched
515 * without finding it.
517 for( rtn = NULL; (rtn == NULL) && (index < rtld.slots); index++ )
518 if( ((rtld_exclude & rtld.flags[index]) == 0)
519 && ((rtld.flags[index] & RTLD_GLOBAL) == RTLD_GLOBAL) )
520 rtn = GetProcAddress( rtld.modules[index], name );
522 /* Note the symbol named in the current search, so that we may
523 * check for consistency in the event that the next search is
524 * invoked as an RTLD_NEXT request.
526 last_named_symbol = name;
529 { /* When a specific module reference is specified, confine the
530 * search to the specified module alone...
532 rtn = GetProcAddress( (HMODULE)(module), name );
534 /* ...and inhibit any attempt to follow this search with one
535 * specifying RTLD_NEXT; (this would not be valid, since there
536 * is no concept of a "next" module to be searched, when not
537 * searching through an ordered list of modules).
539 last_named_symbol = NULL;
544 /* The named symbol was not found in any module which was searched;
545 * record the appropriate error message for retrieval by dlerror().
547 char *reason = dlfcn_strerror( GetLastError() );
548 dlfcn_store_error_message( "dlsym:'%s': %s", name, reason );
552 /* Return the symbol address, as assigned to the return value;
553 * (this will be NULL, if the named symbol was not found).
558 static int dlclose_store_error_message( int status )
560 /* A private helper function to record an appropriate dlerror()
561 * message, on failure of dlclose().
563 char *reason = dlfcn_strerror( status = GetLastError() );
564 dlfcn_store_error_message( "dlclose: %s", reason );
569 static __inline__ int dlclose_internal_check_for_error( int status )
571 /* A private helper function to set the return status for dlclose(),
572 * while also recording a dlerror() message, when status is "failed".
574 return (status == 0) ? dlclose_store_error_message( status ) : 0;
577 static int dlclose_internal( void *module )
579 /* This is the formal implementation of the public dlclose() function;
580 * it will call Microsoft's FreeLibrary() function passing the specified
581 * module handle, provided this is listed in the global module table as
582 * having been explicitly opened by our dlopen() function.
585 for( index = 0; index < rtld.slots; index++ )
586 if( module == rtld.modules[index] )
588 /* The specified module handle is present in the global modules list;
589 * while we could simply call FreeLibrary() immediately, it may not be
590 * prudent to do so in respect of implicitly loaded modules, but for
591 * those which we have explicitly loaded...
593 if( ((rtld.flags[index] & RTLD_EXPLICIT) == RTLD_EXPLICIT)
595 * ...and which can be successfully released by FreeLibrary()...
597 && (dlclose_internal_check_for_error( FreeLibrary( module )) == 0) )
599 * ...we mark them as no longer available for dlsym() processing,
600 * and return immediately, indicating success...
602 return rtld.flags[index] = 0;
604 /* ...but when we didn't successfully release the module, we have
605 * no need to continue the search for its handle in the global list
606 * of modules, (because we've already found it); we may immediately
607 * abandon the search.
612 /* If we get to here, we either didn't find the specified module handle in
613 * the global list of modules, or we found it but were unable to release
614 * it; in either case, we force a module error condition.
616 return dlclose_store_error_message( FreeLibrary( NULL ) );
619 static void dlfcn_init( void )
621 /* This private initialization function must be called, as a
622 * prerequisite to the first use of either dlopen() or dlsym()
623 * in any process; it uses Microsoft's PSAPI.DLL interface to
624 * enumerate the implicitly loaded process modules, so that
625 * they may be searched implicitly by dlsym().
627 if( psapi_lookup_fn_init() == NULL )
629 /* Initialization of the PSAPI.DLL interface failed. Record
630 * this for possible retrieval by dlerror(); we can do no more,
631 * so the list of implicitly loaded mudules will remain empty.
633 char *reason = dlfcn_strerror( GetLastError() );
634 dlfcn_store_error_message( "dlfcn_init:%s: %s", psapi.dll_name, reason );
638 { /* We've acquired a reference for the PSAPI EnumProcessModules()
639 * service; invoke it, to obtain a list of all modules which are
640 * currently accessible, within the address space of the current
641 * process, (which we assume to represent all modules which were
642 * loaded implicitly, at process start-up)...
644 rtld.slots = psapi_enum_modules( &(rtld.modules) );
646 * ...but, noting that we explicitly added a refererence for the
647 * PSAPI.DLL services interface, which we needed to acquire this
648 * list, we may now discard that reference.
650 FreeLibrary( psapi.dll );
652 /* Provided we've obtained a non-empty list of modules...
654 if( (rtld.modules != NULL)
656 * ...and we can successfully allocate storage for an associated
657 * collection of flags registers, (one per module)...
659 && ((rtld.flags = realloc( rtld.flags, rtld.slots )) != NULL) )
661 /* ...examine each reference in the modules list, and for each
662 * which is not NULL...
665 for( index = 0; index < rtld.slots; index++ )
666 if( (rtld.modules[index] != NULL)
668 * ...and which is not the PSAPI.DLL reference we've already
671 && ((rtld.modules[index] != psapi.dll)
673 * ...unless it continues to provide a valid reference to the
674 * PSAPI EnumProcessModules() service, (which indicates that
675 * the associated DLL had been implicitly loaded, BEFORE we
676 * explicitly requested it)...
678 || (psapi_lookup_fn( psapi.dll ) == psapi.lookup)) )
680 * ...then we set the associated flags to indicate that the
681 * module was implicitly loaded and globally accessible...
683 rtld.flags[index] = RTLD_GLOBAL;
685 /* ...otherwise, we mark it as if dlclose() has already been
686 * implicitly called, to discard it.
688 rtld.flags[index] = 0;
690 /* Having thus populated the global modules list, on first call
691 * to either dlopen() or dlsym(), we don't need to (and indeed we
692 * shouldn't) do this again; redirect each of these two calls to
693 * their respective internal handlers.
695 __mingw_dlfcn.dlopen = dlopen_internal;
696 __mingw_dlfcn.dlsym = dlsym_internal;
701 /* The global symbol table needs to be initialized, before we process
702 * the first call to either dlopen() or dlsym(); the following pair of
703 * initializer functions take care of this requirement, before passing
704 * the first-time request to the appropriate internal handler.
706 static void *dlopen_init( const char *name, int mode )
707 { dlfcn_init(); return dlopen_internal( name, mode ); }
709 static void *dlsym_init( void *module, const char *name )
710 { dlfcn_init(); return dlsym_internal( module, name ); }
712 /* Finally, we may define the __mingw_dlfcn structure, and set up its
713 * initial function pointers, referring to the four API functions...
715 struct __dlfcn__ __mingw_dlfcn =
716 { dlopen_init, /* dlopen() implementation, with initialization. */
717 dlsym_init, /* dlsym() implementation, with initialization. */
718 dlerror_internal, /* dlerror() implementation; (direct reference). */
719 dlclose_internal /* dlclose() implementation; (direct reference). */
722 /* $RCSfile$: end of file */