/src/glib/gmodule/gmodule-dl.c
Line | Count | Source |
1 | | /* GMODULE - GLIB wrapper code for dynamic module loading |
2 | | * Copyright (C) 1998, 2000 Tim Janik |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | * |
6 | | * This library is free software; you can redistribute it and/or |
7 | | * modify it under the terms of the GNU Lesser General Public |
8 | | * License as published by the Free Software Foundation; either |
9 | | * version 2.1 of the License, or (at your option) any later version. |
10 | | * |
11 | | * This library is distributed in the hope that it will be useful, |
12 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | | * Lesser General Public License for more details. |
15 | | * |
16 | | * You should have received a copy of the GNU Lesser General Public |
17 | | * License along with this library; if not, see <http://www.gnu.org/licenses/>. |
18 | | */ |
19 | | |
20 | | /* |
21 | | * Modified by the GLib Team and others 1997-2000. See the AUTHORS |
22 | | * file for a list of people on the GLib Team. See the ChangeLog |
23 | | * files for a list of changes. These files are distributed with |
24 | | * GLib at ftp://ftp.gtk.org/pub/gtk/. |
25 | | */ |
26 | | |
27 | | /* |
28 | | * MT safe |
29 | | */ |
30 | | #include "config.h" |
31 | | |
32 | | #include <dlfcn.h> |
33 | | #include <glib.h> |
34 | | |
35 | | #ifdef __CYGWIN__ |
36 | | #define CYGWIN_WORKAROUND |
37 | | #endif |
38 | | |
39 | | #ifdef CYGWIN_WORKAROUND |
40 | | #include <sys/cygwin.h> |
41 | | #include <windows.h> |
42 | | #include <tlhelp32.h> |
43 | | |
44 | | #include <errno.h> |
45 | | #endif |
46 | | |
47 | | /* Perl includes <nlist.h> and <link.h> instead of <dlfcn.h> on some systems? */ |
48 | | |
49 | | |
50 | | /* dlerror() is not implemented on all systems |
51 | | */ |
52 | | #ifndef G_MODULE_HAVE_DLERROR |
53 | | # ifdef __NetBSD__ |
54 | | # define dlerror() g_strerror (errno) |
55 | | # else /* !__NetBSD__ */ |
56 | | /* could we rely on errno's state here? */ |
57 | | # define dlerror() "unknown dl-error" |
58 | | # endif /* !__NetBSD__ */ |
59 | | #endif /* G_MODULE_HAVE_DLERROR */ |
60 | | |
61 | | /* some flags are missing on some systems, so we provide |
62 | | * harmless defaults. |
63 | | * The Perl sources say, RTLD_LAZY needs to be defined as (1), |
64 | | * at least for Solaris 1. |
65 | | * |
66 | | * Mandatory: |
67 | | * RTLD_LAZY - resolve undefined symbols as code from the dynamic library |
68 | | * is executed. |
69 | | * RTLD_NOW - resolve all undefined symbols before dlopen returns, and fail |
70 | | * if this cannot be done. |
71 | | * Optionally: |
72 | | * RTLD_GLOBAL - the external symbols defined in the library will be made |
73 | | * available to subsequently loaded libraries. |
74 | | */ |
75 | | #ifndef HAVE_RTLD_LAZY |
76 | | #define RTLD_LAZY 1 |
77 | | #endif /* RTLD_LAZY */ |
78 | | #ifndef HAVE_RTLD_NOW |
79 | | #define RTLD_NOW 0 |
80 | | #endif /* RTLD_NOW */ |
81 | | /* some systems (OSF1 V5.0) have broken RTLD_GLOBAL linkage */ |
82 | | #ifdef G_MODULE_BROKEN_RTLD_GLOBAL |
83 | | #undef RTLD_GLOBAL |
84 | | #undef HAVE_RTLD_GLOBAL |
85 | | #endif /* G_MODULE_BROKEN_RTLD_GLOBAL */ |
86 | | #ifndef HAVE_RTLD_GLOBAL |
87 | | #define RTLD_GLOBAL 0 |
88 | | #endif /* RTLD_GLOBAL */ |
89 | | |
90 | | |
91 | | /* According to POSIX.1-2001, dlerror() is not necessarily thread-safe |
92 | | * (see https://pubs.opengroup.org/onlinepubs/009695399/), and so must be |
93 | | * called within the same locked section as the dlopen()/dlsym() call which |
94 | | * may have caused an error. |
95 | | * |
96 | | * However, some libc implementations, such as glibc, implement dlerror() using |
97 | | * thread-local storage, so are thread-safe. As of early 2021: |
98 | | * - glibc is thread-safe: https://github.com/bminor/glibc/blob/HEAD/dlfcn/libc_dlerror_result.c |
99 | | * - musl-libc is thread-safe since version 1.1.9 -- released in 2015: https://wiki.musl-libc.org/functional-differences-from-glibc.html#Thread-safety-of-%3Ccode%3Edlerror%3C/code%3E |
100 | | * - uclibc-ng is not thread-safe: https://cgit.uclibc-ng.org/cgi/cgit/uclibc-ng.git/tree/ldso/libdl/libdl.c?id=132decd2a043d0ccf799f42bf89f3ae0c11e95d5#n1075 |
101 | | * - Other libc implementations have not been checked, and no problems have |
102 | | * been reported with them in 10 years, so default to assuming that they |
103 | | * don’t need additional thread-safety from GLib |
104 | | */ |
105 | | #if defined(__UCLIBC__) |
106 | | G_LOCK_DEFINE_STATIC (errors); |
107 | | #else |
108 | | #define DLERROR_IS_THREADSAFE 1 |
109 | | #endif |
110 | | |
111 | | static void |
112 | | lock_dlerror (void) |
113 | 0 | { |
114 | | #ifndef DLERROR_IS_THREADSAFE |
115 | | G_LOCK (errors); |
116 | | #endif |
117 | 0 | } |
118 | | |
119 | | static void |
120 | | unlock_dlerror (void) |
121 | 0 | { |
122 | | #ifndef DLERROR_IS_THREADSAFE |
123 | | G_UNLOCK (errors); |
124 | | #endif |
125 | 0 | } |
126 | | |
127 | | /* This should be called with lock_dlerror() held */ |
128 | | static const gchar * |
129 | | fetch_dlerror (gboolean replace_null) |
130 | 0 | { |
131 | 0 | const gchar *msg = dlerror (); |
132 | | |
133 | | /* make sure we always return an error message != NULL, if |
134 | | * expected to do so. */ |
135 | |
|
136 | 0 | if (!msg && replace_null) |
137 | 0 | return "unknown dl-error"; |
138 | | |
139 | 0 | return msg; |
140 | 0 | } |
141 | | |
142 | | static gpointer |
143 | | _g_module_open (const gchar *file_name, |
144 | | gboolean bind_lazy, |
145 | | gboolean bind_local, |
146 | | GError **error) |
147 | 0 | { |
148 | 0 | gpointer handle; |
149 | | |
150 | 0 | lock_dlerror (); |
151 | 0 | handle = dlopen (file_name, |
152 | 0 | (bind_local ? RTLD_LOCAL : RTLD_GLOBAL) | (bind_lazy ? RTLD_LAZY : RTLD_NOW)); |
153 | 0 | if (!handle) |
154 | 0 | { |
155 | 0 | const gchar *message = fetch_dlerror (TRUE); |
156 | |
|
157 | 0 | g_module_set_error (message); |
158 | 0 | g_set_error_literal (error, G_MODULE_ERROR, G_MODULE_ERROR_FAILED, message); |
159 | 0 | } |
160 | |
|
161 | 0 | unlock_dlerror (); |
162 | | |
163 | 0 | return handle; |
164 | 0 | } |
165 | | |
166 | | static gpointer |
167 | | _g_module_self (void) |
168 | 0 | { |
169 | 0 | gpointer handle; |
170 | | |
171 | | /* to query symbols from the program itself, special link options |
172 | | * are required on some systems. |
173 | | */ |
174 | | |
175 | | /* On Android 32 bit (i.e. not __LP64__), dlopen(NULL) |
176 | | * does not work reliable and generally no symbols are found |
177 | | * at all. RTLD_DEFAULT works though. |
178 | | * On Android 64 bit, dlopen(NULL) seems to work but dlsym(handle) |
179 | | * always returns 'undefined symbol'. Only if RTLD_DEFAULT or |
180 | | * NULL is given, dlsym returns an appropriate pointer. |
181 | | */ |
182 | 0 | lock_dlerror (); |
183 | | #if defined(__ANDROID__) || defined(__NetBSD__) |
184 | | handle = RTLD_DEFAULT; |
185 | | #else |
186 | 0 | handle = dlopen (NULL, RTLD_GLOBAL | RTLD_LAZY); |
187 | 0 | #endif |
188 | 0 | if (!handle) |
189 | 0 | g_module_set_error (fetch_dlerror (TRUE)); |
190 | 0 | unlock_dlerror (); |
191 | | |
192 | 0 | return handle; |
193 | 0 | } |
194 | | |
195 | | static void |
196 | | _g_module_close (gpointer handle) |
197 | 0 | { |
198 | | #if defined(__ANDROID__) || defined(__NetBSD__) |
199 | | if (handle != RTLD_DEFAULT) |
200 | | #endif |
201 | 0 | { |
202 | 0 | lock_dlerror (); |
203 | 0 | if (dlclose (handle) != 0) |
204 | 0 | g_module_set_error (fetch_dlerror (TRUE)); |
205 | 0 | unlock_dlerror (); |
206 | 0 | } |
207 | 0 | } |
208 | | |
209 | | #ifdef CYGWIN_WORKAROUND |
210 | | |
211 | | static gpointer |
212 | | find_in_any_module_cygwin (const char *symbol_name) |
213 | | { |
214 | | HANDLE snapshot; |
215 | | MODULEENTRY32 entry = { |
216 | | .dwSize = sizeof (entry), |
217 | | }; |
218 | | gpointer p = NULL; |
219 | | |
220 | | retry: |
221 | | snapshot = CreateToolhelp32Snapshot (TH32CS_SNAPMODULE, 0); |
222 | | if (snapshot == INVALID_HANDLE_VALUE) |
223 | | { |
224 | | DWORD code = GetLastError (); |
225 | | if (code == ERROR_BAD_LENGTH) |
226 | | { |
227 | | /* Probably only happens when inspecting other processes */ |
228 | | g_thread_yield (); |
229 | | goto retry; |
230 | | } |
231 | | else |
232 | | { |
233 | | g_warning ("%s failed with error code %u", |
234 | | "CreateToolhelp32Snapshot", |
235 | | (unsigned int) code); |
236 | | return NULL; |
237 | | } |
238 | | } |
239 | | |
240 | | if (Module32First (snapshot, &entry)) |
241 | | { |
242 | | do |
243 | | { |
244 | | if ((p = GetProcAddress (entry.hModule, symbol_name)) != NULL) |
245 | | { |
246 | | ssize_t size = 0; |
247 | | char *posix_path; |
248 | | |
249 | | G_STATIC_ASSERT (G_N_ELEMENTS (entry.szExePath) <= MAX_PATH); |
250 | | |
251 | | do |
252 | | { |
253 | | posix_path = (size > 0) ? g_alloca ((long unsigned int) size) : NULL; |
254 | | size = cygwin_conv_path (CCP_WIN_W_TO_POSIX, entry.szExePath, posix_path, (size_t) size); |
255 | | } |
256 | | while (size > 0); |
257 | | |
258 | | if (size < 0) |
259 | | { |
260 | | g_warning ("%s failed with errno %d", "cygwin_conv_path", errno); |
261 | | break; |
262 | | } |
263 | | |
264 | | if (g_str_has_prefix (posix_path, "/usr/lib") || |
265 | | g_str_has_prefix (posix_path, "/usr/local/lib")) |
266 | | { |
267 | | p = NULL; |
268 | | } |
269 | | else |
270 | | break; |
271 | | } |
272 | | } |
273 | | while (Module32Next (snapshot, &entry)); |
274 | | } |
275 | | |
276 | | CloseHandle (snapshot); |
277 | | |
278 | | return p; |
279 | | } |
280 | | |
281 | | #endif |
282 | | |
283 | | static gpointer |
284 | | _g_module_symbol (gpointer handle, |
285 | | const gchar *symbol_name) |
286 | 0 | { |
287 | 0 | gpointer p; |
288 | 0 | const gchar *msg; |
289 | |
|
290 | 0 | lock_dlerror (); |
291 | 0 | fetch_dlerror (FALSE); |
292 | 0 | p = dlsym (handle, symbol_name); |
293 | 0 | msg = fetch_dlerror (FALSE); |
294 | 0 | #ifndef CYGWIN_WORKAROUND |
295 | 0 | if (msg) |
296 | 0 | g_module_set_error (msg); |
297 | 0 | unlock_dlerror (); |
298 | | #else |
299 | | if (msg) |
300 | | { |
301 | | char *msg_copy = g_strdup (msg); |
302 | | unlock_dlerror (); |
303 | | p = find_in_any_module_cygwin (symbol_name); |
304 | | if (!p) |
305 | | g_module_set_error_unduped (msg_copy); |
306 | | else |
307 | | g_free (msg_copy); |
308 | | } |
309 | | #endif |
310 | |
|
311 | 0 | return p; |
312 | 0 | } |