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