]>
Commit | Line | Data |
---|---|---|
3ef1f32e | 1 | // ELF-specific support for sections with shared libraries. |
2 | // Copyright (C) 2019 Free Software Foundation, Inc. | |
3 | ||
4 | // GCC is free software; you can redistribute it and/or modify it under | |
5 | // the terms of the GNU General Public License as published by the Free | |
6 | // Software Foundation; either version 3, or (at your option) any later | |
7 | // version. | |
8 | ||
9 | // GCC is distributed in the hope that it will be useful, but WITHOUT ANY | |
10 | // WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 | // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
12 | // for more details. | |
13 | ||
14 | // Under Section 7 of GPL version 3, you are granted additional | |
15 | // permissions described in the GCC Runtime Library Exception, version | |
16 | // 3.1, as published by the Free Software Foundation. | |
03385ed3 | 17 | |
3ef1f32e | 18 | // You should have received a copy of the GNU General Public License and |
19 | // a copy of the GCC Runtime Library Exception along with this program; | |
20 | // see the files COPYING3 and COPYING.RUNTIME respectively. If not, see | |
21 | // <http://www.gnu.org/licenses/>. | |
22 | ||
23 | module gcc.sections.elf_shared; | |
03385ed3 | 24 | |
25 | version (CRuntime_Glibc) enum SharedELF = true; | |
3ef1f32e | 26 | else version (CRuntime_Musl) enum SharedELF = true; |
03385ed3 | 27 | else version (FreeBSD) enum SharedELF = true; |
28 | else version (NetBSD) enum SharedELF = true; | |
3ef1f32e | 29 | else version (DragonFlyBSD) enum SharedELF = true; |
30 | else version (CRuntime_UClibc) enum SharedELF = true; | |
c3e1a755 | 31 | else version (Solaris) enum SharedELF = true; |
03385ed3 | 32 | else enum SharedELF = false; |
33 | static if (SharedELF): | |
34 | ||
35 | // debug = PRINTF; | |
36 | import core.memory; | |
22163f0d | 37 | import core.stdc.config; |
03385ed3 | 38 | import core.stdc.stdio; |
39 | import core.stdc.stdlib : calloc, exit, free, malloc, EXIT_FAILURE; | |
40 | import core.stdc.string : strlen; | |
41 | version (linux) | |
42 | { | |
43 | import core.sys.linux.dlfcn; | |
44 | import core.sys.linux.elf; | |
45 | import core.sys.linux.link; | |
46 | } | |
47 | else version (FreeBSD) | |
48 | { | |
49 | import core.sys.freebsd.dlfcn; | |
50 | import core.sys.freebsd.sys.elf; | |
51 | import core.sys.freebsd.sys.link_elf; | |
52 | } | |
53 | else version (NetBSD) | |
54 | { | |
55 | import core.sys.netbsd.dlfcn; | |
56 | import core.sys.netbsd.sys.elf; | |
57 | import core.sys.netbsd.sys.link_elf; | |
58 | } | |
3ef1f32e | 59 | else version (DragonFlyBSD) |
60 | { | |
61 | import core.sys.dragonflybsd.dlfcn; | |
62 | import core.sys.dragonflybsd.sys.elf; | |
63 | import core.sys.dragonflybsd.sys.link_elf; | |
64 | } | |
c3e1a755 | 65 | else version (Solaris) |
66 | { | |
67 | import core.sys.solaris.dlfcn; | |
68 | import core.sys.solaris.link; | |
69 | import core.sys.solaris.sys.elf; | |
70 | import core.sys.solaris.sys.link; | |
71 | } | |
03385ed3 | 72 | else |
73 | { | |
74 | static assert(0, "unimplemented"); | |
75 | } | |
76 | import core.sys.posix.pthread; | |
79c21307 | 77 | import gcc.config; |
03385ed3 | 78 | import rt.deh; |
79 | import rt.dmain2; | |
80 | import rt.minfo; | |
81 | import rt.util.container.array; | |
82 | import rt.util.container.hashtab; | |
83 | ||
3ef1f32e | 84 | /**** |
85 | * Asserts the specified condition, independent from -release, by abort()ing. | |
86 | * Regular assertions throw an AssertError and thus require an initialized | |
87 | * GC, which isn't the case (yet or anymore) for the startup/shutdown code in | |
88 | * this module (called by CRT ctors/dtors etc.). | |
89 | */ | |
90 | private void safeAssert(bool condition, scope string msg, size_t line = __LINE__) @nogc nothrow @safe | |
91 | { | |
92 | import core.internal.abort; | |
93 | condition || abort(msg, __FILE__, line); | |
94 | } | |
95 | ||
03385ed3 | 96 | alias DSO SectionGroup; |
97 | struct DSO | |
98 | { | |
99 | static int opApply(scope int delegate(ref DSO) dg) | |
100 | { | |
101 | foreach (dso; _loadedDSOs) | |
102 | { | |
103 | if (auto res = dg(*dso)) | |
104 | return res; | |
105 | } | |
106 | return 0; | |
107 | } | |
108 | ||
109 | static int opApplyReverse(scope int delegate(ref DSO) dg) | |
110 | { | |
111 | foreach_reverse (dso; _loadedDSOs) | |
112 | { | |
113 | if (auto res = dg(*dso)) | |
114 | return res; | |
115 | } | |
116 | return 0; | |
117 | } | |
118 | ||
119 | @property immutable(ModuleInfo*)[] modules() const nothrow @nogc | |
120 | { | |
121 | return _moduleGroup.modules; | |
122 | } | |
123 | ||
124 | @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc | |
125 | { | |
126 | return _moduleGroup; | |
127 | } | |
128 | ||
129 | @property immutable(FuncTable)[] ehTables() const nothrow @nogc | |
130 | { | |
131 | return null; | |
132 | } | |
133 | ||
134 | @property inout(void[])[] gcRanges() inout nothrow @nogc | |
135 | { | |
136 | return _gcRanges[]; | |
137 | } | |
138 | ||
139 | private: | |
140 | ||
141 | invariant() | |
142 | { | |
3ef1f32e | 143 | safeAssert(_moduleGroup.modules.length > 0, "No modules for DSO."); |
144 | safeAssert(_tlsMod || !_tlsSize, "Inconsistent TLS fields for DSO."); | |
03385ed3 | 145 | } |
146 | ||
147 | ModuleGroup _moduleGroup; | |
148 | Array!(void[]) _gcRanges; | |
149 | size_t _tlsMod; | |
150 | size_t _tlsSize; | |
151 | ||
152 | version (Shared) | |
153 | { | |
154 | Array!(void[]) _codeSegments; // array of code segments | |
155 | Array!(DSO*) _deps; // D libraries needed by this DSO | |
156 | void* _handle; // corresponding handle | |
157 | } | |
3ef1f32e | 158 | |
159 | // get the TLS range for the executing thread | |
160 | void[] tlsRange() const nothrow @nogc | |
161 | { | |
162 | return getTLSRange(_tlsMod, _tlsSize); | |
163 | } | |
03385ed3 | 164 | } |
165 | ||
166 | /**** | |
167 | * Boolean flag set to true while the runtime is initialized. | |
168 | */ | |
169 | __gshared bool _isRuntimeInitialized; | |
170 | ||
171 | ||
172 | version (FreeBSD) private __gshared void* dummy_ref; | |
3ef1f32e | 173 | version (DragonFlyBSD) private __gshared void* dummy_ref; |
03385ed3 | 174 | version (NetBSD) private __gshared void* dummy_ref; |
c3e1a755 | 175 | version (Solaris) private __gshared void* dummy_ref; |
03385ed3 | 176 | |
177 | /**** | |
178 | * Gets called on program startup just before GC is initialized. | |
179 | */ | |
180 | void initSections() nothrow @nogc | |
181 | { | |
182 | _isRuntimeInitialized = true; | |
183 | // reference symbol to support weak linkage | |
184 | version (FreeBSD) dummy_ref = &_d_dso_registry; | |
3ef1f32e | 185 | version (DragonFlyBSD) dummy_ref = &_d_dso_registry; |
03385ed3 | 186 | version (NetBSD) dummy_ref = &_d_dso_registry; |
c3e1a755 | 187 | version (Solaris) dummy_ref = &_d_dso_registry; |
03385ed3 | 188 | } |
189 | ||
190 | ||
191 | /*** | |
192 | * Gets called on program shutdown just after GC is terminated. | |
193 | */ | |
194 | void finiSections() nothrow @nogc | |
195 | { | |
196 | _isRuntimeInitialized = false; | |
197 | } | |
198 | ||
199 | alias ScanDG = void delegate(void* pbeg, void* pend) nothrow; | |
200 | ||
201 | version (Shared) | |
202 | { | |
203 | /*** | |
204 | * Called once per thread; returns array of thread local storage ranges | |
205 | */ | |
206 | Array!(ThreadDSO)* initTLSRanges() @nogc nothrow | |
207 | { | |
3ef1f32e | 208 | return &_loadedDSOs(); |
03385ed3 | 209 | } |
210 | ||
211 | void finiTLSRanges(Array!(ThreadDSO)* tdsos) @nogc nothrow | |
212 | { | |
213 | // Nothing to do here. tdsos used to point to the _loadedDSOs instance | |
214 | // in the dying thread's TLS segment and as such is not valid anymore. | |
215 | // The memory for the array contents was already reclaimed in | |
216 | // cleanupLoadedLibraries(). | |
217 | } | |
218 | ||
219 | void scanTLSRanges(Array!(ThreadDSO)* tdsos, scope ScanDG dg) nothrow | |
220 | { | |
221 | foreach (ref tdso; *tdsos) | |
222 | dg(tdso._tlsRange.ptr, tdso._tlsRange.ptr + tdso._tlsRange.length); | |
223 | } | |
224 | ||
225 | // interface for core.thread to inherit loaded libraries | |
226 | void* pinLoadedLibraries() nothrow @nogc | |
227 | { | |
228 | auto res = cast(Array!(ThreadDSO)*)calloc(1, Array!(ThreadDSO).sizeof); | |
229 | res.length = _loadedDSOs.length; | |
230 | foreach (i, ref tdso; _loadedDSOs) | |
231 | { | |
232 | (*res)[i] = tdso; | |
233 | if (tdso._addCnt) | |
234 | { | |
235 | // Increment the dlopen ref for explicitly loaded libraries to pin them. | |
3ef1f32e | 236 | const success = .dlopen(linkMapForHandle(tdso._pdso._handle).l_name, RTLD_LAZY) !is null; |
237 | safeAssert(success, "Failed to increment dlopen ref."); | |
03385ed3 | 238 | (*res)[i]._addCnt = 1; // new array takes over the additional ref count |
239 | } | |
240 | } | |
241 | return res; | |
242 | } | |
243 | ||
244 | void unpinLoadedLibraries(void* p) nothrow @nogc | |
245 | { | |
246 | auto pary = cast(Array!(ThreadDSO)*)p; | |
247 | // In case something failed we need to undo the pinning. | |
248 | foreach (ref tdso; *pary) | |
249 | { | |
250 | if (tdso._addCnt) | |
251 | { | |
252 | auto handle = tdso._pdso._handle; | |
3ef1f32e | 253 | safeAssert(handle !is null, "Invalid library handle."); |
03385ed3 | 254 | .dlclose(handle); |
255 | } | |
256 | } | |
257 | pary.reset(); | |
258 | .free(pary); | |
259 | } | |
260 | ||
261 | // Called before TLS ctors are ran, copy over the loaded libraries | |
262 | // of the parent thread. | |
263 | void inheritLoadedLibraries(void* p) nothrow @nogc | |
264 | { | |
3ef1f32e | 265 | safeAssert(_loadedDSOs.empty, "DSOs have already been registered for this thread."); |
03385ed3 | 266 | _loadedDSOs.swap(*cast(Array!(ThreadDSO)*)p); |
267 | .free(p); | |
268 | foreach (ref dso; _loadedDSOs) | |
269 | { | |
270 | // the copied _tlsRange corresponds to parent thread | |
271 | dso.updateTLSRange(); | |
272 | } | |
273 | } | |
274 | ||
275 | // Called after all TLS dtors ran, decrements all remaining dlopen refs. | |
276 | void cleanupLoadedLibraries() nothrow @nogc | |
277 | { | |
278 | foreach (ref tdso; _loadedDSOs) | |
279 | { | |
280 | if (tdso._addCnt == 0) continue; | |
281 | ||
282 | auto handle = tdso._pdso._handle; | |
3ef1f32e | 283 | safeAssert(handle !is null, "Invalid DSO handle."); |
03385ed3 | 284 | for (; tdso._addCnt > 0; --tdso._addCnt) |
285 | .dlclose(handle); | |
286 | } | |
287 | ||
288 | // Free the memory for the array contents. | |
289 | _loadedDSOs.reset(); | |
290 | } | |
291 | } | |
292 | else | |
293 | { | |
294 | /*** | |
295 | * Called once per thread; returns array of thread local storage ranges | |
296 | */ | |
297 | Array!(void[])* initTLSRanges() nothrow @nogc | |
298 | { | |
3ef1f32e | 299 | return &_tlsRanges(); |
03385ed3 | 300 | } |
301 | ||
302 | void finiTLSRanges(Array!(void[])* rngs) nothrow @nogc | |
303 | { | |
304 | rngs.reset(); | |
305 | } | |
306 | ||
307 | void scanTLSRanges(Array!(void[])* rngs, scope ScanDG dg) nothrow | |
308 | { | |
309 | foreach (rng; *rngs) | |
310 | dg(rng.ptr, rng.ptr + rng.length); | |
311 | } | |
312 | } | |
313 | ||
314 | private: | |
315 | ||
03385ed3 | 316 | version (Shared) |
317 | { | |
318 | /* | |
319 | * Array of thread local DSO metadata for all libraries loaded and | |
320 | * initialized in this thread. | |
321 | * | |
322 | * Note: | |
323 | * A newly spawned thread will inherit these libraries. | |
324 | * Note: | |
325 | * We use an array here to preserve the order of | |
326 | * initialization. If that became a performance issue, we | |
327 | * could use a hash table and enumerate the DSOs during | |
328 | * loading so that the hash table values could be sorted when | |
329 | * necessary. | |
330 | */ | |
331 | struct ThreadDSO | |
332 | { | |
333 | DSO* _pdso; | |
334 | static if (_pdso.sizeof == 8) uint _refCnt, _addCnt; | |
335 | else static if (_pdso.sizeof == 4) ushort _refCnt, _addCnt; | |
336 | else static assert(0, "unimplemented"); | |
337 | void[] _tlsRange; | |
338 | alias _pdso this; | |
339 | // update the _tlsRange for the executing thread | |
340 | void updateTLSRange() nothrow @nogc | |
341 | { | |
3ef1f32e | 342 | _tlsRange = _pdso.tlsRange(); |
03385ed3 | 343 | } |
344 | } | |
3ef1f32e | 345 | @property ref Array!(ThreadDSO) _loadedDSOs() @nogc nothrow { static Array!(ThreadDSO) x; return x; } |
03385ed3 | 346 | |
347 | /* | |
348 | * Set to true during rt_loadLibrary/rt_unloadLibrary calls. | |
349 | */ | |
350 | bool _rtLoading; | |
351 | ||
352 | /* | |
353 | * Hash table to map link_map* to corresponding DSO*. | |
354 | * The hash table is protected by a Mutex. | |
355 | */ | |
356 | __gshared pthread_mutex_t _handleToDSOMutex; | |
3ef1f32e | 357 | @property ref HashTab!(void*, DSO*) _handleToDSO() @nogc nothrow { __gshared HashTab!(void*, DSO*) x; return x; } |
03385ed3 | 358 | |
359 | /* | |
360 | * Section in executable that contains copy relocations. | |
361 | * Might be null when druntime is dynamically loaded by a C host. | |
362 | */ | |
363 | __gshared const(void)[] _copyRelocSection; | |
364 | } | |
365 | else | |
366 | { | |
367 | /* | |
368 | * Static DSOs loaded by the runtime linker. This includes the | |
369 | * executable. These can't be unloaded. | |
370 | */ | |
3ef1f32e | 371 | @property ref Array!(DSO*) _loadedDSOs() @nogc nothrow { __gshared Array!(DSO*) x; return x; } |
03385ed3 | 372 | |
373 | /* | |
374 | * Thread local array that contains TLS memory ranges for each | |
375 | * library initialized in this thread. | |
376 | */ | |
3ef1f32e | 377 | @property ref Array!(void[]) _tlsRanges() @nogc nothrow { static Array!(void[]) x; return x; } |
03385ed3 | 378 | |
379 | enum _rtLoading = false; | |
380 | } | |
381 | ||
382 | /////////////////////////////////////////////////////////////////////////////// | |
383 | // Compiler to runtime interface. | |
384 | /////////////////////////////////////////////////////////////////////////////// | |
385 | ||
03385ed3 | 386 | /* |
387 | * This data structure is generated by the compiler, and then passed to | |
388 | * _d_dso_registry(). | |
389 | */ | |
390 | struct CompilerDSOData | |
391 | { | |
392 | size_t _version; // currently 1 | |
393 | void** _slot; // can be used to store runtime data | |
394 | immutable(object.ModuleInfo*)* _minfo_beg, _minfo_end; // array of modules in this object file | |
395 | } | |
396 | ||
397 | T[] toRange(T)(T* beg, T* end) { return beg[0 .. end - beg]; } | |
398 | ||
399 | /* For each shared library and executable, the compiler generates code that | |
400 | * sets up CompilerDSOData and calls _d_dso_registry(). | |
401 | * A pointer to that code is inserted into both the .ctors and .dtors | |
402 | * segment so it gets called by the loader on startup and shutdown. | |
403 | */ | |
404 | extern(C) void _d_dso_registry(CompilerDSOData* data) | |
405 | { | |
406 | // only one supported currently | |
3ef1f32e | 407 | safeAssert(data._version >= 1, "Incompatible compiler-generated DSO data version."); |
03385ed3 | 408 | |
409 | // no backlink => register | |
410 | if (*data._slot is null) | |
411 | { | |
412 | immutable firstDSO = _loadedDSOs.empty; | |
413 | if (firstDSO) initLocks(); | |
414 | ||
415 | DSO* pdso = cast(DSO*).calloc(1, DSO.sizeof); | |
416 | assert(typeid(DSO).initializer().ptr is null); | |
417 | *data._slot = pdso; // store backlink in library record | |
418 | ||
419 | pdso._moduleGroup = ModuleGroup(toRange(data._minfo_beg, data._minfo_end)); | |
420 | ||
421 | dl_phdr_info info = void; | |
3ef1f32e | 422 | const headerFound = findDSOInfoForAddr(data._slot, &info); |
423 | safeAssert(headerFound, "Failed to find image header."); | |
03385ed3 | 424 | |
425 | scanSegments(info, pdso); | |
426 | ||
427 | version (Shared) | |
428 | { | |
429 | auto handle = handleForAddr(data._slot); | |
430 | ||
03385ed3 | 431 | getDependencies(info, pdso._deps); |
432 | pdso._handle = handle; | |
433 | setDSOForHandle(pdso, pdso._handle); | |
434 | ||
435 | if (!_rtLoading) | |
436 | { | |
437 | /* This DSO was not loaded by rt_loadLibrary which | |
438 | * happens for all dependencies of an executable or | |
439 | * the first dlopen call from a C program. | |
440 | * In this case we add the DSO to the _loadedDSOs of this | |
441 | * thread with a refCnt of 1 and call the TlsCtors. | |
442 | */ | |
443 | immutable ushort refCnt = 1, addCnt = 0; | |
3ef1f32e | 444 | _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt, pdso.tlsRange())); |
03385ed3 | 445 | } |
446 | } | |
447 | else | |
448 | { | |
3ef1f32e | 449 | foreach (p; _loadedDSOs) |
450 | safeAssert(p !is pdso, "DSO already registered."); | |
03385ed3 | 451 | _loadedDSOs.insertBack(pdso); |
3ef1f32e | 452 | _tlsRanges.insertBack(pdso.tlsRange()); |
03385ed3 | 453 | } |
454 | ||
455 | // don't initialize modules before rt_init was called (see Bugzilla 11378) | |
456 | if (_isRuntimeInitialized) | |
457 | { | |
458 | registerGCRanges(pdso); | |
459 | // rt_loadLibrary will run tls ctors, so do this only for dlopen | |
460 | immutable runTlsCtors = !_rtLoading; | |
461 | runModuleConstructors(pdso, runTlsCtors); | |
462 | } | |
463 | } | |
464 | // has backlink => unregister | |
465 | else | |
466 | { | |
467 | DSO* pdso = cast(DSO*)*data._slot; | |
468 | *data._slot = null; | |
469 | ||
470 | // don't finalizes modules after rt_term was called (see Bugzilla 11378) | |
471 | if (_isRuntimeInitialized) | |
472 | { | |
473 | // rt_unloadLibrary already ran tls dtors, so do this only for dlclose | |
474 | immutable runTlsDtors = !_rtLoading; | |
475 | runModuleDestructors(pdso, runTlsDtors); | |
476 | unregisterGCRanges(pdso); | |
477 | // run finalizers after module dtors (same order as in rt_term) | |
478 | version (Shared) runFinalizers(pdso); | |
479 | } | |
480 | ||
481 | version (Shared) | |
482 | { | |
483 | if (!_rtLoading) | |
484 | { | |
485 | /* This DSO was not unloaded by rt_unloadLibrary so we | |
486 | * have to remove it from _loadedDSOs here. | |
487 | */ | |
488 | foreach (i, ref tdso; _loadedDSOs) | |
489 | { | |
490 | if (tdso._pdso == pdso) | |
491 | { | |
492 | _loadedDSOs.remove(i); | |
493 | break; | |
494 | } | |
495 | } | |
496 | } | |
497 | ||
498 | unsetDSOForHandle(pdso, pdso._handle); | |
03385ed3 | 499 | } |
500 | else | |
501 | { | |
502 | // static DSOs are unloaded in reverse order | |
3ef1f32e | 503 | safeAssert(pdso == _loadedDSOs.back, "DSO being unregistered isn't current last one."); |
03385ed3 | 504 | _loadedDSOs.popBack(); |
505 | } | |
506 | ||
507 | freeDSO(pdso); | |
508 | ||
3ef1f32e | 509 | // last DSO being unloaded => shutdown registry |
510 | if (_loadedDSOs.empty) | |
511 | { | |
512 | version (Shared) | |
513 | { | |
514 | safeAssert(_handleToDSO.empty, "_handleToDSO not in sync with _loadedDSOs."); | |
515 | _handleToDSO.reset(); | |
516 | } | |
517 | finiLocks(); | |
518 | } | |
03385ed3 | 519 | } |
520 | } | |
521 | ||
522 | /////////////////////////////////////////////////////////////////////////////// | |
3ef1f32e | 523 | // Dynamic loading |
03385ed3 | 524 | /////////////////////////////////////////////////////////////////////////////// |
525 | ||
526 | // Shared D libraries are only supported when linking against a shared druntime library. | |
527 | ||
528 | version (Shared) | |
529 | { | |
530 | ThreadDSO* findThreadDSO(DSO* pdso) nothrow @nogc | |
531 | { | |
532 | foreach (ref tdata; _loadedDSOs) | |
533 | if (tdata._pdso == pdso) return &tdata; | |
534 | return null; | |
535 | } | |
536 | ||
537 | void incThreadRef(DSO* pdso, bool incAdd) | |
538 | { | |
539 | if (auto tdata = findThreadDSO(pdso)) // already initialized | |
540 | { | |
541 | if (incAdd && ++tdata._addCnt > 1) return; | |
542 | ++tdata._refCnt; | |
543 | } | |
544 | else | |
545 | { | |
546 | foreach (dep; pdso._deps) | |
547 | incThreadRef(dep, false); | |
548 | immutable ushort refCnt = 1, addCnt = incAdd ? 1 : 0; | |
3ef1f32e | 549 | _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt, pdso.tlsRange())); |
03385ed3 | 550 | pdso._moduleGroup.runTlsCtors(); |
551 | } | |
552 | } | |
553 | ||
554 | void decThreadRef(DSO* pdso, bool decAdd) | |
555 | { | |
556 | auto tdata = findThreadDSO(pdso); | |
3ef1f32e | 557 | safeAssert(tdata !is null, "Failed to find thread DSO."); |
558 | safeAssert(!decAdd || tdata._addCnt > 0, "Mismatching rt_unloadLibrary call."); | |
03385ed3 | 559 | |
560 | if (decAdd && --tdata._addCnt > 0) return; | |
561 | if (--tdata._refCnt > 0) return; | |
562 | ||
563 | pdso._moduleGroup.runTlsDtors(); | |
564 | foreach (i, ref td; _loadedDSOs) | |
565 | if (td._pdso == pdso) _loadedDSOs.remove(i); | |
566 | foreach (dep; pdso._deps) | |
567 | decThreadRef(dep, false); | |
568 | } | |
569 | ||
570 | extern(C) void* rt_loadLibrary(const char* name) | |
571 | { | |
572 | immutable save = _rtLoading; | |
573 | _rtLoading = true; | |
574 | scope (exit) _rtLoading = save; | |
575 | ||
576 | auto handle = .dlopen(name, RTLD_LAZY); | |
577 | if (handle is null) return null; | |
578 | ||
579 | // if it's a D library | |
580 | if (auto pdso = dsoForHandle(handle)) | |
581 | incThreadRef(pdso, true); | |
582 | return handle; | |
583 | } | |
584 | ||
585 | extern(C) int rt_unloadLibrary(void* handle) | |
586 | { | |
587 | if (handle is null) return false; | |
588 | ||
589 | immutable save = _rtLoading; | |
590 | _rtLoading = true; | |
591 | scope (exit) _rtLoading = save; | |
592 | ||
593 | // if it's a D library | |
594 | if (auto pdso = dsoForHandle(handle)) | |
595 | decThreadRef(pdso, true); | |
596 | return .dlclose(handle) == 0; | |
597 | } | |
598 | } | |
599 | ||
600 | /////////////////////////////////////////////////////////////////////////////// | |
3ef1f32e | 601 | // Helper functions |
03385ed3 | 602 | /////////////////////////////////////////////////////////////////////////////// |
603 | ||
604 | void initLocks() nothrow @nogc | |
605 | { | |
606 | version (Shared) | |
607 | !pthread_mutex_init(&_handleToDSOMutex, null) || assert(0); | |
608 | } | |
609 | ||
610 | void finiLocks() nothrow @nogc | |
611 | { | |
612 | version (Shared) | |
613 | !pthread_mutex_destroy(&_handleToDSOMutex) || assert(0); | |
614 | } | |
615 | ||
616 | void runModuleConstructors(DSO* pdso, bool runTlsCtors) | |
617 | { | |
618 | pdso._moduleGroup.sortCtors(); | |
619 | pdso._moduleGroup.runCtors(); | |
620 | if (runTlsCtors) pdso._moduleGroup.runTlsCtors(); | |
621 | } | |
622 | ||
623 | void runModuleDestructors(DSO* pdso, bool runTlsDtors) | |
624 | { | |
625 | if (runTlsDtors) pdso._moduleGroup.runTlsDtors(); | |
626 | pdso._moduleGroup.runDtors(); | |
627 | } | |
628 | ||
629 | void registerGCRanges(DSO* pdso) nothrow @nogc | |
630 | { | |
631 | foreach (rng; pdso._gcRanges) | |
632 | GC.addRange(rng.ptr, rng.length); | |
633 | } | |
634 | ||
635 | void unregisterGCRanges(DSO* pdso) nothrow @nogc | |
636 | { | |
637 | foreach (rng; pdso._gcRanges) | |
638 | GC.removeRange(rng.ptr); | |
639 | } | |
640 | ||
641 | version (Shared) void runFinalizers(DSO* pdso) | |
642 | { | |
643 | foreach (seg; pdso._codeSegments) | |
644 | GC.runFinalizers(seg); | |
645 | } | |
646 | ||
647 | void freeDSO(DSO* pdso) nothrow @nogc | |
648 | { | |
649 | pdso._gcRanges.reset(); | |
3ef1f32e | 650 | version (Shared) |
651 | { | |
652 | pdso._codeSegments.reset(); | |
653 | pdso._deps.reset(); | |
654 | pdso._handle = null; | |
655 | } | |
03385ed3 | 656 | .free(pdso); |
657 | } | |
658 | ||
659 | version (Shared) | |
660 | { | |
661 | @nogc nothrow: | |
3ef1f32e | 662 | link_map* linkMapForHandle(void* handle) |
03385ed3 | 663 | { |
664 | link_map* map; | |
3ef1f32e | 665 | const success = dlinfo(handle, RTLD_DI_LINKMAP, &map) == 0; |
666 | safeAssert(success, "Failed to get DSO info."); | |
03385ed3 | 667 | return map; |
668 | } | |
669 | ||
3ef1f32e | 670 | link_map* exeLinkMap(link_map* map) |
03385ed3 | 671 | { |
3ef1f32e | 672 | safeAssert(map !is null, "Invalid link_map."); |
03385ed3 | 673 | while (map.l_prev !is null) |
674 | map = map.l_prev; | |
675 | return map; | |
676 | } | |
677 | ||
3ef1f32e | 678 | DSO* dsoForHandle(void* handle) |
03385ed3 | 679 | { |
680 | DSO* pdso; | |
681 | !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); | |
682 | if (auto ppdso = handle in _handleToDSO) | |
683 | pdso = *ppdso; | |
684 | !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); | |
685 | return pdso; | |
686 | } | |
687 | ||
3ef1f32e | 688 | void setDSOForHandle(DSO* pdso, void* handle) |
03385ed3 | 689 | { |
690 | !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); | |
3ef1f32e | 691 | safeAssert(handle !in _handleToDSO, "DSO already registered."); |
03385ed3 | 692 | _handleToDSO[handle] = pdso; |
693 | !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); | |
694 | } | |
695 | ||
3ef1f32e | 696 | void unsetDSOForHandle(DSO* pdso, void* handle) |
03385ed3 | 697 | { |
698 | !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); | |
3ef1f32e | 699 | safeAssert(_handleToDSO[handle] == pdso, "Handle doesn't match registered DSO."); |
03385ed3 | 700 | _handleToDSO.remove(handle); |
701 | !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); | |
702 | } | |
703 | ||
3ef1f32e | 704 | void getDependencies(in ref dl_phdr_info info, ref Array!(DSO*) deps) |
03385ed3 | 705 | { |
706 | // get the entries of the .dynamic section | |
707 | ElfW!"Dyn"[] dyns; | |
708 | foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum]) | |
709 | { | |
710 | if (phdr.p_type == PT_DYNAMIC) | |
711 | { | |
712 | auto p = cast(ElfW!"Dyn"*)(info.dlpi_addr + (phdr.p_vaddr & ~(size_t.sizeof - 1))); | |
713 | dyns = p[0 .. phdr.p_memsz / ElfW!"Dyn".sizeof]; | |
714 | break; | |
715 | } | |
716 | } | |
717 | // find the string table which contains the sonames | |
718 | const(char)* strtab; | |
719 | foreach (dyn; dyns) | |
720 | { | |
721 | if (dyn.d_tag == DT_STRTAB) | |
722 | { | |
3ef1f32e | 723 | version (CRuntime_Musl) |
724 | strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate | |
725 | else version (linux) | |
03385ed3 | 726 | strtab = cast(const(char)*)dyn.d_un.d_ptr; |
727 | else version (FreeBSD) | |
728 | strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate | |
729 | else version (NetBSD) | |
730 | strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate | |
3ef1f32e | 731 | else version (DragonFlyBSD) |
732 | strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate | |
c3e1a755 | 733 | else version (Solaris) |
734 | strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate | |
03385ed3 | 735 | else |
736 | static assert(0, "unimplemented"); | |
737 | break; | |
738 | } | |
739 | } | |
740 | foreach (dyn; dyns) | |
741 | { | |
742 | immutable tag = dyn.d_tag; | |
743 | if (!(tag == DT_NEEDED || tag == DT_AUXILIARY || tag == DT_FILTER)) | |
744 | continue; | |
745 | ||
746 | // soname of the dependency | |
747 | auto name = strtab + dyn.d_un.d_val; | |
748 | // get handle without loading the library | |
749 | auto handle = handleForName(name); | |
750 | // the runtime linker has already loaded all dependencies | |
3ef1f32e | 751 | safeAssert(handle !is null, "Failed to get library handle."); |
03385ed3 | 752 | // if it's a D library |
753 | if (auto pdso = dsoForHandle(handle)) | |
754 | deps.insertBack(pdso); // append it to the dependencies | |
755 | } | |
756 | } | |
757 | ||
3ef1f32e | 758 | void* handleForName(const char* name) |
03385ed3 | 759 | { |
760 | auto handle = .dlopen(name, RTLD_NOLOAD | RTLD_LAZY); | |
c3e1a755 | 761 | version (Solaris) { } |
762 | else if (handle !is null) .dlclose(handle); // drop reference count | |
03385ed3 | 763 | return handle; |
764 | } | |
765 | } | |
766 | ||
767 | /////////////////////////////////////////////////////////////////////////////// | |
768 | // Elf program header iteration | |
769 | /////////////////////////////////////////////////////////////////////////////// | |
770 | ||
771 | /************ | |
772 | * Scan segments in Linux dl_phdr_info struct and store | |
773 | * the TLS and writeable data segments in *pdso. | |
774 | */ | |
775 | void scanSegments(in ref dl_phdr_info info, DSO* pdso) nothrow @nogc | |
776 | { | |
777 | foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum]) | |
778 | { | |
779 | switch (phdr.p_type) | |
780 | { | |
781 | case PT_LOAD: | |
782 | if (phdr.p_flags & PF_W) // writeable data segment | |
783 | { | |
784 | auto beg = cast(void*)(info.dlpi_addr + (phdr.p_vaddr & ~(size_t.sizeof - 1))); | |
785 | pdso._gcRanges.insertBack(beg[0 .. phdr.p_memsz]); | |
786 | } | |
787 | version (Shared) if (phdr.p_flags & PF_X) // code segment | |
788 | { | |
789 | auto beg = cast(void*)(info.dlpi_addr + (phdr.p_vaddr & ~(size_t.sizeof - 1))); | |
790 | pdso._codeSegments.insertBack(beg[0 .. phdr.p_memsz]); | |
791 | } | |
792 | break; | |
793 | ||
794 | case PT_TLS: // TLS segment | |
3ef1f32e | 795 | safeAssert(!pdso._tlsSize, "Multiple TLS segments in image header."); |
79c21307 | 796 | static if (OS_Have_Dlpi_Tls_Modid) |
797 | { | |
798 | pdso._tlsMod = info.dlpi_tls_modid; | |
799 | pdso._tlsSize = phdr.p_memsz; | |
800 | } | |
801 | else version (Solaris) | |
802 | { | |
803 | struct Rt_map | |
804 | { | |
805 | Link_map rt_public; | |
806 | const char* rt_pathname; | |
807 | c_ulong rt_padstart; | |
808 | c_ulong rt_padimlen; | |
809 | c_ulong rt_msize; | |
810 | uint rt_flags; | |
811 | uint rt_flags1; | |
812 | c_ulong rt_tlsmodid; | |
813 | } | |
814 | ||
815 | Rt_map* map; | |
816 | version (Shared) | |
817 | dlinfo(handleForName(info.dlpi_name), RTLD_DI_LINKMAP, &map); | |
818 | else | |
819 | dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &map); | |
820 | // Until Solaris 11.4, tlsmodid for the executable is 0. | |
821 | // Let it start at 1 as the rest of the code expects. | |
822 | pdso._tlsMod = map.rt_tlsmodid + 1; | |
823 | pdso._tlsSize = phdr.p_memsz; | |
824 | } | |
825 | else | |
826 | { | |
827 | pdso._tlsMod = 0; | |
828 | pdso._tlsSize = 0; | |
829 | } | |
03385ed3 | 830 | break; |
831 | ||
832 | default: | |
833 | break; | |
834 | } | |
835 | } | |
836 | } | |
837 | ||
838 | /************************** | |
839 | * Input: | |
3ef1f32e | 840 | * result where the output is to be written; dl_phdr_info is an OS struct |
03385ed3 | 841 | * Returns: |
842 | * true if found, and *result is filled in | |
843 | * References: | |
844 | * http://linux.die.net/man/3/dl_iterate_phdr | |
845 | */ | |
3ef1f32e | 846 | bool findDSOInfoForAddr(in void* addr, dl_phdr_info* result=null) nothrow @nogc |
03385ed3 | 847 | { |
c3e1a755 | 848 | version (linux) enum IterateManually = true; |
849 | else version (NetBSD) enum IterateManually = true; | |
850 | else version (Solaris) enum IterateManually = true; | |
851 | else enum IterateManually = false; | |
03385ed3 | 852 | |
3ef1f32e | 853 | static if (IterateManually) |
03385ed3 | 854 | { |
3ef1f32e | 855 | static struct DG { const(void)* addr; dl_phdr_info* result; } |
856 | ||
857 | extern(C) int callback(dl_phdr_info* info, size_t sz, void* arg) nothrow @nogc | |
03385ed3 | 858 | { |
3ef1f32e | 859 | auto p = cast(DG*)arg; |
860 | if (findSegmentForAddr(*info, p.addr)) | |
861 | { | |
862 | if (p.result !is null) *p.result = *info; | |
863 | return 1; // break; | |
864 | } | |
865 | return 0; // continue iteration | |
03385ed3 | 866 | } |
03385ed3 | 867 | |
3ef1f32e | 868 | auto dg = DG(addr, result); |
03385ed3 | 869 | |
3ef1f32e | 870 | /* OS function that walks through the list of an application's shared objects and |
871 | * calls 'callback' once for each object, until either all shared objects | |
872 | * have been processed or 'callback' returns a nonzero value. | |
873 | */ | |
874 | return dl_iterate_phdr(&callback, &dg) != 0; | |
875 | } | |
876 | else version (FreeBSD) | |
03385ed3 | 877 | { |
3ef1f32e | 878 | return !!_rtld_addr_phdr(addr, result); |
03385ed3 | 879 | } |
3ef1f32e | 880 | else version (DragonFlyBSD) |
881 | { | |
882 | return !!_rtld_addr_phdr(addr, result); | |
883 | } | |
884 | else | |
885 | static assert(0, "unimplemented"); | |
03385ed3 | 886 | } |
887 | ||
888 | /********************************* | |
889 | * Determine if 'addr' lies within shared object 'info'. | |
890 | * If so, return true and fill in 'result' with the corresponding ELF program header. | |
891 | */ | |
892 | bool findSegmentForAddr(in ref dl_phdr_info info, in void* addr, ElfW!"Phdr"* result=null) nothrow @nogc | |
893 | { | |
894 | if (addr < cast(void*)info.dlpi_addr) // less than base address of object means quick reject | |
895 | return false; | |
896 | ||
897 | foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum]) | |
898 | { | |
899 | auto beg = cast(void*)(info.dlpi_addr + phdr.p_vaddr); | |
900 | if (cast(size_t)(addr - beg) < phdr.p_memsz) | |
901 | { | |
902 | if (result !is null) *result = phdr; | |
903 | return true; | |
904 | } | |
905 | } | |
906 | return false; | |
907 | } | |
908 | ||
909 | version (linux) import core.sys.linux.errno : program_invocation_name; | |
910 | // should be in core.sys.freebsd.stdlib | |
911 | version (FreeBSD) extern(C) const(char)* getprogname() nothrow @nogc; | |
3ef1f32e | 912 | version (DragonFlyBSD) extern(C) const(char)* getprogname() nothrow @nogc; |
03385ed3 | 913 | version (NetBSD) extern(C) const(char)* getprogname() nothrow @nogc; |
c3e1a755 | 914 | version (Solaris) extern(C) const(char)* getprogname() nothrow @nogc; |
03385ed3 | 915 | |
916 | @property const(char)* progname() nothrow @nogc | |
917 | { | |
918 | version (linux) return program_invocation_name; | |
919 | version (FreeBSD) return getprogname(); | |
3ef1f32e | 920 | version (DragonFlyBSD) return getprogname(); |
03385ed3 | 921 | version (NetBSD) return getprogname(); |
c3e1a755 | 922 | version (Solaris) return getprogname(); |
03385ed3 | 923 | } |
924 | ||
925 | const(char)[] dsoName(const char* dlpi_name) nothrow @nogc | |
926 | { | |
927 | // the main executable doesn't have a name in its dlpi_name field | |
928 | const char* p = dlpi_name[0] != 0 ? dlpi_name : progname; | |
929 | return p[0 .. strlen(p)]; | |
930 | } | |
931 | ||
03385ed3 | 932 | /************************** |
933 | * Input: | |
934 | * addr an internal address of a DSO | |
935 | * Returns: | |
936 | * the dlopen handle for that DSO or null if addr is not within a loaded DSO | |
937 | */ | |
938 | version (Shared) void* handleForAddr(void* addr) nothrow @nogc | |
939 | { | |
940 | Dl_info info = void; | |
941 | if (dladdr(addr, &info) != 0) | |
942 | return handleForName(info.dli_fname); | |
943 | return null; | |
944 | } | |
945 | ||
946 | /////////////////////////////////////////////////////////////////////////////// | |
947 | // TLS module helper | |
948 | /////////////////////////////////////////////////////////////////////////////// | |
949 | ||
950 | ||
951 | /* | |
952 | * Returns: the TLS memory range for a given module and the calling | |
953 | * thread or null if that module has no TLS. | |
954 | * | |
955 | * Note: This will cause the TLS memory to be eagerly allocated. | |
956 | */ | |
957 | struct tls_index | |
958 | { | |
22163f0d | 959 | version (CRuntime_Glibc) |
960 | { | |
961 | // For x86_64, fields are of type uint64_t, this is important for x32 | |
962 | // where tls_index would otherwise have the wrong size. | |
963 | // See https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86_64/dl-tls.h | |
964 | version (X86_64) | |
965 | { | |
966 | ulong ti_module; | |
967 | ulong ti_offset; | |
968 | } | |
969 | else | |
970 | { | |
971 | c_ulong ti_module; | |
972 | c_ulong ti_offset; | |
973 | } | |
974 | } | |
975 | else | |
976 | { | |
977 | size_t ti_module; | |
978 | size_t ti_offset; | |
979 | } | |
03385ed3 | 980 | } |
981 | ||
982 | extern(C) void* __tls_get_addr(tls_index* ti) nothrow @nogc; | |
983 | ||
984 | /* The dynamic thread vector (DTV) pointers may point 0x8000 past the start of | |
985 | * each TLS block. This is at least true for PowerPC and Mips platforms. | |
986 | * See: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/powerpc/dl-tls.h;h=f7cf6f96ebfb505abfd2f02be0ad0e833107c0cd;hb=HEAD#l34 | |
987 | * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/mips/dl-tls.h;h=93a6dc050cb144b9f68b96fb3199c60f5b1fcd18;hb=HEAD#l32 | |
387a5d4a | 988 | * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/riscv/dl-tls.h;h=ab2d860314de94c18812bc894ff6b3f55368f20f;hb=HEAD#l32 |
03385ed3 | 989 | */ |
990 | version (X86) | |
387a5d4a | 991 | enum TLS_DTV_OFFSET = 0x0; |
03385ed3 | 992 | else version (X86_64) |
387a5d4a | 993 | enum TLS_DTV_OFFSET = 0x0; |
03385ed3 | 994 | else version (ARM) |
387a5d4a | 995 | enum TLS_DTV_OFFSET = 0x0; |
03385ed3 | 996 | else version (AArch64) |
387a5d4a | 997 | enum TLS_DTV_OFFSET = 0x0; |
998 | else version (RISCV32) | |
999 | enum TLS_DTV_OFFSET = 0x800; | |
1000 | else version (RISCV64) | |
1001 | enum TLS_DTV_OFFSET = 0x800; | |
a6872a2f | 1002 | else version (HPPA) |
1003 | enum TLS_DTV_OFFSET = 0x0; | |
03385ed3 | 1004 | else version (SPARC) |
387a5d4a | 1005 | enum TLS_DTV_OFFSET = 0x0; |
03385ed3 | 1006 | else version (SPARC64) |
387a5d4a | 1007 | enum TLS_DTV_OFFSET = 0x0; |
03385ed3 | 1008 | else version (PPC) |
1009 | enum TLS_DTV_OFFSET = 0x8000; | |
1010 | else version (PPC64) | |
1011 | enum TLS_DTV_OFFSET = 0x8000; | |
1012 | else version (MIPS32) | |
1013 | enum TLS_DTV_OFFSET = 0x8000; | |
1014 | else version (MIPS64) | |
1015 | enum TLS_DTV_OFFSET = 0x8000; | |
1016 | else | |
1017 | static assert( false, "Platform not supported." ); | |
1018 | ||
1019 | void[] getTLSRange(size_t mod, size_t sz) nothrow @nogc | |
1020 | { | |
1021 | if (mod == 0) | |
1022 | return null; | |
1023 | ||
79c21307 | 1024 | version (Solaris) |
1025 | { | |
1026 | static if (!OS_Have_Dlpi_Tls_Modid) | |
1027 | mod -= 1; | |
1028 | } | |
1029 | ||
03385ed3 | 1030 | // base offset |
1031 | auto ti = tls_index(mod, 0); | |
1032 | return (__tls_get_addr(&ti)-TLS_DTV_OFFSET)[0 .. sz]; | |
1033 | } |