From 32703b80f66f7ca504eb79bee7e745f22cf096a8 Mon Sep 17 00:00:00 2001 From: Iain Buclaw Date: Thu, 8 Apr 2021 16:11:16 +0200 Subject: libphobos: Add section support code for MACHO and PE/COFF This replaces the original and untested support for Windows and OSX, and is the 90% of the work needed to support libphobos on those targets. The core.thread interface has been updated to accomodate for the same function might be implemented by any of the platform-dependent modules. libphobos/ChangeLog: * libdruntime/Makefile.am (DRUNTIME_DSOURCES): Removed gcc/sections/android.d, elf_shared.d, osx.d, win32.d, and win64.d. Added gcc/sections/common.d, elf.d macho.d, and pecoff.d. * libdruntime/Makefile.in: Regenerate. * libdruntime/core/thread/osthread.d: Update externDFunc FQDN names to use platform independant section function names. * libdruntime/gcc/sections/elf_shared.d: Renamed to... * libdruntime/gcc/sections/elf.d: ...this. Mangle functions for core.thread interface as if they come from the gcc.sections module. * libdruntime/gcc/sections/package.d: Update public imports, declare functions for core.thread interface. * libdruntime/gcc/sections/android.d: Removed. * libdruntime/gcc/sections/osx.d: Removed. * libdruntime/gcc/sections/win32.d: Removed. * libdruntime/gcc/sections/win64.d: Removed. * libdruntime/gcc/sections/common.d: New file. * libdruntime/gcc/sections/macho.d: New file. * libdruntime/gcc/sections/pecoff.d: New file. --- libphobos/libdruntime/gcc/sections/android.d | 184 ---- libphobos/libdruntime/gcc/sections/common.d | 39 + libphobos/libdruntime/gcc/sections/elf.d | 1073 ++++++++++++++++++++++ libphobos/libdruntime/gcc/sections/elf_shared.d | 1122 ----------------------- libphobos/libdruntime/gcc/sections/macho.d | 738 +++++++++++++++ libphobos/libdruntime/gcc/sections/osx.d | 284 ------ libphobos/libdruntime/gcc/sections/package.d | 47 +- libphobos/libdruntime/gcc/sections/pecoff.d | 826 +++++++++++++++++ libphobos/libdruntime/gcc/sections/win32.d | 183 ---- libphobos/libdruntime/gcc/sections/win64.d | 321 ------- 10 files changed, 2701 insertions(+), 2116 deletions(-) delete mode 100644 libphobos/libdruntime/gcc/sections/android.d create mode 100644 libphobos/libdruntime/gcc/sections/common.d create mode 100644 libphobos/libdruntime/gcc/sections/elf.d delete mode 100644 libphobos/libdruntime/gcc/sections/elf_shared.d create mode 100644 libphobos/libdruntime/gcc/sections/macho.d delete mode 100644 libphobos/libdruntime/gcc/sections/osx.d create mode 100644 libphobos/libdruntime/gcc/sections/pecoff.d delete mode 100644 libphobos/libdruntime/gcc/sections/win32.d delete mode 100644 libphobos/libdruntime/gcc/sections/win64.d (limited to 'libphobos/libdruntime/gcc') diff --git a/libphobos/libdruntime/gcc/sections/android.d b/libphobos/libdruntime/gcc/sections/android.d deleted file mode 100644 index 4af26b4..0000000 --- a/libphobos/libdruntime/gcc/sections/android.d +++ /dev/null @@ -1,184 +0,0 @@ -// Bionic-specific support for sections. -// Copyright (C) 2019-2021 Free Software Foundation, Inc. - -// GCC is free software; you can redistribute it and/or modify it under -// the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 3, or (at your option) any later -// version. - -// GCC is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. - -// Under Section 7 of GPL version 3, you are granted additional -// permissions described in the GCC Runtime Library Exception, version -// 3.1, as published by the Free Software Foundation. - -// You should have received a copy of the GNU General Public License and -// a copy of the GCC Runtime Library Exception along with this program; -// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -// . - -module gcc.sections.android; - -version (CRuntime_Bionic): - -// debug = PRINTF; -debug(PRINTF) import core.stdc.stdio; -import core.stdc.stdlib : malloc, free; -import rt.deh, rt.minfo; -import core.sys.posix.pthread; -import core.stdc.stdlib : calloc; -import core.stdc.string : memcpy; - -struct SectionGroup -{ - static int opApply(scope int delegate(ref SectionGroup) dg) - { - return dg(_sections); - } - - static int opApplyReverse(scope int delegate(ref SectionGroup) dg) - { - return dg(_sections); - } - - @property immutable(ModuleInfo*)[] modules() const nothrow @nogc - { - return _moduleGroup.modules; - } - - @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc - { - return _moduleGroup; - } - - @property immutable(FuncTable)[] ehTables() const nothrow @nogc - { - auto pbeg = cast(immutable(FuncTable)*)&__start_deh; - auto pend = cast(immutable(FuncTable)*)&__stop_deh; - return pbeg[0 .. pend - pbeg]; - } - - @property inout(void[])[] gcRanges() inout nothrow @nogc - { - return _gcRanges[]; - } - -private: - ModuleGroup _moduleGroup; - void[][1] _gcRanges; -} - -void initSections() nothrow @nogc -{ - pthread_key_create(&_tlsKey, null); - - auto mbeg = cast(immutable ModuleInfo**)&__start_minfo; - auto mend = cast(immutable ModuleInfo**)&__stop_minfo; - _sections.moduleGroup = ModuleGroup(mbeg[0 .. mend - mbeg]); - - auto pbeg = cast(void*)&_tlsend; - auto pend = cast(void*)&__bss_end__; - // _tlsend is a 32-bit int and may not be 64-bit void*-aligned, so align pbeg. - version (D_LP64) pbeg = cast(void*)(cast(size_t)(pbeg + 7) & ~cast(size_t)7); - _sections._gcRanges[0] = pbeg[0 .. pend - pbeg]; -} - -void finiSections() nothrow @nogc -{ - pthread_key_delete(_tlsKey); -} - -void[]* initTLSRanges() nothrow @nogc -{ - return &getTLSBlock(); -} - -void finiTLSRanges(void[]* rng) nothrow @nogc -{ - .free(rng.ptr); - .free(rng); -} - -void scanTLSRanges(void[]* rng, scope void delegate(void* pbeg, void* pend) nothrow dg) nothrow -{ - dg(rng.ptr, rng.ptr + rng.length); -} - -/* NOTE: The Bionic C library ignores thread-local data stored in the normal - * .tbss/.tdata ELF sections, which are marked with the SHF_TLS/STT_TLS - * flags. So instead we roll our own by keeping TLS data in the - * .tdata/.tbss sections but removing the SHF_TLS/STT_TLS flags, and - * access the TLS data using this function and the _tlsstart/_tlsend - * symbols as delimiters. - * - * This function is called by the code emitted by the compiler. It - * is expected to translate an address in the TLS static data to - * the corresponding address in the TLS dynamic per-thread data. - */ - -extern(C) void* __tls_get_addr( void* p ) nothrow @nogc -{ - debug(PRINTF) printf(" __tls_get_addr input - %p\n", p); - immutable offset = cast(size_t)(p - cast(void*)&_tlsstart); - auto tls = getTLSBlockAlloc(); - assert(offset < tls.length); - return tls.ptr + offset; -} - -private: - -__gshared pthread_key_t _tlsKey; - -ref void[] getTLSBlock() nothrow @nogc -{ - auto pary = cast(void[]*)pthread_getspecific(_tlsKey); - if (pary is null) - { - pary = cast(void[]*).calloc(1, (void[]).sizeof); - if (pthread_setspecific(_tlsKey, pary) != 0) - { - import core.stdc.stdio; - perror("pthread_setspecific failed with"); - assert(0); - } - } - return *pary; -} - -ref void[] getTLSBlockAlloc() nothrow @nogc -{ - auto pary = &getTLSBlock(); - if (!pary.length) - { - auto pbeg = cast(void*)&_tlsstart; - auto pend = cast(void*)&_tlsend; - auto p = .malloc(pend - pbeg); - memcpy(p, pbeg, pend - pbeg); - *pary = p[0 .. pend - pbeg]; - } - return *pary; -} - -__gshared SectionGroup _sections; - -extern(C) -{ - /* Symbols created by the compiler/linker and inserted into the - * object file that 'bracket' sections. - */ - extern __gshared - { - void* __start_deh; - void* __stop_deh; - void* __start_minfo; - void* __stop_minfo; - - size_t __bss_end__; - - int _tlsstart; - int _tlsend; - } -} diff --git a/libphobos/libdruntime/gcc/sections/common.d b/libphobos/libdruntime/gcc/sections/common.d new file mode 100644 index 0000000..85fdc0e --- /dev/null +++ b/libphobos/libdruntime/gcc/sections/common.d @@ -0,0 +1,39 @@ +// Contains various utility functions used by the runtime implementation. +// Copyright (C) 2019-2021 Free Software Foundation, Inc. + +// GCC is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3, or (at your option) any later +// version. + +// GCC is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +module gcc.sections.common; + +/** + * Asserts that the given condition is `true`. + * + * The assertion is independent from -release, by abort()ing. Regular assertions + * throw an AssertError and thus require an initialized GC, which might not be + * the case (yet or anymore) for the startup/shutdown code in this package + * (called by CRT ctors/dtors etc.). + */ +package(gcc) void safeAssert( + bool condition, scope string msg, scope string file = __FILE__, size_t line = __LINE__ +) nothrow @nogc @safe +{ + import core.internal.abort; + condition || abort(msg, file, line); +} diff --git a/libphobos/libdruntime/gcc/sections/elf.d b/libphobos/libdruntime/gcc/sections/elf.d new file mode 100644 index 0000000..8450aec --- /dev/null +++ b/libphobos/libdruntime/gcc/sections/elf.d @@ -0,0 +1,1073 @@ +// ELF-specific support for sections with shared libraries. +// Copyright (C) 2019-2021 Free Software Foundation, Inc. + +// GCC is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3, or (at your option) any later +// version. + +// GCC is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +module gcc.sections.elf; + +version (MIPS32) version = MIPS_Any; +version (MIPS64) version = MIPS_Any; +version (RISCV32) version = RISCV_Any; +version (RISCV64) version = RISCV_Any; +version (S390) version = IBMZ_Any; +version (SystemZ) version = IBMZ_Any; + +version (CRuntime_Glibc) enum SharedELF = true; +else version (CRuntime_Musl) enum SharedELF = true; +else version (FreeBSD) enum SharedELF = true; +else version (NetBSD) enum SharedELF = true; +else version (DragonFlyBSD) enum SharedELF = true; +else version (CRuntime_UClibc) enum SharedELF = true; +else version (Solaris) enum SharedELF = true; +else enum SharedELF = false; +static if (SharedELF): + +import core.memory; +import core.stdc.config; +import core.stdc.stdio; +import core.stdc.stdlib : calloc, exit, free, malloc, EXIT_FAILURE; +import core.stdc.string : strlen; +version (linux) +{ + import core.sys.linux.dlfcn; + import core.sys.linux.elf; + import core.sys.linux.link; +} +else version (FreeBSD) +{ + import core.sys.freebsd.dlfcn; + import core.sys.freebsd.sys.elf; + import core.sys.freebsd.sys.link_elf; +} +else version (NetBSD) +{ + import core.sys.netbsd.dlfcn; + import core.sys.netbsd.sys.elf; + import core.sys.netbsd.sys.link_elf; +} +else version (DragonFlyBSD) +{ + import core.sys.dragonflybsd.dlfcn; + import core.sys.dragonflybsd.sys.elf; + import core.sys.dragonflybsd.sys.link_elf; +} +else version (Solaris) +{ + import core.sys.solaris.dlfcn; + import core.sys.solaris.link; + import core.sys.solaris.sys.elf; + import core.sys.solaris.sys.link; +} +else +{ + static assert(0, "unimplemented"); +} +import core.sys.posix.pthread; +import rt.deh; +import rt.dmain2; +import rt.minfo; +import rt.util.container.array; +import rt.util.container.hashtab; +import gcc.builtins; +import gcc.config; +import gcc.sections.common; + +alias DSO SectionGroup; +struct DSO +{ + static int opApply(scope int delegate(ref DSO) dg) + { + foreach (dso; _loadedDSOs) + { + if (auto res = dg(*dso)) + return res; + } + return 0; + } + + static int opApplyReverse(scope int delegate(ref DSO) dg) + { + foreach_reverse (dso; _loadedDSOs) + { + if (auto res = dg(*dso)) + return res; + } + return 0; + } + + @property immutable(ModuleInfo*)[] modules() const nothrow @nogc + { + return _moduleGroup.modules; + } + + @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc + { + return _moduleGroup; + } + + @property inout(void[])[] gcRanges() inout nothrow @nogc + { + return _gcRanges[]; + } + +private: + + invariant() + { + safeAssert(_moduleGroup.modules.length > 0, "No modules for DSO."); + safeAssert(_tlsMod || !_tlsSize, "Inconsistent TLS fields for DSO."); + } + + ModuleGroup _moduleGroup; + Array!(void[]) _gcRanges; + size_t _tlsMod; + size_t _tlsSize; + + version (Shared) + { + Array!(void[]) _codeSegments; // array of code segments + Array!(DSO*) _deps; // D libraries needed by this DSO + void* _handle; // corresponding handle + } + + // get the TLS range for the executing thread + void[] tlsRange() const nothrow @nogc + { + return getTLSRange(_tlsMod, _tlsSize); + } +} + +/**** + * Boolean flag set to true while the runtime is initialized. + */ +__gshared bool _isRuntimeInitialized; + + +/**** + * Gets called on program startup just before GC is initialized. + */ +void initSections() nothrow @nogc +{ + _isRuntimeInitialized = true; +} + + +/*** + * Gets called on program shutdown just after GC is terminated. + */ +void finiSections() nothrow @nogc +{ + _isRuntimeInitialized = false; +} + +alias ScanDG = void delegate(void* pbeg, void* pend) nothrow; + +version (Shared) +{ + import gcc.sections : pinLoadedLibraries, unpinLoadedLibraries, + inheritLoadedLibraries, cleanupLoadedLibraries; + + /*** + * Called once per thread; returns array of thread local storage ranges + */ + Array!(ThreadDSO)* initTLSRanges() @nogc nothrow + { + return &_loadedDSOs(); + } + + void finiTLSRanges(Array!(ThreadDSO)* tdsos) @nogc nothrow + { + // Nothing to do here. tdsos used to point to the _loadedDSOs instance + // in the dying thread's TLS segment and as such is not valid anymore. + // The memory for the array contents was already reclaimed in + // cleanupLoadedLibraries(). + } + + void scanTLSRanges(Array!(ThreadDSO)* tdsos, scope ScanDG dg) nothrow + { + version (GNU_EMUTLS) + { + import gcc.emutls; + _d_emutls_scan(dg); + } + else + { + foreach (ref tdso; *tdsos) + dg(tdso._tlsRange.ptr, tdso._tlsRange.ptr + tdso._tlsRange.length); + } + } + + size_t sizeOfTLS() nothrow @nogc + { + auto tdsos = initTLSRanges(); + size_t sum; + foreach (ref tdso; *tdsos) + sum += tdso._tlsRange.length; + return sum; + } + + // interface for core.thread to inherit loaded libraries + pragma(mangle, gcc.sections.pinLoadedLibraries.mangleof) + void* pinLoadedLibraries() nothrow @nogc + { + auto res = cast(Array!(ThreadDSO)*)calloc(1, Array!(ThreadDSO).sizeof); + res.length = _loadedDSOs.length; + foreach (i, ref tdso; _loadedDSOs) + { + (*res)[i] = tdso; + if (tdso._addCnt) + { + // Increment the dlopen ref for explicitly loaded libraries to pin them. + const success = .dlopen(linkMapForHandle(tdso._pdso._handle).l_name, RTLD_LAZY) !is null; + safeAssert(success, "Failed to increment dlopen ref."); + (*res)[i]._addCnt = 1; // new array takes over the additional ref count + } + } + return res; + } + + pragma(mangle, gcc.sections.unpinLoadedLibraries.mangleof) + void unpinLoadedLibraries(void* p) nothrow @nogc + { + auto pary = cast(Array!(ThreadDSO)*)p; + // In case something failed we need to undo the pinning. + foreach (ref tdso; *pary) + { + if (tdso._addCnt) + { + auto handle = tdso._pdso._handle; + safeAssert(handle !is null, "Invalid library handle."); + .dlclose(handle); + } + } + pary.reset(); + .free(pary); + } + + // Called before TLS ctors are ran, copy over the loaded libraries + // of the parent thread. + pragma(mangle, gcc.sections.inheritLoadedLibraries.mangleof) + void inheritLoadedLibraries(void* p) nothrow @nogc + { + safeAssert(_loadedDSOs.empty, "DSOs have already been registered for this thread."); + _loadedDSOs.swap(*cast(Array!(ThreadDSO)*)p); + .free(p); + foreach (ref dso; _loadedDSOs) + { + // the copied _tlsRange corresponds to parent thread + dso.updateTLSRange(); + } + } + + // Called after all TLS dtors ran, decrements all remaining dlopen refs. + pragma(mangle, gcc.sections.cleanupLoadedLibraries.mangleof) + void cleanupLoadedLibraries() nothrow @nogc + { + foreach (ref tdso; _loadedDSOs) + { + if (tdso._addCnt == 0) continue; + + auto handle = tdso._pdso._handle; + safeAssert(handle !is null, "Invalid DSO handle."); + for (; tdso._addCnt > 0; --tdso._addCnt) + .dlclose(handle); + } + + // Free the memory for the array contents. + _loadedDSOs.reset(); + } +} +else +{ + /*** + * Called once per thread; returns array of thread local storage ranges + */ + Array!(void[])* initTLSRanges() nothrow @nogc + { + auto rngs = &_tlsRanges(); + if (rngs.empty) + { + foreach (ref pdso; _loadedDSOs) + rngs.insertBack(pdso.tlsRange()); + } + return rngs; + } + + void finiTLSRanges(Array!(void[])* rngs) nothrow @nogc + { + rngs.reset(); + } + + void scanTLSRanges(Array!(void[])* rngs, scope ScanDG dg) nothrow + { + version (GNU_EMUTLS) + { + import gcc.emutls; + _d_emutls_scan(dg); + } + else + { + foreach (rng; *rngs) + dg(rng.ptr, rng.ptr + rng.length); + } + } + + size_t sizeOfTLS() nothrow @nogc + { + auto rngs = initTLSRanges(); + size_t sum; + foreach (rng; *rngs) + sum += rng.length; + return sum; + } +} + +private: + +version (Shared) +{ + /* + * Array of thread local DSO metadata for all libraries loaded and + * initialized in this thread. + * + * Note: + * A newly spawned thread will inherit these libraries. + * Note: + * We use an array here to preserve the order of + * initialization. If that became a performance issue, we + * could use a hash table and enumerate the DSOs during + * loading so that the hash table values could be sorted when + * necessary. + */ + struct ThreadDSO + { + DSO* _pdso; + static if (_pdso.sizeof == 8) uint _refCnt, _addCnt; + else static if (_pdso.sizeof == 4) ushort _refCnt, _addCnt; + else static assert(0, "unimplemented"); + void[] _tlsRange; + alias _pdso this; + // update the _tlsRange for the executing thread + void updateTLSRange() nothrow @nogc + { + _tlsRange = _pdso.tlsRange(); + } + } + @property ref Array!(ThreadDSO) _loadedDSOs() @nogc nothrow { static Array!(ThreadDSO) x; return x; } + + /* + * Set to true during rt_loadLibrary/rt_unloadLibrary calls. + */ + bool _rtLoading; + + /* + * Hash table to map link_map* to corresponding DSO*. + * The hash table is protected by a Mutex. + */ + __gshared pthread_mutex_t _handleToDSOMutex; + @property ref HashTab!(void*, DSO*) _handleToDSO() @nogc nothrow { __gshared HashTab!(void*, DSO*) x; return x; } +} +else +{ + /* + * Static DSOs loaded by the runtime linker. This includes the + * executable. These can't be unloaded. + */ + @property ref Array!(DSO*) _loadedDSOs() @nogc nothrow { __gshared Array!(DSO*) x; return x; } + + /* + * Thread local array that contains TLS memory ranges for each + * library initialized in this thread. + */ + @property ref Array!(void[]) _tlsRanges() @nogc nothrow { static Array!(void[]) x; return x; } + + enum _rtLoading = false; +} + +/////////////////////////////////////////////////////////////////////////////// +// Compiler to runtime interface. +/////////////////////////////////////////////////////////////////////////////// + +/* + * This data structure is generated by the compiler, and then passed to + * _d_dso_registry(). + */ +struct CompilerDSOData +{ + size_t _version; // currently 1 + void** _slot; // can be used to store runtime data + immutable(object.ModuleInfo*)* _minfo_beg, _minfo_end; // array of modules in this object file +} + +T[] toRange(T)(T* beg, T* end) { return beg[0 .. end - beg]; } + +/* For each shared library and executable, the compiler generates code that + * sets up CompilerDSOData and calls _d_dso_registry(). + * A pointer to that code is inserted into both the .ctors and .dtors + * segment so it gets called by the loader on startup and shutdown. + */ +extern(C) void _d_dso_registry(CompilerDSOData* data) +{ + // only one supported currently + safeAssert(data._version >= 1, "Incompatible compiler-generated DSO data version."); + + // no backlink => register + if (*data._slot is null) + { + immutable firstDSO = _loadedDSOs.empty; + if (firstDSO) initLocks(); + + DSO* pdso = cast(DSO*).calloc(1, DSO.sizeof); + assert(typeid(DSO).initializer().ptr is null); + *data._slot = pdso; // store backlink in library record + + pdso._moduleGroup = ModuleGroup(toRange(data._minfo_beg, data._minfo_end)); + + dl_phdr_info info = void; + const headerFound = findDSOInfoForAddr(data._slot, &info); + safeAssert(headerFound, "Failed to find image header."); + + scanSegments(info, pdso); + + version (Shared) + { + auto handle = handleForAddr(data._slot); + + getDependencies(info, pdso._deps); + pdso._handle = handle; + setDSOForHandle(pdso, pdso._handle); + + if (!_rtLoading) + { + /* This DSO was not loaded by rt_loadLibrary which + * happens for all dependencies of an executable or + * the first dlopen call from a C program. + * In this case we add the DSO to the _loadedDSOs of this + * thread with a refCnt of 1 and call the TlsCtors. + */ + immutable ushort refCnt = 1, addCnt = 0; + _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt, pdso.tlsRange())); + } + } + else + { + foreach (p; _loadedDSOs) + safeAssert(p !is pdso, "DSO already registered."); + _loadedDSOs.insertBack(pdso); + _tlsRanges.insertBack(pdso.tlsRange()); + } + + // don't initialize modules before rt_init was called (see Bugzilla 11378) + if (_isRuntimeInitialized) + { + registerGCRanges(pdso); + // rt_loadLibrary will run tls ctors, so do this only for dlopen + immutable runTlsCtors = !_rtLoading; + runModuleConstructors(pdso, runTlsCtors); + } + } + // has backlink => unregister + else + { + DSO* pdso = cast(DSO*)*data._slot; + *data._slot = null; + + // don't finalizes modules after rt_term was called (see Bugzilla 11378) + if (_isRuntimeInitialized) + { + // rt_unloadLibrary already ran tls dtors, so do this only for dlclose + immutable runTlsDtors = !_rtLoading; + runModuleDestructors(pdso, runTlsDtors); + unregisterGCRanges(pdso); + // run finalizers after module dtors (same order as in rt_term) + version (Shared) runFinalizers(pdso); + } + + version (Shared) + { + if (!_rtLoading) + { + /* This DSO was not unloaded by rt_unloadLibrary so we + * have to remove it from _loadedDSOs here. + */ + foreach (i, ref tdso; _loadedDSOs) + { + if (tdso._pdso == pdso) + { + _loadedDSOs.remove(i); + break; + } + } + } + + unsetDSOForHandle(pdso, pdso._handle); + } + else + { + // static DSOs are unloaded in reverse order + safeAssert(pdso == _loadedDSOs.back, "DSO being unregistered isn't current last one."); + _loadedDSOs.popBack(); + } + + freeDSO(pdso); + + // last DSO being unloaded => shutdown registry + if (_loadedDSOs.empty) + { + version (Shared) + { + safeAssert(_handleToDSO.empty, "_handleToDSO not in sync with _loadedDSOs."); + _handleToDSO.reset(); + } + finiLocks(); + version (GNU_EMUTLS) + { + import gcc.emutls; + _d_emutls_destroy(); + } + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Dynamic loading +/////////////////////////////////////////////////////////////////////////////// + +// Shared D libraries are only supported when linking against a shared druntime library. + +version (Shared) +{ + ThreadDSO* findThreadDSO(DSO* pdso) nothrow @nogc + { + foreach (ref tdata; _loadedDSOs) + if (tdata._pdso == pdso) return &tdata; + return null; + } + + void incThreadRef(DSO* pdso, bool incAdd) + { + if (auto tdata = findThreadDSO(pdso)) // already initialized + { + if (incAdd && ++tdata._addCnt > 1) return; + ++tdata._refCnt; + } + else + { + foreach (dep; pdso._deps) + incThreadRef(dep, false); + immutable ushort refCnt = 1, addCnt = incAdd ? 1 : 0; + _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt, pdso.tlsRange())); + pdso._moduleGroup.runTlsCtors(); + } + } + + void decThreadRef(DSO* pdso, bool decAdd) + { + auto tdata = findThreadDSO(pdso); + safeAssert(tdata !is null, "Failed to find thread DSO."); + safeAssert(!decAdd || tdata._addCnt > 0, "Mismatching rt_unloadLibrary call."); + + if (decAdd && --tdata._addCnt > 0) return; + if (--tdata._refCnt > 0) return; + + pdso._moduleGroup.runTlsDtors(); + foreach (i, ref td; _loadedDSOs) + if (td._pdso == pdso) _loadedDSOs.remove(i); + foreach (dep; pdso._deps) + decThreadRef(dep, false); + } + + extern(C) void* rt_loadLibrary(const char* name) + { + immutable save = _rtLoading; + _rtLoading = true; + scope (exit) _rtLoading = save; + + auto handle = .dlopen(name, RTLD_LAZY); + if (handle is null) return null; + + // if it's a D library + if (auto pdso = dsoForHandle(handle)) + incThreadRef(pdso, true); + return handle; + } + + extern(C) int rt_unloadLibrary(void* handle) + { + if (handle is null) return false; + + immutable save = _rtLoading; + _rtLoading = true; + scope (exit) _rtLoading = save; + + // if it's a D library + if (auto pdso = dsoForHandle(handle)) + decThreadRef(pdso, true); + return .dlclose(handle) == 0; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Helper functions +/////////////////////////////////////////////////////////////////////////////// + +void initLocks() nothrow @nogc +{ + version (Shared) + !pthread_mutex_init(&_handleToDSOMutex, null) || assert(0); +} + +void finiLocks() nothrow @nogc +{ + version (Shared) + !pthread_mutex_destroy(&_handleToDSOMutex) || assert(0); +} + +void runModuleConstructors(DSO* pdso, bool runTlsCtors) +{ + pdso._moduleGroup.sortCtors(); + pdso._moduleGroup.runCtors(); + if (runTlsCtors) pdso._moduleGroup.runTlsCtors(); +} + +void runModuleDestructors(DSO* pdso, bool runTlsDtors) +{ + if (runTlsDtors) pdso._moduleGroup.runTlsDtors(); + pdso._moduleGroup.runDtors(); +} + +void registerGCRanges(DSO* pdso) nothrow @nogc +{ + foreach (rng; pdso._gcRanges) + GC.addRange(rng.ptr, rng.length); +} + +void unregisterGCRanges(DSO* pdso) nothrow @nogc +{ + foreach (rng; pdso._gcRanges) + GC.removeRange(rng.ptr); +} + +version (Shared) void runFinalizers(DSO* pdso) +{ + foreach (seg; pdso._codeSegments) + GC.runFinalizers(seg); +} + +void freeDSO(DSO* pdso) nothrow @nogc +{ + pdso._gcRanges.reset(); + version (Shared) + { + pdso._codeSegments.reset(); + pdso._deps.reset(); + pdso._handle = null; + } + .free(pdso); +} + +version (Shared) +{ +@nogc nothrow: + link_map* linkMapForHandle(void* handle) + { + link_map* map; + const success = dlinfo(handle, RTLD_DI_LINKMAP, &map) == 0; + safeAssert(success, "Failed to get DSO info."); + return map; + } + + link_map* exeLinkMap(link_map* map) + { + safeAssert(map !is null, "Invalid link_map."); + while (map.l_prev !is null) + map = map.l_prev; + return map; + } + + DSO* dsoForHandle(void* handle) + { + DSO* pdso; + !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); + if (auto ppdso = handle in _handleToDSO) + pdso = *ppdso; + !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); + return pdso; + } + + void setDSOForHandle(DSO* pdso, void* handle) + { + !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); + safeAssert(handle !in _handleToDSO, "DSO already registered."); + _handleToDSO[handle] = pdso; + !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); + } + + void unsetDSOForHandle(DSO* pdso, void* handle) + { + !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); + safeAssert(_handleToDSO[handle] == pdso, "Handle doesn't match registered DSO."); + _handleToDSO.remove(handle); + !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); + } + + void getDependencies(in ref dl_phdr_info info, ref Array!(DSO*) deps) + { + // get the entries of the .dynamic section + ElfW!"Dyn"[] dyns; + foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum]) + { + if (phdr.p_type == PT_DYNAMIC) + { + auto p = cast(ElfW!"Dyn"*)(info.dlpi_addr + (phdr.p_vaddr & ~(size_t.sizeof - 1))); + dyns = p[0 .. phdr.p_memsz / ElfW!"Dyn".sizeof]; + break; + } + } + // find the string table which contains the sonames + const(char)* strtab; + foreach (dyn; dyns) + { + if (dyn.d_tag == DT_STRTAB) + { + version (CRuntime_Musl) + strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate + else version (linux) + { + // This might change in future glibc releases (after 2.29) as dynamic sections + // are not required to be read-only on RISC-V. This was copy & pasted from MIPS + // while upstreaming RISC-V support. Otherwise MIPS is the only arch which sets + // in glibc: #define DL_RO_DYN_SECTION 1 + version (RISCV_Any) + strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate + else version (MIPS_Any) + strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate + else + strtab = cast(const(char)*)dyn.d_un.d_ptr; + } + else version (FreeBSD) + strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate + else version (NetBSD) + strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate + else version (DragonFlyBSD) + strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate + else version (Solaris) + strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate + else + static assert(0, "unimplemented"); + break; + } + } + foreach (dyn; dyns) + { + immutable tag = dyn.d_tag; + if (!(tag == DT_NEEDED || tag == DT_AUXILIARY || tag == DT_FILTER)) + continue; + + // soname of the dependency + auto name = strtab + dyn.d_un.d_val; + // get handle without loading the library + auto handle = handleForName(name); + // the runtime linker has already loaded all dependencies + safeAssert(handle !is null, "Failed to get library handle."); + // if it's a D library + if (auto pdso = dsoForHandle(handle)) + deps.insertBack(pdso); // append it to the dependencies + } + } + + void* handleForName(const char* name) + { + auto handle = .dlopen(name, RTLD_NOLOAD | RTLD_LAZY); + version (Solaris) { } + else if (handle !is null) .dlclose(handle); // drop reference count + return handle; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Elf program header iteration +/////////////////////////////////////////////////////////////////////////////// + +/************ + * Scan segments in Linux dl_phdr_info struct and store + * the TLS and writeable data segments in *pdso. + */ +void scanSegments(in ref dl_phdr_info info, DSO* pdso) nothrow @nogc +{ + foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum]) + { + switch (phdr.p_type) + { + case PT_LOAD: + if (phdr.p_flags & PF_W) // writeable data segment + { + auto beg = cast(void*)(info.dlpi_addr + (phdr.p_vaddr & ~(size_t.sizeof - 1))); + pdso._gcRanges.insertBack(beg[0 .. phdr.p_memsz]); + } + version (Shared) if (phdr.p_flags & PF_X) // code segment + { + auto beg = cast(void*)(info.dlpi_addr + (phdr.p_vaddr & ~(size_t.sizeof - 1))); + pdso._codeSegments.insertBack(beg[0 .. phdr.p_memsz]); + } + break; + + case PT_TLS: // TLS segment + version (GNU_EMUTLS) + { + } + else + { + safeAssert(!pdso._tlsSize, "Multiple TLS segments in image header."); + static if (OS_Have_Dlpi_Tls_Modid) + { + pdso._tlsMod = info.dlpi_tls_modid; + pdso._tlsSize = phdr.p_memsz; + } + else version (Solaris) + { + struct Rt_map + { + Link_map rt_public; + const char* rt_pathname; + c_ulong rt_padstart; + c_ulong rt_padimlen; + c_ulong rt_msize; + uint rt_flags; + uint rt_flags1; + c_ulong rt_tlsmodid; + } + + Rt_map* map; + version (Shared) + dlinfo(handleForName(info.dlpi_name), RTLD_DI_LINKMAP, &map); + else + dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &map); + // Until Solaris 11.4, tlsmodid for the executable is 0. + // Let it start at 1 as the rest of the code expects. + pdso._tlsMod = map.rt_tlsmodid + 1; + pdso._tlsSize = phdr.p_memsz; + } + else + { + pdso._tlsMod = 0; + pdso._tlsSize = 0; + } + } + break; + + default: + break; + } + } +} + +/************************** + * Input: + * result where the output is to be written; dl_phdr_info is an OS struct + * Returns: + * true if found, and *result is filled in + * References: + * http://linux.die.net/man/3/dl_iterate_phdr + */ +bool findDSOInfoForAddr(in void* addr, dl_phdr_info* result=null) nothrow @nogc +{ + version (linux) enum IterateManually = true; + else version (NetBSD) enum IterateManually = true; + else version (Solaris) enum IterateManually = true; + else enum IterateManually = false; + + static if (IterateManually) + { + static struct DG { const(void)* addr; dl_phdr_info* result; } + + extern(C) int callback(dl_phdr_info* info, size_t sz, void* arg) nothrow @nogc + { + auto p = cast(DG*)arg; + if (findSegmentForAddr(*info, p.addr)) + { + if (p.result !is null) *p.result = *info; + return 1; // break; + } + return 0; // continue iteration + } + + auto dg = DG(addr, result); + + /* OS function that walks through the list of an application's shared objects and + * calls 'callback' once for each object, until either all shared objects + * have been processed or 'callback' returns a nonzero value. + */ + return dl_iterate_phdr(&callback, &dg) != 0; + } + else version (FreeBSD) + { + return !!_rtld_addr_phdr(addr, result); + } + else version (DragonFlyBSD) + { + return !!_rtld_addr_phdr(addr, result); + } + else + static assert(0, "unimplemented"); +} + +/********************************* + * Determine if 'addr' lies within shared object 'info'. + * If so, return true and fill in 'result' with the corresponding ELF program header. + */ +bool findSegmentForAddr(in ref dl_phdr_info info, in void* addr, ElfW!"Phdr"* result=null) nothrow @nogc +{ + if (addr < cast(void*)info.dlpi_addr) // less than base address of object means quick reject + return false; + + foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum]) + { + auto beg = cast(void*)(info.dlpi_addr + phdr.p_vaddr); + if (cast(size_t)(addr - beg) < phdr.p_memsz) + { + if (result !is null) *result = phdr; + return true; + } + } + return false; +} + +/************************** + * Input: + * addr an internal address of a DSO + * Returns: + * the dlopen handle for that DSO or null if addr is not within a loaded DSO + */ +version (Shared) void* handleForAddr(void* addr) nothrow @nogc +{ + Dl_info info = void; + if (dladdr(addr, &info) != 0) + return handleForName(info.dli_fname); + return null; +} + +/////////////////////////////////////////////////////////////////////////////// +// TLS module helper +/////////////////////////////////////////////////////////////////////////////// + + +/* + * Returns: the TLS memory range for a given module and the calling + * thread or null if that module has no TLS. + * + * Note: This will cause the TLS memory to be eagerly allocated. + */ +struct tls_index +{ + version (CRuntime_Glibc) + { + // For x86_64, fields are of type uint64_t, this is important for x32 + // where tls_index would otherwise have the wrong size. + // See https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86_64/dl-tls.h + version (X86_64) + { + ulong ti_module; + ulong ti_offset; + } + else + { + c_ulong ti_module; + c_ulong ti_offset; + } + } + else + { + size_t ti_module; + size_t ti_offset; + } +} + +extern(C) void* __tls_get_addr(tls_index* ti) nothrow @nogc; +extern(C) void* __ibmz_get_tls_offset(tls_index *ti) nothrow @nogc; + +/* The dynamic thread vector (DTV) pointers may point 0x8000 past the start of + * each TLS block. This is at least true for PowerPC and Mips platforms. + * See: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/powerpc/dl-tls.h;h=f7cf6f96ebfb505abfd2f02be0ad0e833107c0cd;hb=HEAD#l34 + * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/mips/dl-tls.h;h=93a6dc050cb144b9f68b96fb3199c60f5b1fcd18;hb=HEAD#l32 + * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/riscv/dl-tls.h;h=ab2d860314de94c18812bc894ff6b3f55368f20f;hb=HEAD#l32 + */ +version (X86) + enum TLS_DTV_OFFSET = 0x0; +else version (X86_64) + enum TLS_DTV_OFFSET = 0x0; +else version (ARM) + enum TLS_DTV_OFFSET = 0x0; +else version (AArch64) + enum TLS_DTV_OFFSET = 0x0; +else version (RISCV32) + enum TLS_DTV_OFFSET = 0x800; +else version (RISCV64) + enum TLS_DTV_OFFSET = 0x800; +else version (HPPA) + enum TLS_DTV_OFFSET = 0x0; +else version (SPARC) + enum TLS_DTV_OFFSET = 0x0; +else version (SPARC64) + enum TLS_DTV_OFFSET = 0x0; +else version (PPC) + enum TLS_DTV_OFFSET = 0x8000; +else version (PPC64) + enum TLS_DTV_OFFSET = 0x8000; +else version (MIPS32) + enum TLS_DTV_OFFSET = 0x8000; +else version (MIPS64) + enum TLS_DTV_OFFSET = 0x8000; +else version (IBMZ_Any) + enum TLS_DTV_OFFSET = 0x0; +else + static assert( false, "Platform not supported." ); + +void[] getTLSRange(size_t mod, size_t sz) nothrow @nogc +{ + if (mod == 0) + return null; + + version (GNU_EMUTLS) + return null; // Handled in scanTLSRanges(). + else + { + version (Solaris) + { + static if (!OS_Have_Dlpi_Tls_Modid) + mod -= 1; + } + + // base offset + auto ti = tls_index(mod, 0); + version (CRuntime_Musl) + return (__tls_get_addr(&ti)-TLS_DTV_OFFSET)[0 .. sz]; + else version (IBMZ_Any) + { + // IBM Z only provides __tls_get_offset instead of __tls_get_addr + // which returns an offset relative to the thread pointer. + auto addr = __ibmz_get_tls_offset(&ti); + addr = addr + cast(c_ulong)__builtin_thread_pointer(); + return addr[0 .. sz]; + } + else + return (__tls_get_addr(&ti)-TLS_DTV_OFFSET)[0 .. sz]; + } +} diff --git a/libphobos/libdruntime/gcc/sections/elf_shared.d b/libphobos/libdruntime/gcc/sections/elf_shared.d deleted file mode 100644 index 5b0fad9..0000000 --- a/libphobos/libdruntime/gcc/sections/elf_shared.d +++ /dev/null @@ -1,1122 +0,0 @@ -// ELF-specific support for sections with shared libraries. -// Copyright (C) 2019-2021 Free Software Foundation, Inc. - -// GCC is free software; you can redistribute it and/or modify it under -// the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 3, or (at your option) any later -// version. - -// GCC is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. - -// Under Section 7 of GPL version 3, you are granted additional -// permissions described in the GCC Runtime Library Exception, version -// 3.1, as published by the Free Software Foundation. - -// You should have received a copy of the GNU General Public License and -// a copy of the GCC Runtime Library Exception along with this program; -// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -// . - -module gcc.sections.elf_shared; - -version (MIPS32) version = MIPS_Any; -version (MIPS64) version = MIPS_Any; -version (RISCV32) version = RISCV_Any; -version (RISCV64) version = RISCV_Any; -version (S390) version = IBMZ_Any; -version (SystemZ) version = IBMZ_Any; - -version (CRuntime_Glibc) enum SharedELF = true; -else version (CRuntime_Musl) enum SharedELF = true; -else version (FreeBSD) enum SharedELF = true; -else version (NetBSD) enum SharedELF = true; -else version (DragonFlyBSD) enum SharedELF = true; -else version (CRuntime_UClibc) enum SharedELF = true; -else version (Solaris) enum SharedELF = true; -else enum SharedELF = false; -static if (SharedELF): - -// debug = PRINTF; -import core.memory; -import core.stdc.config; -import core.stdc.stdio; -import core.stdc.stdlib : calloc, exit, free, malloc, EXIT_FAILURE; -import core.stdc.string : strlen; -version (linux) -{ - import core.sys.linux.dlfcn; - import core.sys.linux.elf; - import core.sys.linux.link; -} -else version (FreeBSD) -{ - import core.sys.freebsd.dlfcn; - import core.sys.freebsd.sys.elf; - import core.sys.freebsd.sys.link_elf; -} -else version (NetBSD) -{ - import core.sys.netbsd.dlfcn; - import core.sys.netbsd.sys.elf; - import core.sys.netbsd.sys.link_elf; -} -else version (DragonFlyBSD) -{ - import core.sys.dragonflybsd.dlfcn; - import core.sys.dragonflybsd.sys.elf; - import core.sys.dragonflybsd.sys.link_elf; -} -else version (Solaris) -{ - import core.sys.solaris.dlfcn; - import core.sys.solaris.link; - import core.sys.solaris.sys.elf; - import core.sys.solaris.sys.link; -} -else -{ - static assert(0, "unimplemented"); -} -import core.sys.posix.pthread; -import gcc.builtins; -import gcc.config; -import rt.deh; -import rt.dmain2; -import rt.minfo; -import rt.util.container.array; -import rt.util.container.hashtab; - -/**** - * Asserts the specified condition, independent from -release, by abort()ing. - * Regular assertions throw an AssertError and thus require an initialized - * GC, which isn't the case (yet or anymore) for the startup/shutdown code in - * this module (called by CRT ctors/dtors etc.). - */ -private void safeAssert(bool condition, scope string msg, size_t line = __LINE__) @nogc nothrow @safe -{ - import core.internal.abort; - condition || abort(msg, __FILE__, line); -} - -alias DSO SectionGroup; -struct DSO -{ - static int opApply(scope int delegate(ref DSO) dg) - { - foreach (dso; _loadedDSOs) - { - if (auto res = dg(*dso)) - return res; - } - return 0; - } - - static int opApplyReverse(scope int delegate(ref DSO) dg) - { - foreach_reverse (dso; _loadedDSOs) - { - if (auto res = dg(*dso)) - return res; - } - return 0; - } - - @property immutable(ModuleInfo*)[] modules() const nothrow @nogc - { - return _moduleGroup.modules; - } - - @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc - { - return _moduleGroup; - } - - @property immutable(FuncTable)[] ehTables() const nothrow @nogc - { - return null; - } - - @property inout(void[])[] gcRanges() inout nothrow @nogc - { - return _gcRanges[]; - } - -private: - - invariant() - { - safeAssert(_moduleGroup.modules.length > 0, "No modules for DSO."); - safeAssert(_tlsMod || !_tlsSize, "Inconsistent TLS fields for DSO."); - } - - ModuleGroup _moduleGroup; - Array!(void[]) _gcRanges; - size_t _tlsMod; - size_t _tlsSize; - - version (Shared) - { - Array!(void[]) _codeSegments; // array of code segments - Array!(DSO*) _deps; // D libraries needed by this DSO - void* _handle; // corresponding handle - } - - // get the TLS range for the executing thread - void[] tlsRange() const nothrow @nogc - { - return getTLSRange(_tlsMod, _tlsSize); - } -} - -/**** - * Boolean flag set to true while the runtime is initialized. - */ -__gshared bool _isRuntimeInitialized; - - -version (FreeBSD) private __gshared void* dummy_ref; -version (DragonFlyBSD) private __gshared void* dummy_ref; -version (NetBSD) private __gshared void* dummy_ref; -version (Solaris) private __gshared void* dummy_ref; - -/**** - * Gets called on program startup just before GC is initialized. - */ -void initSections() nothrow @nogc -{ - _isRuntimeInitialized = true; - // reference symbol to support weak linkage - version (FreeBSD) dummy_ref = &_d_dso_registry; - version (DragonFlyBSD) dummy_ref = &_d_dso_registry; - version (NetBSD) dummy_ref = &_d_dso_registry; - version (Solaris) dummy_ref = &_d_dso_registry; -} - - -/*** - * Gets called on program shutdown just after GC is terminated. - */ -void finiSections() nothrow @nogc -{ - _isRuntimeInitialized = false; -} - -alias ScanDG = void delegate(void* pbeg, void* pend) nothrow; - -version (Shared) -{ - /*** - * Called once per thread; returns array of thread local storage ranges - */ - Array!(ThreadDSO)* initTLSRanges() @nogc nothrow - { - return &_loadedDSOs(); - } - - void finiTLSRanges(Array!(ThreadDSO)* tdsos) @nogc nothrow - { - // Nothing to do here. tdsos used to point to the _loadedDSOs instance - // in the dying thread's TLS segment and as such is not valid anymore. - // The memory for the array contents was already reclaimed in - // cleanupLoadedLibraries(). - } - - void scanTLSRanges(Array!(ThreadDSO)* tdsos, scope ScanDG dg) nothrow - { - version (GNU_EMUTLS) - { - import gcc.emutls; - _d_emutls_scan(dg); - } - else - { - foreach (ref tdso; *tdsos) - dg(tdso._tlsRange.ptr, tdso._tlsRange.ptr + tdso._tlsRange.length); - } - } - - size_t sizeOfTLS() nothrow @nogc - { - auto tdsos = initTLSRanges(); - size_t sum; - foreach (ref tdso; *tdsos) - sum += tdso._tlsRange.length; - return sum; - } - - // interface for core.thread to inherit loaded libraries - void* pinLoadedLibraries() nothrow @nogc - { - auto res = cast(Array!(ThreadDSO)*)calloc(1, Array!(ThreadDSO).sizeof); - res.length = _loadedDSOs.length; - foreach (i, ref tdso; _loadedDSOs) - { - (*res)[i] = tdso; - if (tdso._addCnt) - { - // Increment the dlopen ref for explicitly loaded libraries to pin them. - const success = .dlopen(linkMapForHandle(tdso._pdso._handle).l_name, RTLD_LAZY) !is null; - safeAssert(success, "Failed to increment dlopen ref."); - (*res)[i]._addCnt = 1; // new array takes over the additional ref count - } - } - return res; - } - - void unpinLoadedLibraries(void* p) nothrow @nogc - { - auto pary = cast(Array!(ThreadDSO)*)p; - // In case something failed we need to undo the pinning. - foreach (ref tdso; *pary) - { - if (tdso._addCnt) - { - auto handle = tdso._pdso._handle; - safeAssert(handle !is null, "Invalid library handle."); - .dlclose(handle); - } - } - pary.reset(); - .free(pary); - } - - // Called before TLS ctors are ran, copy over the loaded libraries - // of the parent thread. - void inheritLoadedLibraries(void* p) nothrow @nogc - { - safeAssert(_loadedDSOs.empty, "DSOs have already been registered for this thread."); - _loadedDSOs.swap(*cast(Array!(ThreadDSO)*)p); - .free(p); - foreach (ref dso; _loadedDSOs) - { - // the copied _tlsRange corresponds to parent thread - dso.updateTLSRange(); - } - } - - // Called after all TLS dtors ran, decrements all remaining dlopen refs. - void cleanupLoadedLibraries() nothrow @nogc - { - foreach (ref tdso; _loadedDSOs) - { - if (tdso._addCnt == 0) continue; - - auto handle = tdso._pdso._handle; - safeAssert(handle !is null, "Invalid DSO handle."); - for (; tdso._addCnt > 0; --tdso._addCnt) - .dlclose(handle); - } - - // Free the memory for the array contents. - _loadedDSOs.reset(); - } -} -else -{ - /*** - * Called once per thread; returns array of thread local storage ranges - */ - Array!(void[])* initTLSRanges() nothrow @nogc - { - auto rngs = &_tlsRanges(); - if (rngs.empty) - { - foreach (ref pdso; _loadedDSOs) - rngs.insertBack(pdso.tlsRange()); - } - return rngs; - } - - void finiTLSRanges(Array!(void[])* rngs) nothrow @nogc - { - rngs.reset(); - } - - void scanTLSRanges(Array!(void[])* rngs, scope ScanDG dg) nothrow - { - version (GNU_EMUTLS) - { - import gcc.emutls; - _d_emutls_scan(dg); - } - else - { - foreach (rng; *rngs) - dg(rng.ptr, rng.ptr + rng.length); - } - } - - size_t sizeOfTLS() nothrow @nogc - { - auto rngs = initTLSRanges(); - size_t sum; - foreach (rng; *rngs) - sum += rng.length; - return sum; - } -} - -private: - -version (Shared) -{ - /* - * Array of thread local DSO metadata for all libraries loaded and - * initialized in this thread. - * - * Note: - * A newly spawned thread will inherit these libraries. - * Note: - * We use an array here to preserve the order of - * initialization. If that became a performance issue, we - * could use a hash table and enumerate the DSOs during - * loading so that the hash table values could be sorted when - * necessary. - */ - struct ThreadDSO - { - DSO* _pdso; - static if (_pdso.sizeof == 8) uint _refCnt, _addCnt; - else static if (_pdso.sizeof == 4) ushort _refCnt, _addCnt; - else static assert(0, "unimplemented"); - void[] _tlsRange; - alias _pdso this; - // update the _tlsRange for the executing thread - void updateTLSRange() nothrow @nogc - { - _tlsRange = _pdso.tlsRange(); - } - } - @property ref Array!(ThreadDSO) _loadedDSOs() @nogc nothrow { static Array!(ThreadDSO) x; return x; } - - /* - * Set to true during rt_loadLibrary/rt_unloadLibrary calls. - */ - bool _rtLoading; - - /* - * Hash table to map link_map* to corresponding DSO*. - * The hash table is protected by a Mutex. - */ - __gshared pthread_mutex_t _handleToDSOMutex; - @property ref HashTab!(void*, DSO*) _handleToDSO() @nogc nothrow { __gshared HashTab!(void*, DSO*) x; return x; } - - /* - * Section in executable that contains copy relocations. - * Might be null when druntime is dynamically loaded by a C host. - */ - __gshared const(void)[] _copyRelocSection; -} -else -{ - /* - * Static DSOs loaded by the runtime linker. This includes the - * executable. These can't be unloaded. - */ - @property ref Array!(DSO*) _loadedDSOs() @nogc nothrow { __gshared Array!(DSO*) x; return x; } - - /* - * Thread local array that contains TLS memory ranges for each - * library initialized in this thread. - */ - @property ref Array!(void[]) _tlsRanges() @nogc nothrow { static Array!(void[]) x; return x; } - - enum _rtLoading = false; -} - -/////////////////////////////////////////////////////////////////////////////// -// Compiler to runtime interface. -/////////////////////////////////////////////////////////////////////////////// - -/* - * This data structure is generated by the compiler, and then passed to - * _d_dso_registry(). - */ -struct CompilerDSOData -{ - size_t _version; // currently 1 - void** _slot; // can be used to store runtime data - immutable(object.ModuleInfo*)* _minfo_beg, _minfo_end; // array of modules in this object file -} - -T[] toRange(T)(T* beg, T* end) { return beg[0 .. end - beg]; } - -/* For each shared library and executable, the compiler generates code that - * sets up CompilerDSOData and calls _d_dso_registry(). - * A pointer to that code is inserted into both the .ctors and .dtors - * segment so it gets called by the loader on startup and shutdown. - */ -extern(C) void _d_dso_registry(CompilerDSOData* data) -{ - // only one supported currently - safeAssert(data._version >= 1, "Incompatible compiler-generated DSO data version."); - - // no backlink => register - if (*data._slot is null) - { - immutable firstDSO = _loadedDSOs.empty; - if (firstDSO) initLocks(); - - DSO* pdso = cast(DSO*).calloc(1, DSO.sizeof); - assert(typeid(DSO).initializer().ptr is null); - *data._slot = pdso; // store backlink in library record - - pdso._moduleGroup = ModuleGroup(toRange(data._minfo_beg, data._minfo_end)); - - dl_phdr_info info = void; - const headerFound = findDSOInfoForAddr(data._slot, &info); - safeAssert(headerFound, "Failed to find image header."); - - scanSegments(info, pdso); - - version (Shared) - { - auto handle = handleForAddr(data._slot); - - getDependencies(info, pdso._deps); - pdso._handle = handle; - setDSOForHandle(pdso, pdso._handle); - - if (!_rtLoading) - { - /* This DSO was not loaded by rt_loadLibrary which - * happens for all dependencies of an executable or - * the first dlopen call from a C program. - * In this case we add the DSO to the _loadedDSOs of this - * thread with a refCnt of 1 and call the TlsCtors. - */ - immutable ushort refCnt = 1, addCnt = 0; - _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt, pdso.tlsRange())); - } - } - else - { - foreach (p; _loadedDSOs) - safeAssert(p !is pdso, "DSO already registered."); - _loadedDSOs.insertBack(pdso); - _tlsRanges.insertBack(pdso.tlsRange()); - } - - // don't initialize modules before rt_init was called (see Bugzilla 11378) - if (_isRuntimeInitialized) - { - registerGCRanges(pdso); - // rt_loadLibrary will run tls ctors, so do this only for dlopen - immutable runTlsCtors = !_rtLoading; - runModuleConstructors(pdso, runTlsCtors); - } - } - // has backlink => unregister - else - { - DSO* pdso = cast(DSO*)*data._slot; - *data._slot = null; - - // don't finalizes modules after rt_term was called (see Bugzilla 11378) - if (_isRuntimeInitialized) - { - // rt_unloadLibrary already ran tls dtors, so do this only for dlclose - immutable runTlsDtors = !_rtLoading; - runModuleDestructors(pdso, runTlsDtors); - unregisterGCRanges(pdso); - // run finalizers after module dtors (same order as in rt_term) - version (Shared) runFinalizers(pdso); - } - - version (Shared) - { - if (!_rtLoading) - { - /* This DSO was not unloaded by rt_unloadLibrary so we - * have to remove it from _loadedDSOs here. - */ - foreach (i, ref tdso; _loadedDSOs) - { - if (tdso._pdso == pdso) - { - _loadedDSOs.remove(i); - break; - } - } - } - - unsetDSOForHandle(pdso, pdso._handle); - } - else - { - // static DSOs are unloaded in reverse order - safeAssert(pdso == _loadedDSOs.back, "DSO being unregistered isn't current last one."); - _loadedDSOs.popBack(); - } - - freeDSO(pdso); - - // last DSO being unloaded => shutdown registry - if (_loadedDSOs.empty) - { - version (Shared) - { - safeAssert(_handleToDSO.empty, "_handleToDSO not in sync with _loadedDSOs."); - _handleToDSO.reset(); - } - finiLocks(); - version (GNU_EMUTLS) - { - import gcc.emutls; - _d_emutls_destroy(); - } - } - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Dynamic loading -/////////////////////////////////////////////////////////////////////////////// - -// Shared D libraries are only supported when linking against a shared druntime library. - -version (Shared) -{ - ThreadDSO* findThreadDSO(DSO* pdso) nothrow @nogc - { - foreach (ref tdata; _loadedDSOs) - if (tdata._pdso == pdso) return &tdata; - return null; - } - - void incThreadRef(DSO* pdso, bool incAdd) - { - if (auto tdata = findThreadDSO(pdso)) // already initialized - { - if (incAdd && ++tdata._addCnt > 1) return; - ++tdata._refCnt; - } - else - { - foreach (dep; pdso._deps) - incThreadRef(dep, false); - immutable ushort refCnt = 1, addCnt = incAdd ? 1 : 0; - _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt, pdso.tlsRange())); - pdso._moduleGroup.runTlsCtors(); - } - } - - void decThreadRef(DSO* pdso, bool decAdd) - { - auto tdata = findThreadDSO(pdso); - safeAssert(tdata !is null, "Failed to find thread DSO."); - safeAssert(!decAdd || tdata._addCnt > 0, "Mismatching rt_unloadLibrary call."); - - if (decAdd && --tdata._addCnt > 0) return; - if (--tdata._refCnt > 0) return; - - pdso._moduleGroup.runTlsDtors(); - foreach (i, ref td; _loadedDSOs) - if (td._pdso == pdso) _loadedDSOs.remove(i); - foreach (dep; pdso._deps) - decThreadRef(dep, false); - } - - extern(C) void* rt_loadLibrary(const char* name) - { - immutable save = _rtLoading; - _rtLoading = true; - scope (exit) _rtLoading = save; - - auto handle = .dlopen(name, RTLD_LAZY); - if (handle is null) return null; - - // if it's a D library - if (auto pdso = dsoForHandle(handle)) - incThreadRef(pdso, true); - return handle; - } - - extern(C) int rt_unloadLibrary(void* handle) - { - if (handle is null) return false; - - immutable save = _rtLoading; - _rtLoading = true; - scope (exit) _rtLoading = save; - - // if it's a D library - if (auto pdso = dsoForHandle(handle)) - decThreadRef(pdso, true); - return .dlclose(handle) == 0; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Helper functions -/////////////////////////////////////////////////////////////////////////////// - -void initLocks() nothrow @nogc -{ - version (Shared) - !pthread_mutex_init(&_handleToDSOMutex, null) || assert(0); -} - -void finiLocks() nothrow @nogc -{ - version (Shared) - !pthread_mutex_destroy(&_handleToDSOMutex) || assert(0); -} - -void runModuleConstructors(DSO* pdso, bool runTlsCtors) -{ - pdso._moduleGroup.sortCtors(); - pdso._moduleGroup.runCtors(); - if (runTlsCtors) pdso._moduleGroup.runTlsCtors(); -} - -void runModuleDestructors(DSO* pdso, bool runTlsDtors) -{ - if (runTlsDtors) pdso._moduleGroup.runTlsDtors(); - pdso._moduleGroup.runDtors(); -} - -void registerGCRanges(DSO* pdso) nothrow @nogc -{ - foreach (rng; pdso._gcRanges) - GC.addRange(rng.ptr, rng.length); -} - -void unregisterGCRanges(DSO* pdso) nothrow @nogc -{ - foreach (rng; pdso._gcRanges) - GC.removeRange(rng.ptr); -} - -version (Shared) void runFinalizers(DSO* pdso) -{ - foreach (seg; pdso._codeSegments) - GC.runFinalizers(seg); -} - -void freeDSO(DSO* pdso) nothrow @nogc -{ - pdso._gcRanges.reset(); - version (Shared) - { - pdso._codeSegments.reset(); - pdso._deps.reset(); - pdso._handle = null; - } - .free(pdso); -} - -version (Shared) -{ -@nogc nothrow: - link_map* linkMapForHandle(void* handle) - { - link_map* map; - const success = dlinfo(handle, RTLD_DI_LINKMAP, &map) == 0; - safeAssert(success, "Failed to get DSO info."); - return map; - } - - link_map* exeLinkMap(link_map* map) - { - safeAssert(map !is null, "Invalid link_map."); - while (map.l_prev !is null) - map = map.l_prev; - return map; - } - - DSO* dsoForHandle(void* handle) - { - DSO* pdso; - !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); - if (auto ppdso = handle in _handleToDSO) - pdso = *ppdso; - !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); - return pdso; - } - - void setDSOForHandle(DSO* pdso, void* handle) - { - !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); - safeAssert(handle !in _handleToDSO, "DSO already registered."); - _handleToDSO[handle] = pdso; - !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); - } - - void unsetDSOForHandle(DSO* pdso, void* handle) - { - !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); - safeAssert(_handleToDSO[handle] == pdso, "Handle doesn't match registered DSO."); - _handleToDSO.remove(handle); - !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); - } - - void getDependencies(in ref dl_phdr_info info, ref Array!(DSO*) deps) - { - // get the entries of the .dynamic section - ElfW!"Dyn"[] dyns; - foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum]) - { - if (phdr.p_type == PT_DYNAMIC) - { - auto p = cast(ElfW!"Dyn"*)(info.dlpi_addr + (phdr.p_vaddr & ~(size_t.sizeof - 1))); - dyns = p[0 .. phdr.p_memsz / ElfW!"Dyn".sizeof]; - break; - } - } - // find the string table which contains the sonames - const(char)* strtab; - foreach (dyn; dyns) - { - if (dyn.d_tag == DT_STRTAB) - { - version (CRuntime_Musl) - strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate - else version (linux) - { - // This might change in future glibc releases (after 2.29) as dynamic sections - // are not required to be read-only on RISC-V. This was copy & pasted from MIPS - // while upstreaming RISC-V support. Otherwise MIPS is the only arch which sets - // in glibc: #define DL_RO_DYN_SECTION 1 - version (RISCV_Any) - strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate - else version (MIPS_Any) - strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate - else - strtab = cast(const(char)*)dyn.d_un.d_ptr; - } - else version (FreeBSD) - strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate - else version (NetBSD) - strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate - else version (DragonFlyBSD) - strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate - else version (Solaris) - strtab = cast(const(char)*)(info.dlpi_addr + dyn.d_un.d_ptr); // relocate - else - static assert(0, "unimplemented"); - break; - } - } - foreach (dyn; dyns) - { - immutable tag = dyn.d_tag; - if (!(tag == DT_NEEDED || tag == DT_AUXILIARY || tag == DT_FILTER)) - continue; - - // soname of the dependency - auto name = strtab + dyn.d_un.d_val; - // get handle without loading the library - auto handle = handleForName(name); - // the runtime linker has already loaded all dependencies - safeAssert(handle !is null, "Failed to get library handle."); - // if it's a D library - if (auto pdso = dsoForHandle(handle)) - deps.insertBack(pdso); // append it to the dependencies - } - } - - void* handleForName(const char* name) - { - auto handle = .dlopen(name, RTLD_NOLOAD | RTLD_LAZY); - version (Solaris) { } - else if (handle !is null) .dlclose(handle); // drop reference count - return handle; - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Elf program header iteration -/////////////////////////////////////////////////////////////////////////////// - -/************ - * Scan segments in Linux dl_phdr_info struct and store - * the TLS and writeable data segments in *pdso. - */ -void scanSegments(in ref dl_phdr_info info, DSO* pdso) nothrow @nogc -{ - foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum]) - { - switch (phdr.p_type) - { - case PT_LOAD: - if (phdr.p_flags & PF_W) // writeable data segment - { - auto beg = cast(void*)(info.dlpi_addr + (phdr.p_vaddr & ~(size_t.sizeof - 1))); - pdso._gcRanges.insertBack(beg[0 .. phdr.p_memsz]); - } - version (Shared) if (phdr.p_flags & PF_X) // code segment - { - auto beg = cast(void*)(info.dlpi_addr + (phdr.p_vaddr & ~(size_t.sizeof - 1))); - pdso._codeSegments.insertBack(beg[0 .. phdr.p_memsz]); - } - break; - - case PT_TLS: // TLS segment - version (GNU_EMUTLS) - { - } - else - { - safeAssert(!pdso._tlsSize, "Multiple TLS segments in image header."); - static if (OS_Have_Dlpi_Tls_Modid) - { - pdso._tlsMod = info.dlpi_tls_modid; - pdso._tlsSize = phdr.p_memsz; - } - else version (Solaris) - { - struct Rt_map - { - Link_map rt_public; - const char* rt_pathname; - c_ulong rt_padstart; - c_ulong rt_padimlen; - c_ulong rt_msize; - uint rt_flags; - uint rt_flags1; - c_ulong rt_tlsmodid; - } - - Rt_map* map; - version (Shared) - dlinfo(handleForName(info.dlpi_name), RTLD_DI_LINKMAP, &map); - else - dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &map); - // Until Solaris 11.4, tlsmodid for the executable is 0. - // Let it start at 1 as the rest of the code expects. - pdso._tlsMod = map.rt_tlsmodid + 1; - pdso._tlsSize = phdr.p_memsz; - } - else - { - pdso._tlsMod = 0; - pdso._tlsSize = 0; - } - } - break; - - default: - break; - } - } -} - -/************************** - * Input: - * result where the output is to be written; dl_phdr_info is an OS struct - * Returns: - * true if found, and *result is filled in - * References: - * http://linux.die.net/man/3/dl_iterate_phdr - */ -bool findDSOInfoForAddr(in void* addr, dl_phdr_info* result=null) nothrow @nogc -{ - version (linux) enum IterateManually = true; - else version (NetBSD) enum IterateManually = true; - else version (Solaris) enum IterateManually = true; - else enum IterateManually = false; - - static if (IterateManually) - { - static struct DG { const(void)* addr; dl_phdr_info* result; } - - extern(C) int callback(dl_phdr_info* info, size_t sz, void* arg) nothrow @nogc - { - auto p = cast(DG*)arg; - if (findSegmentForAddr(*info, p.addr)) - { - if (p.result !is null) *p.result = *info; - return 1; // break; - } - return 0; // continue iteration - } - - auto dg = DG(addr, result); - - /* OS function that walks through the list of an application's shared objects and - * calls 'callback' once for each object, until either all shared objects - * have been processed or 'callback' returns a nonzero value. - */ - return dl_iterate_phdr(&callback, &dg) != 0; - } - else version (FreeBSD) - { - return !!_rtld_addr_phdr(addr, result); - } - else version (DragonFlyBSD) - { - return !!_rtld_addr_phdr(addr, result); - } - else - static assert(0, "unimplemented"); -} - -/********************************* - * Determine if 'addr' lies within shared object 'info'. - * If so, return true and fill in 'result' with the corresponding ELF program header. - */ -bool findSegmentForAddr(in ref dl_phdr_info info, in void* addr, ElfW!"Phdr"* result=null) nothrow @nogc -{ - if (addr < cast(void*)info.dlpi_addr) // less than base address of object means quick reject - return false; - - foreach (ref phdr; info.dlpi_phdr[0 .. info.dlpi_phnum]) - { - auto beg = cast(void*)(info.dlpi_addr + phdr.p_vaddr); - if (cast(size_t)(addr - beg) < phdr.p_memsz) - { - if (result !is null) *result = phdr; - return true; - } - } - return false; -} - -version (linux) import core.sys.linux.errno : program_invocation_name; -// should be in core.sys.freebsd.stdlib -version (FreeBSD) extern(C) const(char)* getprogname() nothrow @nogc; -version (DragonFlyBSD) extern(C) const(char)* getprogname() nothrow @nogc; -version (NetBSD) extern(C) const(char)* getprogname() nothrow @nogc; -version (Solaris) extern(C) const(char)* getprogname() nothrow @nogc; - -@property const(char)* progname() nothrow @nogc -{ - version (linux) return program_invocation_name; - version (FreeBSD) return getprogname(); - version (DragonFlyBSD) return getprogname(); - version (NetBSD) return getprogname(); - version (Solaris) return getprogname(); -} - -const(char)[] dsoName(const char* dlpi_name) nothrow @nogc -{ - // the main executable doesn't have a name in its dlpi_name field - const char* p = dlpi_name[0] != 0 ? dlpi_name : progname; - return p[0 .. strlen(p)]; -} - -/************************** - * Input: - * addr an internal address of a DSO - * Returns: - * the dlopen handle for that DSO or null if addr is not within a loaded DSO - */ -version (Shared) void* handleForAddr(void* addr) nothrow @nogc -{ - Dl_info info = void; - if (dladdr(addr, &info) != 0) - return handleForName(info.dli_fname); - return null; -} - -/////////////////////////////////////////////////////////////////////////////// -// TLS module helper -/////////////////////////////////////////////////////////////////////////////// - - -/* - * Returns: the TLS memory range for a given module and the calling - * thread or null if that module has no TLS. - * - * Note: This will cause the TLS memory to be eagerly allocated. - */ -struct tls_index -{ - version (CRuntime_Glibc) - { - // For x86_64, fields are of type uint64_t, this is important for x32 - // where tls_index would otherwise have the wrong size. - // See https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/x86_64/dl-tls.h - version (X86_64) - { - ulong ti_module; - ulong ti_offset; - } - else - { - c_ulong ti_module; - c_ulong ti_offset; - } - } - else - { - size_t ti_module; - size_t ti_offset; - } -} - -extern(C) void* __tls_get_addr(tls_index* ti) nothrow @nogc; -extern(C) void* __ibmz_get_tls_offset(tls_index *ti) nothrow @nogc; - -/* The dynamic thread vector (DTV) pointers may point 0x8000 past the start of - * each TLS block. This is at least true for PowerPC and Mips platforms. - * See: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/powerpc/dl-tls.h;h=f7cf6f96ebfb505abfd2f02be0ad0e833107c0cd;hb=HEAD#l34 - * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/mips/dl-tls.h;h=93a6dc050cb144b9f68b96fb3199c60f5b1fcd18;hb=HEAD#l32 - * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/riscv/dl-tls.h;h=ab2d860314de94c18812bc894ff6b3f55368f20f;hb=HEAD#l32 - */ -version (X86) - enum TLS_DTV_OFFSET = 0x0; -else version (X86_64) - enum TLS_DTV_OFFSET = 0x0; -else version (ARM) - enum TLS_DTV_OFFSET = 0x0; -else version (AArch64) - enum TLS_DTV_OFFSET = 0x0; -else version (RISCV32) - enum TLS_DTV_OFFSET = 0x800; -else version (RISCV64) - enum TLS_DTV_OFFSET = 0x800; -else version (HPPA) - enum TLS_DTV_OFFSET = 0x0; -else version (SPARC) - enum TLS_DTV_OFFSET = 0x0; -else version (SPARC64) - enum TLS_DTV_OFFSET = 0x0; -else version (PPC) - enum TLS_DTV_OFFSET = 0x8000; -else version (PPC64) - enum TLS_DTV_OFFSET = 0x8000; -else version (MIPS32) - enum TLS_DTV_OFFSET = 0x8000; -else version (MIPS64) - enum TLS_DTV_OFFSET = 0x8000; -else version (IBMZ_Any) - enum TLS_DTV_OFFSET = 0x0; -else - static assert( false, "Platform not supported." ); - -void[] getTLSRange(size_t mod, size_t sz) nothrow @nogc -{ - if (mod == 0) - return null; - - version (GNU_EMUTLS) - return null; // Handled in scanTLSRanges(). - else - { - version (Solaris) - { - static if (!OS_Have_Dlpi_Tls_Modid) - mod -= 1; - } - - // base offset - auto ti = tls_index(mod, 0); - version (CRuntime_Musl) - return (__tls_get_addr(&ti)-TLS_DTV_OFFSET)[0 .. sz]; - else version (IBMZ_Any) - { - // IBM Z only provides __tls_get_offset instead of __tls_get_addr - // which returns an offset relative to the thread pointer. - auto addr = __ibmz_get_tls_offset(&ti); - addr = addr + cast(c_ulong)__builtin_thread_pointer(); - return addr[0 .. sz]; - } - else - return (__tls_get_addr(&ti)-TLS_DTV_OFFSET)[0 .. sz]; - } -} diff --git a/libphobos/libdruntime/gcc/sections/macho.d b/libphobos/libdruntime/gcc/sections/macho.d new file mode 100644 index 0000000..3ce58a53 --- /dev/null +++ b/libphobos/libdruntime/gcc/sections/macho.d @@ -0,0 +1,738 @@ +// MACHO-specific support for sections. +// Copyright (C) 2021 Free Software Foundation, Inc. + +// GCC is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3, or (at your option) any later +// version. + +// GCC is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +module gcc.sections.macho; + +version (OSX): + +import core.memory; +import core.stdc.stdlib; +import core.sys.darwin.dlfcn; +import core.sys.darwin.mach.dyld; +import core.sys.darwin.mach.getsect; +import core.sys.posix.pthread; +import rt.minfo; +import rt.util.container.array; +import rt.util.container.hashtab; +import gcc.sections.common; + +version (GNU_EMUTLS) + import gcc.emutls; + +alias DSO SectionGroup; +struct DSO +{ + static int opApply(scope int delegate(ref DSO) dg) + { + foreach (dso; _loadedDSOs) + { + if (auto res = dg(*dso)) + return res; + } + return 0; + } + + static int opApplyReverse(scope int delegate(ref DSO) dg) + { + foreach_reverse (dso; _loadedDSOs) + { + if (auto res = dg(*dso)) + return res; + } + return 0; + } + + @property immutable(ModuleInfo*)[] modules() const nothrow @nogc + { + return _moduleGroup.modules; + } + + @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc + { + return _moduleGroup; + } + + @property inout(void[])[] gcRanges() inout nothrow @nogc + { + return _gcRanges[]; + } + +private: + + invariant() + { + safeAssert(_moduleGroup.modules.length > 0, "No modules for DSO."); + } + + void** _slot; + ModuleGroup _moduleGroup; + Array!(void[]) _gcRanges; + + version (Shared) + { + Array!(void[]) _codeSegments; // array of code segments + Array!(DSO*) _deps; // D libraries needed by this DSO + void* _handle; // corresponding handle + } +} + +/**** + * Boolean flag set to true while the runtime is initialized. + */ +__gshared bool _isRuntimeInitialized; + +/**** + * Gets called on program startup just before GC is initialized. + */ +void initSections() nothrow @nogc +{ + _isRuntimeInitialized = true; +} + +/*** + * Gets called on program shutdown just after GC is terminated. + */ +void finiSections() nothrow @nogc +{ + _isRuntimeInitialized = false; +} + +alias ScanDG = void delegate(void* pbeg, void* pend) nothrow; + +version (Shared) +{ + import gcc.sections : pinLoadedLibraries, unpinLoadedLibraries, + inheritLoadedLibraries, cleanupLoadedLibraries; + + /*** + * Called once per thread; returns array of thread local storage ranges + */ + Array!(ThreadDSO)* initTLSRanges() @nogc nothrow + { + return &_loadedDSOs(); + } + + void finiTLSRanges(Array!(ThreadDSO)* tdsos) @nogc nothrow + { + // Nothing to do here. tdsos used to point to the _loadedDSOs instance + // in the dying thread's TLS segment and as such is not valid anymore. + // The memory for the array contents was already reclaimed in + // cleanupLoadedLibraries(). + } + + void scanTLSRanges(Array!(ThreadDSO)* tdsos, scope ScanDG dg) nothrow + { + version (GNU_EMUTLS) + _d_emutls_scan(dg); + else + static assert(0, "Native TLS unimplemented"); + } + + // interface for core.thread to inherit loaded libraries + pragma(mangle, gcc.sections.pinLoadedLibraries.mangleof) + void* pinLoadedLibraries() nothrow @nogc + { + auto res = cast(Array!(ThreadDSO)*)calloc(1, Array!(ThreadDSO).sizeof); + res.length = _loadedDSOs.length; + foreach (i, ref tdso; _loadedDSOs) + { + (*res)[i] = tdso; + if (tdso._addCnt) + { + // Increment the dlopen ref for explicitly loaded libraries to pin them. + const success = .dlopen(nameForDSO(tdso._pdso), RTLD_LAZY) !is null; + safeAssert(success, "Failed to increment dlopen ref."); + (*res)[i]._addCnt = 1; // new array takes over the additional ref count + } + } + return res; + } + + pragma(mangle, gcc.sections.unpinLoadedLibraries.mangleof) + void unpinLoadedLibraries(void* p) nothrow @nogc + { + auto pary = cast(Array!(ThreadDSO)*)p; + // In case something failed we need to undo the pinning. + foreach (ref tdso; *pary) + { + if (tdso._addCnt) + { + auto handle = tdso._pdso._handle; + safeAssert(handle !is null, "Invalid library handle."); + .dlclose(handle); + } + } + pary.reset(); + .free(pary); + } + + // Called before TLS ctors are ran, copy over the loaded libraries + // of the parent thread. + pragma(mangle, gcc.sections.inheritLoadedLibraries.mangleof) + void inheritLoadedLibraries(void* p) nothrow @nogc + { + safeAssert(_loadedDSOs.empty, "DSOs have already been registered for this thread."); + _loadedDSOs.swap(*cast(Array!(ThreadDSO)*)p); + .free(p); + } + + // Called after all TLS dtors ran, decrements all remaining dlopen refs. + pragma(mangle, gcc.sections.cleanupLoadedLibraries.mangleof) + void cleanupLoadedLibraries() nothrow @nogc + { + foreach (ref tdso; _loadedDSOs) + { + if (tdso._addCnt == 0) continue; + + auto handle = tdso._pdso._handle; + safeAssert(handle !is null, "Invalid DSO handle."); + for (; tdso._addCnt > 0; --tdso._addCnt) + .dlclose(handle); + } + + // Free the memory for the array contents. + _loadedDSOs.reset(); + } +} +else +{ + /*** + * Called once per thread; returns array of thread local storage ranges + */ + Array!(void[])* initTLSRanges() nothrow @nogc + { + return null; + } + + void finiTLSRanges(Array!(void[])* rngs) nothrow @nogc + { + } + + void scanTLSRanges(Array!(void[])* rngs, scope ScanDG dg) nothrow + { + version (GNU_EMUTLS) + _d_emutls_scan(dg); + else + static assert(0, "Native TLS unimplemented"); + } +} + +private: + +version (Shared) +{ + /* + * Array of thread local DSO metadata for all libraries loaded and + * initialized in this thread. + * + * Note: + * A newly spawned thread will inherit these libraries. + * Note: + * We use an array here to preserve the order of + * initialization. If that became a performance issue, we + * could use a hash table and enumerate the DSOs during + * loading so that the hash table values could be sorted when + * necessary. + */ + struct ThreadDSO + { + DSO* _pdso; + static if (_pdso.sizeof == 8) uint _refCnt, _addCnt; + else static if (_pdso.sizeof == 4) ushort _refCnt, _addCnt; + else static assert(0, "unimplemented"); + alias _pdso this; + } + + @property ref Array!(ThreadDSO) _loadedDSOs() @nogc nothrow + { + static Array!(ThreadDSO) x; + return x; + } + + /* + * Set to true during rt_loadLibrary/rt_unloadLibrary calls. + */ + bool _rtLoading; + + /* + * Hash table to map the native handle (as returned by dlopen) + * to the corresponding DSO*, protected by a mutex. + */ + __gshared pthread_mutex_t _handleToDSOMutex; + @property ref HashTab!(void*, DSO*) _handleToDSO() @nogc nothrow + { + __gshared HashTab!(void*, DSO*) x; + return x; + } +} +else +{ + /* + * Static DSOs loaded by the runtime linker. This includes the + * executable. These can't be unloaded. + */ + @property ref Array!(DSO*) _loadedDSOs() @nogc nothrow + { + __gshared Array!(DSO*) x; + return x; + } + + enum _rtLoading = false; +} + +/////////////////////////////////////////////////////////////////////////////// +// Compiler to runtime interface. +/////////////////////////////////////////////////////////////////////////////// + +struct MachHeader +{ + const(mach_header)* header; // the mach header of the image + intptr_t slide; // virtural memory address slide amount +} + +/**** + * This data structure is generated by the compiler, and then passed to + * _d_dso_registry(). + */ +struct CompilerDSOData +{ + size_t _version; // currently 1 + void** _slot; // can be used to store runtime data + immutable(object.ModuleInfo*)* _minfo_beg, _minfo_end; // array of modules in this object file +} + +T[] toRange(T)(T* beg, T* end) { return beg[0 .. end - beg]; } + +/* For each shared library and executable, the compiler generates code that + * sets up CompilerDSOData and calls _d_dso_registry(). + * A pointer to that code is inserted into both the .ctors and .dtors + * segment so it gets called by the loader on startup and shutdown. + */ +extern(C) void _d_dso_registry(CompilerDSOData* data) +{ + // only one supported currently + safeAssert(data._version >= 1, "Incompatible compiler-generated DSO data version."); + + // no backlink => register + if (*data._slot is null) + { + immutable firstDSO = _loadedDSOs.empty; + if (firstDSO) initLocks(); + + DSO* pdso = cast(DSO*).calloc(1, DSO.sizeof); + assert(typeid(DSO).initializer().ptr is null); + pdso._slot = data._slot; + *data._slot = pdso; // store backlink in library record + + pdso._moduleGroup = ModuleGroup(toRange(data._minfo_beg, data._minfo_end)); + + MachHeader header = void; + const headerFound = findImageHeaderForAddr(data._slot, header); + safeAssert(headerFound, "Failed to find image header."); + + scanSegments(header, pdso); + + version (Shared) + { + auto handle = handleForAddr(data._slot); + + getDependencies(header, pdso._deps); + pdso._handle = handle; + setDSOForHandle(pdso, pdso._handle); + + if (!_rtLoading) + { + /* This DSO was not loaded by rt_loadLibrary which + * happens for all dependencies of an executable or + * the first dlopen call from a C program. + * In this case we add the DSO to the _loadedDSOs of this + * thread with a refCnt of 1 and call the TlsCtors. + */ + immutable ushort refCnt = 1, addCnt = 0; + _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt)); + } + } + else + { + foreach (p; _loadedDSOs) + safeAssert(p !is pdso, "DSO already registered."); + _loadedDSOs.insertBack(pdso); + } + + // don't initialize modules before rt_init was called + if (_isRuntimeInitialized) + { + registerGCRanges(pdso); + // rt_loadLibrary will run tls ctors, so do this only for dlopen + immutable runTlsCtors = !_rtLoading; + runModuleConstructors(pdso, runTlsCtors); + } + } + // has backlink => unregister + else + { + DSO* pdso = cast(DSO*)*data._slot; + *data._slot = null; + + // don't finalizes modules after rt_term was called (see Bugzilla 11378) + if (_isRuntimeInitialized) + { + // rt_unloadLibrary already ran tls dtors, so do this only for dlclose + immutable runTlsDtors = !_rtLoading; + runModuleDestructors(pdso, runTlsDtors); + unregisterGCRanges(pdso); + // run finalizers after module dtors (same order as in rt_term) + version (Shared) runFinalizers(pdso); + } + + version (Shared) + { + if (!_rtLoading) + { + /* This DSO was not unloaded by rt_unloadLibrary so we + * have to remove it from _loadedDSOs here. + */ + foreach (i, ref tdso; _loadedDSOs) + { + if (tdso._pdso == pdso) + { + _loadedDSOs.remove(i); + break; + } + } + } + + unsetDSOForHandle(pdso, pdso._handle); + } + else + { + // static DSOs are unloaded in reverse order + safeAssert(pdso == _loadedDSOs.back, "DSO being unregistered isn't current last one."); + _loadedDSOs.popBack(); + } + + freeDSO(pdso); + + // last DSO being unloaded => shutdown registry + if (_loadedDSOs.empty) + { + version (GNU_EMUTLS) + _d_emutls_destroy(); + version (Shared) + { + safeAssert(_handleToDSO.empty, "_handleToDSO not in sync with _loadedDSOs."); + _handleToDSO.reset(); + } + finiLocks(); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// dynamic loading +/////////////////////////////////////////////////////////////////////////////// + +// Shared D libraries are only supported when linking against a shared druntime library. + +version (Shared) +{ + ThreadDSO* findThreadDSO(DSO* pdso) nothrow @nogc + { + foreach (ref tdata; _loadedDSOs) + if (tdata._pdso == pdso) return &tdata; + return null; + } + + void incThreadRef(DSO* pdso, bool incAdd) + { + if (auto tdata = findThreadDSO(pdso)) // already initialized + { + if (incAdd && ++tdata._addCnt > 1) return; + ++tdata._refCnt; + } + else + { + foreach (dep; pdso._deps) + incThreadRef(dep, false); + immutable ushort refCnt = 1, addCnt = incAdd ? 1 : 0; + _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt)); + pdso._moduleGroup.runTlsCtors(); + } + } + + void decThreadRef(DSO* pdso, bool decAdd) + { + auto tdata = findThreadDSO(pdso); + safeAssert(tdata !is null, "Failed to find thread DSO."); + safeAssert(!decAdd || tdata._addCnt > 0, "Mismatching rt_unloadLibrary call."); + + if (decAdd && --tdata._addCnt > 0) return; + if (--tdata._refCnt > 0) return; + + pdso._moduleGroup.runTlsDtors(); + foreach (i, ref td; _loadedDSOs) + if (td._pdso == pdso) _loadedDSOs.remove(i); + foreach (dep; pdso._deps) + decThreadRef(dep, false); + } + + extern(C) void* rt_loadLibrary(const char* name) + { + immutable save = _rtLoading; + _rtLoading = true; + scope (exit) _rtLoading = save; + + auto handle = .dlopen(name, RTLD_LAZY); + if (handle is null) return null; + + // if it's a D library + if (auto pdso = dsoForHandle(handle)) + incThreadRef(pdso, true); + return handle; + } + + extern(C) int rt_unloadLibrary(void* handle) + { + if (handle is null) return false; + + immutable save = _rtLoading; + _rtLoading = true; + scope (exit) _rtLoading = save; + + // if it's a D library + if (auto pdso = dsoForHandle(handle)) + decThreadRef(pdso, true); + return .dlclose(handle) == 0; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// helper functions +/////////////////////////////////////////////////////////////////////////////// + +void initLocks() nothrow @nogc +{ + version (Shared) + !pthread_mutex_init(&_handleToDSOMutex, null) || assert(0); +} + +void finiLocks() nothrow @nogc +{ + version (Shared) + !pthread_mutex_destroy(&_handleToDSOMutex) || assert(0); +} + +void runModuleConstructors(DSO* pdso, bool runTlsCtors) +{ + pdso._moduleGroup.sortCtors(); + pdso._moduleGroup.runCtors(); + if (runTlsCtors) pdso._moduleGroup.runTlsCtors(); +} + +void runModuleDestructors(DSO* pdso, bool runTlsDtors) +{ + if (runTlsDtors) pdso._moduleGroup.runTlsDtors(); + pdso._moduleGroup.runDtors(); +} + +void registerGCRanges(DSO* pdso) nothrow @nogc +{ + foreach (rng; pdso._gcRanges) + GC.addRange(rng.ptr, rng.length); +} + +void unregisterGCRanges(DSO* pdso) nothrow @nogc +{ + foreach (rng; pdso._gcRanges) + GC.removeRange(rng.ptr); +} + +version (Shared) void runFinalizers(DSO* pdso) +{ + foreach (seg; pdso._codeSegments) + GC.runFinalizers(seg); +} + +void freeDSO(DSO* pdso) nothrow @nogc +{ + pdso._gcRanges.reset(); + version (Shared) + { + pdso._codeSegments.reset(); + pdso._deps.reset(); + pdso._handle = null; + } + .free(pdso); +} + +version (Shared) +{ +@nogc nothrow: + const(char)* nameForDSO(in DSO* pdso) + { + Dl_info info = void; + const success = dladdr(pdso._slot, &info) != 0; + safeAssert(success, "Failed to get DSO info."); + return info.dli_fname; + } + + DSO* dsoForHandle(void* handle) + { + DSO* pdso; + !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); + if (auto ppdso = handle in _handleToDSO) + pdso = *ppdso; + !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); + return pdso; + } + + void setDSOForHandle(DSO* pdso, void* handle) + { + !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); + safeAssert(handle !in _handleToDSO, "DSO already registered."); + _handleToDSO[handle] = pdso; + !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); + } + + void unsetDSOForHandle(DSO* pdso, void* handle) + { + !pthread_mutex_lock(&_handleToDSOMutex) || assert(0); + safeAssert(_handleToDSO[handle] == pdso, "Handle doesn't match registered DSO."); + _handleToDSO.remove(handle); + !pthread_mutex_unlock(&_handleToDSOMutex) || assert(0); + } + + void getDependencies(in MachHeader info, ref Array!(DSO*) deps) + { + // FIXME: Not implemented yet. + } + + void* handleForName(const char* name) + { + auto handle = .dlopen(name, RTLD_NOLOAD | RTLD_LAZY); + if (handle !is null) .dlclose(handle); // drop reference count + return handle; + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Mach-O program header iteration +/////////////////////////////////////////////////////////////////////////////// + +/************ + * Scan segments in the image header and store + * the writeable data segments in *pdso. + */ + +void scanSegments(in MachHeader info, DSO* pdso) +{ + foreach (e; dataSegs) + { + auto sect = getSection(info.header, info.slide, e.seg.ptr, e.sect.ptr); + if (sect != null) + pdso._gcRanges.insertBack((cast(void*)sect.ptr)[0 .. sect.length]); + } + + version (Shared) + { + void[] text = getSection(info.header, info.slide, "__TEXT", "__text"); + if (!text) + assert(0, "Failed to get text section."); + pdso._codeSegments.insertBack(text); + } +} + +/************************** + * Input: + * result where the output is to be written + * Returns: + * true if found, and *result is filled in + */ + +bool findImageHeaderForAddr(in void* addr, out MachHeader result) +{ + Dl_info info; + if (dladdr(addr, &info) == 0) + return false; + + foreach (i; 0 .. _dyld_image_count()) + { + if (info.dli_fbase == _dyld_get_image_header(i)) + { + result.header = cast(const(mach_header)*)info.dli_fbase; + result.slide = _dyld_get_image_vmaddr_slide(i); + return true; + } + } + return false; +} + +/************************** + * Input: + * addr an internal address of a DSO + * Returns: + * the dlopen handle for that DSO or null if addr is not within a loaded DSO + */ +version (Shared) void* handleForAddr(void* addr) nothrow @nogc +{ + Dl_info info = void; + if (dladdr(addr, &info) != 0) + return handleForName(info.dli_fname); + return null; +} + +struct SegRef +{ + string seg; + string sect; +} + +static immutable SegRef[] dataSegs = [{SEG_DATA, SECT_DATA}, + {SEG_DATA, SECT_BSS}, + {SEG_DATA, SECT_COMMON}]; + +/** + * Returns the section for the named section in the named segment + * for the mach_header pointer passed, or null if not found. + */ +ubyte[] getSection(in mach_header* header, intptr_t slide, + in char* segmentName, in char* sectionName) +{ + version (D_LP64) + { + assert(header.magic == MH_MAGIC_64); + auto sect = getsectbynamefromheader_64(cast(mach_header_64*)header, + segmentName, + sectionName); + } + else + { + assert(header.magic == MH_MAGIC); + auto sect = getsectbynamefromheader(header, + segmentName, + sectionName); + } + + if (sect !is null && sect.size > 0) + return (cast(ubyte*)sect.addr + slide)[0 .. cast(size_t)sect.size]; + return null; +} diff --git a/libphobos/libdruntime/gcc/sections/osx.d b/libphobos/libdruntime/gcc/sections/osx.d deleted file mode 100644 index 3e3db70..0000000 --- a/libphobos/libdruntime/gcc/sections/osx.d +++ /dev/null @@ -1,284 +0,0 @@ -// OSX-specific support for sections. -// Copyright (C) 2019-2021 Free Software Foundation, Inc. - -// GCC is free software; you can redistribute it and/or modify it under -// the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 3, or (at your option) any later -// version. - -// GCC is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. - -// Under Section 7 of GPL version 3, you are granted additional -// permissions described in the GCC Runtime Library Exception, version -// 3.1, as published by the Free Software Foundation. - -// You should have received a copy of the GNU General Public License and -// a copy of the GCC Runtime Library Exception along with this program; -// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -// . - -module gcc.sections.osx; - -version (OSX): - -// debug = PRINTF; -import core.stdc.stdio; -import core.stdc.string, core.stdc.stdlib; -import core.sys.posix.pthread; -import core.sys.darwin.mach.dyld; -import core.sys.darwin.mach.getsect; -import rt.deh, rt.minfo; -import rt.util.container.array; - -struct SectionGroup -{ - static int opApply(scope int delegate(ref SectionGroup) dg) - { - return dg(_sections); - } - - static int opApplyReverse(scope int delegate(ref SectionGroup) dg) - { - return dg(_sections); - } - - @property immutable(ModuleInfo*)[] modules() const nothrow @nogc - { - return _moduleGroup.modules; - } - - @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc - { - return _moduleGroup; - } - - @property inout(void[])[] gcRanges() inout nothrow @nogc - { - return _gcRanges[]; - } - - @property immutable(FuncTable)[] ehTables() const nothrow @nogc - { - return _ehTables[]; - } - -private: - immutable(FuncTable)[] _ehTables; - ModuleGroup _moduleGroup; - Array!(void[]) _gcRanges; - immutable(void)[][2] _tlsImage; -} - -/**** - * Boolean flag set to true while the runtime is initialized. - */ -__gshared bool _isRuntimeInitialized; - -/**** - * Gets called on program startup just before GC is initialized. - */ -void initSections() nothrow @nogc -{ - pthread_key_create(&_tlsKey, null); - _dyld_register_func_for_add_image(§ions_osx_onAddImage); - _isRuntimeInitialized = true; -} - -/*** - * Gets called on program shutdown just after GC is terminated. - */ -void finiSections() nothrow @nogc -{ - _sections._gcRanges.reset(); - pthread_key_delete(_tlsKey); - _isRuntimeInitialized = false; -} - -void[]* initTLSRanges() nothrow @nogc -{ - return &getTLSBlock(); -} - -void finiTLSRanges(void[]* rng) nothrow @nogc -{ - .free(rng.ptr); - .free(rng); -} - -void scanTLSRanges(void[]* rng, scope void delegate(void* pbeg, void* pend) nothrow dg) nothrow -{ - dg(rng.ptr, rng.ptr + rng.length); -} - -// NOTE: The Mach-O object file format does not allow for thread local -// storage declarations. So instead we roll our own by putting tls -// into the __tls_data and the __tlscoal_nt sections. -// -// This function is called by the code emitted by the compiler. It -// is expected to translate an address into the TLS static data to -// the corresponding address in the TLS dynamic per-thread data. - -// NB: the compiler mangles this function as '___tls_get_addr' even though it is extern(D) -extern(D) void* ___tls_get_addr( void* p ) -{ - immutable off = tlsOffset(p); - auto tls = getTLSBlockAlloc(); - assert(off < tls.length); - return tls.ptr + off; -} - -private: - -__gshared pthread_key_t _tlsKey; - -size_t tlsOffset(void* p) -in -{ - assert(_sections._tlsImage[0].ptr !is null || - _sections._tlsImage[1].ptr !is null); -} -body -{ - // NOTE: p is an address in the TLS static data emitted by the - // compiler. If it isn't, something is disastrously wrong. - immutable off0 = cast(size_t)(p - _sections._tlsImage[0].ptr); - if (off0 < _sections._tlsImage[0].length) - { - return off0; - } - immutable off1 = cast(size_t)(p - _sections._tlsImage[1].ptr); - if (off1 < _sections._tlsImage[1].length) - { - size_t sz = (_sections._tlsImage[0].length + 15) & ~cast(size_t)15; - return sz + off1; - } - assert(0); -} - -ref void[] getTLSBlock() nothrow @nogc -{ - auto pary = cast(void[]*)pthread_getspecific(_tlsKey); - if (pary is null) - { - pary = cast(void[]*).calloc(1, (void[]).sizeof); - if (pthread_setspecific(_tlsKey, pary) != 0) - { - import core.stdc.stdio; - perror("pthread_setspecific failed with"); - assert(0); - } - } - return *pary; -} - -ref void[] getTLSBlockAlloc() -{ - auto pary = &getTLSBlock(); - if (!pary.length) - { - auto imgs = _sections._tlsImage; - immutable sz0 = (imgs[0].length + 15) & ~cast(size_t)15; - immutable sz2 = sz0 + imgs[1].length; - auto p = .malloc(sz2); - memcpy(p, imgs[0].ptr, imgs[0].length); - memcpy(p + sz0, imgs[1].ptr, imgs[1].length); - *pary = p[0 .. sz2]; - } - return *pary; -} - -__gshared SectionGroup _sections; - -extern (C) void sections_osx_onAddImage(in mach_header* h, intptr_t slide) -{ - foreach (e; dataSegs) - { - auto sect = getSection(h, slide, e.seg.ptr, e.sect.ptr); - if (sect != null) - _sections._gcRanges.insertBack((cast(void*)sect.ptr)[0 .. sect.length]); - } - - auto minfosect = getSection(h, slide, "__DATA", "__minfodata"); - if (minfosect != null) - { - // no support for multiple images yet - // take the sections from the last static image which is the executable - if (_isRuntimeInitialized) - { - fprintf(stderr, "Loading shared libraries isn't yet supported on OSX.\n"); - return; - } - else if (_sections.modules.ptr !is null) - { - fprintf(stderr, "Shared libraries are not yet supported on OSX.\n"); - } - - debug(PRINTF) printf(" minfodata\n"); - auto p = cast(immutable(ModuleInfo*)*)minfosect.ptr; - immutable len = minfosect.length / (*p).sizeof; - - _sections._moduleGroup = ModuleGroup(p[0 .. len]); - } - - auto ehsect = getSection(h, slide, "__DATA", "__deh_eh"); - if (ehsect != null) - { - debug(PRINTF) printf(" deh_eh\n"); - auto p = cast(immutable(FuncTable)*)ehsect.ptr; - immutable len = ehsect.length / (*p).sizeof; - - _sections._ehTables = p[0 .. len]; - } - - auto tlssect = getSection(h, slide, "__DATA", "__tls_data"); - if (tlssect != null) - { - debug(PRINTF) printf(" tls_data %p %p\n", tlssect.ptr, tlssect.ptr + tlssect.length); - _sections._tlsImage[0] = (cast(immutable(void)*)tlssect.ptr)[0 .. tlssect.length]; - } - - auto tlssect2 = getSection(h, slide, "__DATA", "__tlscoal_nt"); - if (tlssect2 != null) - { - debug(PRINTF) printf(" tlscoal_nt %p %p\n", tlssect2.ptr, tlssect2.ptr + tlssect2.length); - _sections._tlsImage[1] = (cast(immutable(void)*)tlssect2.ptr)[0 .. tlssect2.length]; - } -} - -struct SegRef -{ - string seg; - string sect; -} - -static immutable SegRef[] dataSegs = [{SEG_DATA, SECT_DATA}, - {SEG_DATA, SECT_BSS}, - {SEG_DATA, SECT_COMMON}]; - -ubyte[] getSection(in mach_header* header, intptr_t slide, - in char* segmentName, in char* sectionName) -{ - version (X86) - { - assert(header.magic == MH_MAGIC); - auto sect = getsectbynamefromheader(header, - segmentName, - sectionName); - } - else version (X86_64) - { - assert(header.magic == MH_MAGIC_64); - auto sect = getsectbynamefromheader_64(cast(mach_header_64*)header, - segmentName, - sectionName); - } - else - static assert(0, "unimplemented"); - - if (sect !is null && sect.size > 0) - return (cast(ubyte*)sect.addr + slide)[0 .. cast(size_t)sect.size]; - return null; -} diff --git a/libphobos/libdruntime/gcc/sections/package.d b/libphobos/libdruntime/gcc/sections/package.d index fdaf039..4c2b542 100644 --- a/libphobos/libdruntime/gcc/sections/package.d +++ b/libphobos/libdruntime/gcc/sections/package.d @@ -22,27 +22,30 @@ module gcc.sections; -version (CRuntime_Glibc) - public import gcc.sections.elf_shared; -else version (CRuntime_Musl) - public import gcc.sections.elf_shared; -else version (CRuntime_UClibc) - public import gcc.sections.elf_shared; -else version (FreeBSD) - public import gcc.sections.elf_shared; -else version (NetBSD) - public import gcc.sections.elf_shared; -else version (DragonFlyBSD) - public import gcc.sections.elf_shared; -else version (Solaris) - public import gcc.sections.elf_shared; -else version (OSX) - public import gcc.sections.osx; -else version (CRuntime_DigitalMars) - public import gcc.sections.win32; -else version (CRuntime_Microsoft) - public import gcc.sections.win64; -else version (CRuntime_Bionic) - public import gcc.sections.android; +version (CRuntime_Glibc) version = SectionsElf; +version (CRuntime_Musl) version = SectionsElf; +version (CRuntime_UClibc) version = SectionsElf; +version (FreeBSD) version = SectionsElf; +version (NetBSD) version = SectionsElf; +version (DragonFlyBSD) version = SectionsElf; +version (Solaris) version = SectionsElf; +version (OSX) version = SectionsMacho; +version (Windows) version = SectionsPeCoff; + +version (SectionsElf) + public import gcc.sections.elf; +else version (SectionsMacho) + public import gcc.sections.macho; +else version (SectionsPeCoff) + public import gcc.sections.pecoff; else static assert(0, "unimplemented"); + +version (Shared) +{ + // interface for core.thread to inherit loaded libraries + void* pinLoadedLibraries() nothrow @nogc; + void unpinLoadedLibraries(void* p) nothrow @nogc; + void inheritLoadedLibraries(void* p) nothrow @nogc; + void cleanupLoadedLibraries() nothrow @nogc; +} diff --git a/libphobos/libdruntime/gcc/sections/pecoff.d b/libphobos/libdruntime/gcc/sections/pecoff.d new file mode 100644 index 0000000..ed0340e --- /dev/null +++ b/libphobos/libdruntime/gcc/sections/pecoff.d @@ -0,0 +1,826 @@ +// PE/COFF-specific support for sections. +// Copyright (C) 2021 Free Software Foundation, Inc. + +// GCC is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3, or (at your option) any later +// version. + +// GCC is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. + +// Under Section 7 of GPL version 3, you are granted additional +// permissions described in the GCC Runtime Library Exception, version +// 3.1, as published by the Free Software Foundation. + +// You should have received a copy of the GNU General Public License and +// a copy of the GCC Runtime Library Exception along with this program; +// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +// . + +module gcc.sections.pecoff; + +version (Windows): + +import core.memory; +import core.stdc.stdlib; +import core.sys.windows.winbase; +import core.sys.windows.windef; +import core.sys.windows.winnt; +import rt.minfo; +import rt.util.container.array; +import rt.util.container.hashtab; +import gcc.sections.common; + +version (GNU_EMUTLS) + import gcc.emutls; + +alias DSO SectionGroup; +struct DSO +{ + static int opApply(scope int delegate(ref DSO) dg) + { + foreach (dso; _loadedDSOs) + { + if (auto res = dg(*dso)) + return res; + } + return 0; + } + + static int opApplyReverse(scope int delegate(ref DSO) dg) + { + foreach_reverse (dso; _loadedDSOs) + { + if (auto res = dg(*dso)) + return res; + } + return 0; + } + + @property immutable(ModuleInfo*)[] modules() const nothrow @nogc + { + return _moduleGroup.modules; + } + + @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc + { + return _moduleGroup; + } + + @property inout(void[])[] gcRanges() inout nothrow @nogc + { + return _gcRanges[]; + } + +private: + + invariant() + { + safeAssert(_moduleGroup.modules.length > 0, "No modules for DSO."); + } + + void** _slot; + ModuleGroup _moduleGroup; + Array!(void[]) _gcRanges; + + version (Shared) + { + Array!(void[]) _codeSegments; // array of code segments + Array!(DSO*) _deps; // D libraries needed by this DSO + void* _handle; // corresponding handle + } +} + +/**** + * Boolean flag set to true while the runtime is initialized. + */ +__gshared bool _isRuntimeInitialized; + +/**** + * Gets called on program startup just before GC is initialized. + */ +void initSections() nothrow @nogc +{ + _isRuntimeInitialized = true; +} + +/*** + * Gets called on program shutdown just after GC is terminated. + */ +void finiSections() nothrow @nogc +{ + _isRuntimeInitialized = false; +} + +alias ScanDG = void delegate(void* pbeg, void* pend) nothrow; + +version (Shared) +{ + import gcc.sections : pinLoadedLibraries, unpinLoadedLibraries, + inheritLoadedLibraries, cleanupLoadedLibraries; + + /*** + * Called once per thread; returns array of thread local storage ranges + */ + Array!(ThreadDSO)* initTLSRanges() @nogc nothrow + { + return &_loadedDSOs(); + } + + void finiTLSRanges(Array!(ThreadDSO)* tdsos) @nogc nothrow + { + // Nothing to do here. tdsos used to point to the _loadedDSOs instance + // in the dying thread's TLS segment and as such is not valid anymore. + // The memory for the array contents was already reclaimed in + // cleanupLoadedLibraries(). + } + + void scanTLSRanges(Array!(ThreadDSO)* tdsos, scope ScanDG dg) nothrow + { + version (GNU_EMUTLS) + _d_emutls_scan(dg); + else + static assert(0, "Native TLS unimplemented"); + } + + // interface for core.thread to inherit loaded libraries + pragma(mangle, gcc.sections.pinLoadedLibraries.mangleof) + void* pinLoadedLibraries() nothrow @nogc + { + auto res = cast(Array!(ThreadDSO)*)calloc(1, Array!(ThreadDSO).sizeof); + res.length = _loadedDSOs.length; + foreach (i, ref tdso; _loadedDSOs) + { + (*res)[i] = tdso; + if (tdso._addCnt) + { + // Increment the DLL ref for explicitly loaded libraries to pin them. + char[MAX_PATH] buf; + char[] buffer = buf[]; + const success = .LoadLibraryA(nameForDSO(tdso._pdso, buffer)) !is null; + safeAssert(success, "Failed to increment DLL ref."); + (*res)[i]._addCnt = 1; // new array takes over the additional ref count + } + } + return res; + } + + pragma(mangle, gcc.sections.unpinLoadedLibraries.mangleof) + void unpinLoadedLibraries(void* p) nothrow @nogc + { + auto pary = cast(Array!(ThreadDSO)*)p; + // In case something failed we need to undo the pinning. + foreach (ref tdso; *pary) + { + if (tdso._addCnt) + { + auto handle = tdso._pdso._handle; + safeAssert(handle !is null, "Invalid library handle."); + .FreeLibrary(handle); + } + } + pary.reset(); + .free(pary); + } + + // Called before TLS ctors are ran, copy over the loaded libraries + // of the parent thread. + pragma(mangle, gcc.sections.inheritLoadedLibraries.mangleof) + void inheritLoadedLibraries(void* p) nothrow @nogc + { + safeAssert(_loadedDSOs.empty, "DSOs have already been registered for this thread."); + _loadedDSOs.swap(*cast(Array!(ThreadDSO)*)p); + .free(p); + } + + // Called after all TLS dtors ran, decrements all remaining DLL refs. + pragma(mangle, gcc.sections.cleanupLoadedLibraries.mangleof) + void cleanupLoadedLibraries() nothrow @nogc + { + foreach (ref tdso; _loadedDSOs) + { + if (tdso._addCnt == 0) continue; + + auto handle = tdso._pdso._handle; + safeAssert(handle !is null, "Invalid DSO handle."); + for (; tdso._addCnt > 0; --tdso._addCnt) + .FreeLibrary(handle); + } + + // Free the memory for the array contents. + _loadedDSOs.reset(); + } +} +else +{ + /*** + * Called once per thread; returns array of thread local storage ranges + */ + Array!(void[])* initTLSRanges() nothrow @nogc + { + return null; + } + + void finiTLSRanges(Array!(void[])* rngs) nothrow @nogc + { + } + + void scanTLSRanges(Array!(void[])* rngs, scope ScanDG dg) nothrow + { + version (GNU_EMUTLS) + _d_emutls_scan(dg); + else + static assert(0, "Native TLS unimplemented"); + } +} + +private: + +version (Shared) +{ + /* + * Array of thread local DSO metadata for all libraries loaded and + * initialized in this thread. + * + * Note: + * A newly spawned thread will inherit these libraries. + * Note: + * We use an array here to preserve the order of + * initialization. If that became a performance issue, we + * could use a hash table and enumerate the DSOs during + * loading so that the hash table values could be sorted when + * necessary. + */ + struct ThreadDSO + { + DSO* _pdso; + static if (_pdso.sizeof == 8) uint _refCnt, _addCnt; + else static if (_pdso.sizeof == 4) ushort _refCnt, _addCnt; + else static assert(0, "unimplemented"); + alias _pdso this; + } + + @property ref Array!(ThreadDSO) _loadedDSOs() @nogc nothrow + { + static Array!(ThreadDSO) x; + return x; + } + + /* + * Set to true during rt_loadLibrary/rt_unloadLibrary calls. + */ + bool _rtLoading; + + /* + * Hash table to map the native handle (as returned by dlopen) + * to the corresponding DSO*, protected by a mutex. + */ + __gshared CRITICAL_SECTION _handleToDSOMutex; + @property ref HashTab!(void*, DSO*) _handleToDSO() @nogc nothrow + { + __gshared HashTab!(void*, DSO*) x; + return x; + } +} +else +{ + /* + * Static DSOs loaded by the runtime linker. This includes the + * executable. These can't be unloaded. + */ + @property ref Array!(DSO*) _loadedDSOs() @nogc nothrow + { + __gshared Array!(DSO*) x; + return x; + } + + enum _rtLoading = false; +} + +/////////////////////////////////////////////////////////////////////////////// +// Compiler to runtime interface. +/////////////////////////////////////////////////////////////////////////////// + +/**** + * This data structure is generated by the compiler, and then passed to + * _d_dso_registry(). + */ +struct CompilerDSOData +{ + size_t _version; // currently 1 + void** _slot; // can be used to store runtime data + immutable(object.ModuleInfo*)* _minfo_beg, _minfo_end; // array of modules in this object file +} + +T[] toRange(T)(T* beg, T* end) { return beg[0 .. end - beg]; } + +/* For each shared library and executable, the compiler generates code that + * sets up CompilerDSOData and calls _d_dso_registry(). + * A pointer to that code is inserted into both the .ctors and .dtors + * segment so it gets called by the loader on startup and shutdown. + */ +extern(C) void _d_dso_registry(CompilerDSOData* data) +{ + // only one supported currently + safeAssert(data._version >= 1, "Incompatible compiler-generated DSO data version."); + + // no backlink => register + if (*data._slot is null) + { + immutable firstDSO = _loadedDSOs.empty; + if (firstDSO) initLocks(); + + DSO* pdso = cast(DSO*).calloc(1, DSO.sizeof); + assert(typeid(DSO).initializer().ptr is null); + pdso._slot = data._slot; + *data._slot = pdso; // store backlink in library record + + pdso._moduleGroup = ModuleGroup(toRange(data._minfo_beg, data._minfo_end)); + + HMODULE handle = void; + const moduleFound = findModuleHandleForAddr(data._slot, handle); + safeAssert(moduleFound, "Failed to find image header."); + + scanSegments(handle, pdso); + + version (Shared) + { + getDependencies(handle, pdso._deps); + pdso._handle = handle; + setDSOForHandle(pdso, pdso._handle); + + if (!_rtLoading) + { + /* This DSO was not loaded by rt_loadLibrary which + * happens for all dependencies of an executable or + * the first dlopen call from a C program. + * In this case we add the DSO to the _loadedDSOs of this + * thread with a refCnt of 1 and call the TlsCtors. + */ + immutable ushort refCnt = 1, addCnt = 0; + _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt)); + } + } + else + { + foreach (p; _loadedDSOs) + safeAssert(p !is pdso, "DSO already registered."); + _loadedDSOs.insertBack(pdso); + } + + // don't initialize modules before rt_init was called + if (_isRuntimeInitialized) + { + registerGCRanges(pdso); + // rt_loadLibrary will run tls ctors, so do this only for dlopen + immutable runTlsCtors = !_rtLoading; + runModuleConstructors(pdso, runTlsCtors); + } + } + // has backlink => unregister + else + { + DSO* pdso = cast(DSO*)*data._slot; + *data._slot = null; + + // don't finalizes modules after rt_term was called (see Bugzilla 11378) + if (_isRuntimeInitialized) + { + // rt_unloadLibrary already ran tls dtors, so do this only for dlclose + immutable runTlsDtors = !_rtLoading; + runModuleDestructors(pdso, runTlsDtors); + unregisterGCRanges(pdso); + // run finalizers after module dtors (same order as in rt_term) + version (Shared) runFinalizers(pdso); + } + + version (Shared) + { + if (!_rtLoading) + { + /* This DSO was not unloaded by rt_unloadLibrary so we + * have to remove it from _loadedDSOs here. + */ + foreach (i, ref tdso; _loadedDSOs) + { + if (tdso._pdso == pdso) + { + _loadedDSOs.remove(i); + break; + } + } + } + + unsetDSOForHandle(pdso, pdso._handle); + } + else + { + // static DSOs are unloaded in reverse order + safeAssert(pdso == _loadedDSOs.back, "DSO being unregistered isn't current last one."); + _loadedDSOs.popBack(); + } + + freeDSO(pdso); + + // last DSO being unloaded => shutdown registry + if (_loadedDSOs.empty) + { + version (GNU_EMUTLS) + _d_emutls_destroy(); + version (Shared) + { + safeAssert(_handleToDSO.empty, "_handleToDSO not in sync with _loadedDSOs."); + _handleToDSO.reset(); + } + finiLocks(); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// dynamic loading +/////////////////////////////////////////////////////////////////////////////// + +// Shared D libraries are only supported when linking against a shared druntime library. + +version (Shared) +{ + ThreadDSO* findThreadDSO(DSO* pdso) nothrow @nogc + { + foreach (ref tdata; _loadedDSOs) + if (tdata._pdso == pdso) return &tdata; + return null; + } + + void incThreadRef(DSO* pdso, bool incAdd) + { + if (auto tdata = findThreadDSO(pdso)) // already initialized + { + if (incAdd && ++tdata._addCnt > 1) return; + ++tdata._refCnt; + } + else + { + foreach (dep; pdso._deps) + incThreadRef(dep, false); + immutable ushort refCnt = 1, addCnt = incAdd ? 1 : 0; + _loadedDSOs.insertBack(ThreadDSO(pdso, refCnt, addCnt)); + pdso._moduleGroup.runTlsCtors(); + } + } + + void decThreadRef(DSO* pdso, bool decAdd) + { + auto tdata = findThreadDSO(pdso); + safeAssert(tdata !is null, "Failed to find thread DSO."); + safeAssert(!decAdd || tdata._addCnt > 0, "Mismatching rt_unloadLibrary call."); + + if (decAdd && --tdata._addCnt > 0) return; + if (--tdata._refCnt > 0) return; + + pdso._moduleGroup.runTlsDtors(); + foreach (i, ref td; _loadedDSOs) + if (td._pdso == pdso) _loadedDSOs.remove(i); + foreach (dep; pdso._deps) + decThreadRef(dep, false); + } +} + +/*********************************** + * These are a temporary means of providing a GC hook for DLL use. They may be + * replaced with some other similar functionality later. + */ +extern (C) +{ + void* gc_getProxy(); + void gc_setProxy(void* p); + void gc_clrProxy(); + + alias void function(void*) gcSetFn; + alias void function() gcClrFn; +} + +/******************************************* + * Loads a DLL written in D with the name 'name'. + * Returns: + * opaque handle to the DLL if successfully loaded + * null if failure + */ +extern(C) void* rt_loadLibrary(const char* name) +{ + version (Shared) + { + immutable save = _rtLoading; + _rtLoading = true; + scope (exit) _rtLoading = save; + } + return initLibrary(.LoadLibraryA(name)); +} + +extern (C) void* rt_loadLibraryW(const wchar_t* name) +{ + version (Shared) + { + immutable save = _rtLoading; + _rtLoading = true; + scope (exit) _rtLoading = save; + } + return initLibrary(.LoadLibraryW(name)); +} + +void* initLibrary(void* handle) +{ + if (handle is null) + return null; + + version (Shared) + { + // if it's a D library + if (auto pdso = dsoForHandle(handle)) + incThreadRef(pdso, true); + } + gcSetFn gcSet = cast(gcSetFn) GetProcAddress(handle, "gc_setProxy"); + if (gcSet !is null) + { + // BUG: Set proxy, but too late + gcSet(gc_getProxy()); + } + return handle; +} + +/************************************* + * Unloads DLL that was previously loaded by rt_loadLibrary(). + * Input: + * handle the handle returned by rt_loadLibrary() + * Returns: + * 1 succeeded + * 0 some failure happened + */ +extern(C) int rt_unloadLibrary(void* handle) +{ + if (handle is null) + return false; + + version (Shared) + { + immutable save = _rtLoading; + _rtLoading = true; + scope (exit) _rtLoading = save; + + // if it's a D library + if (auto pdso = dsoForHandle(handle)) + decThreadRef(pdso, true); + } + gcClrFn gcClr = cast(gcClrFn) GetProcAddress(handle, "gc_clrProxy"); + if (gcClr !is null) + gcClr(); + return .FreeLibrary(handle) != 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// helper functions +/////////////////////////////////////////////////////////////////////////////// + +void initLocks() nothrow @nogc +{ + version (Shared) + InitializeCriticalSection(&_handleToDSOMutex); +} + +void finiLocks() nothrow @nogc +{ + version (Shared) + DeleteCriticalSection(&_handleToDSOMutex); +} + +void runModuleConstructors(DSO* pdso, bool runTlsCtors) +{ + pdso._moduleGroup.sortCtors(); + pdso._moduleGroup.runCtors(); + if (runTlsCtors) pdso._moduleGroup.runTlsCtors(); +} + +void runModuleDestructors(DSO* pdso, bool runTlsDtors) +{ + if (runTlsDtors) pdso._moduleGroup.runTlsDtors(); + pdso._moduleGroup.runDtors(); +} + +void registerGCRanges(DSO* pdso) nothrow @nogc +{ + foreach (rng; pdso._gcRanges) + GC.addRange(rng.ptr, rng.length); +} + +void unregisterGCRanges(DSO* pdso) nothrow @nogc +{ + foreach (rng; pdso._gcRanges) + GC.removeRange(rng.ptr); +} + +version (Shared) void runFinalizers(DSO* pdso) +{ + foreach (seg; pdso._codeSegments) + GC.runFinalizers(seg); +} + +void freeDSO(DSO* pdso) nothrow @nogc +{ + pdso._gcRanges.reset(); + version (Shared) + { + pdso._codeSegments.reset(); + pdso._deps.reset(); + pdso._handle = null; + } + .free(pdso); +} + +version (Shared) +{ +@nogc nothrow: + const(char)* nameForDSO(DSO* pdso, ref char[] buffer) + { + const success = GetModuleFileNameA(pdso._handle, buffer.ptr, cast(DWORD)buffer.length) != 0; + safeAssert(success, "Failed to get DLL name."); + return buffer.ptr; + } + + DSO* dsoForHandle(in void* handle) + { + DSO* pdso; + .EnterCriticalSection(&_handleToDSOMutex); + if (auto ppdso = handle in _handleToDSO) + pdso = *ppdso; + .LeaveCriticalSection(&_handleToDSOMutex); + return pdso; + } + + void setDSOForHandle(DSO* pdso, void* handle) + { + .EnterCriticalSection(&_handleToDSOMutex); + safeAssert(handle !in _handleToDSO, "DSO already registered."); + _handleToDSO[handle] = pdso; + .LeaveCriticalSection(&_handleToDSOMutex); + } + + void unsetDSOForHandle(DSO* pdso, void* handle) + { + .EnterCriticalSection(&_handleToDSOMutex); + safeAssert(_handleToDSO[handle] == pdso, "Handle doesn't match registered DSO."); + _handleToDSO.remove(handle); + .LeaveCriticalSection(&_handleToDSOMutex); + } + + void getDependencies(in HMODULE handle, ref Array!(DSO*) deps) + { + auto nthdr = getNTHeader(handle); + auto import_entry = nthdr.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + auto addr = import_entry.VirtualAddress; + auto datasize = import_entry.Size; + + if (addr == 0 && datasize == 0) + { + // Maybe the optional header isn't there, look for the section. + foreach (section; getSectionHeader(handle)) + { + if (!compareSectionName(section, ".idata")) + continue; + addr = section.VirtualAddress; + datasize = section.Misc.VirtualSize; + break; + } + if (datasize == 0) + return; + } + else + { + bool foundSection = false; + foreach (section; getSectionHeader(handle)) + { + if (!compareSectionName(section, ".idata")) + continue; + // Section containing import table has no contents. + if (section.Misc.VirtualSize == 0) + return; + foundSection = true; + break; + } + // There is an import table, but the section containing it could not be found + if (!foundSection) + return; + } + + // Get the names of each DLL + for (uint i = 0; i + IMAGE_IMPORT_DESCRIPTOR.sizeof <= datasize; + i += IMAGE_IMPORT_DESCRIPTOR.sizeof) + { + const data = cast(PIMAGE_IMPORT_DESCRIPTOR)(handle + addr + i); + if (data.Name == 0) + break; + + // dll name of dependency + auto name = cast(char*)(handle + data.Name); + // get handle without loading the library + auto libhandle = handleForName(name); + // the runtime linker has already loaded all dependencies + safeAssert(handle !is null, "Failed to get library handle."); + // if it's a D library + if (auto pdso = dsoForHandle(handle)) + deps.insertBack(pdso); // append it to the dependencies + } + } + + void* handleForName(const char* name) + { + return GetModuleHandleA(name); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// PE/COFF program header iteration +/////////////////////////////////////////////////////////////////////////////// + +bool compareSectionName(ref IMAGE_SECTION_HEADER section, string name) nothrow @nogc +{ + if (name[] != cast(char[])section.Name[0 .. name.length]) + return false; + return name.length == 8 || section.Name[name.length] == 0; +} + +/************ + * Scan segments in the image header and store + * the writeable data segments in *pdso. + */ + +void scanSegments(in HMODULE handle, DSO* pdso) nothrow @nogc +{ + foreach (section; getSectionHeader(handle)) + { + // the ".data" image section includes both object file sections ".data" and ".bss" + if (compareSectionName(section, ".data")) + { + auto data = cast(void*)handle + section.VirtualAddress; + pdso._gcRanges.insertBack(data[0 .. section.Misc.VirtualSize]); + continue; + } + + version (Shared) + { + if (compareSectionName(section, ".text")) + { + auto text = cast(void*)handle + section.VirtualAddress; + pdso._codeSegments.insertBack(text[0 .. section.Misc.VirtualSize]); + continue; + } + } + } +} + +/************************** + * Input: + * handle where the output is to be written + * Returns: + * true if found, and *handle is filled in + */ + +bool findModuleHandleForAddr(in void* addr, out HMODULE handle) nothrow @nogc +{ + if (GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + cast(const(wchar)*) addr, &handle)) + return true; + + return false; +} + +/** + * Returns the image NT header for the HMODULE handle passed, + * or null if not found. + */ +PIMAGE_NT_HEADERS getNTHeader(in HMODULE handle) nothrow @nogc +{ + auto doshdr = cast(PIMAGE_DOS_HEADER)handle; + if (doshdr.e_magic != IMAGE_DOS_SIGNATURE) + return null; + + return cast(typeof(return))(cast(void*)doshdr + doshdr.e_lfanew); +} + +/** + * Returns the image section header for the HMODULE handle passed, + * or null if not found. + */ +IMAGE_SECTION_HEADER[] getSectionHeader(in HMODULE handle) nothrow @nogc +{ + if (auto nthdr = getNTHeader(handle)) + { + const void* opthdr = &nthdr.OptionalHeader; + const offset = nthdr.FileHeader.SizeOfOptionalHeader; + const length = nthdr.FileHeader.NumberOfSections; + return (cast(PIMAGE_SECTION_HEADER)(opthdr + offset))[0 .. length]; + } + return null; +} diff --git a/libphobos/libdruntime/gcc/sections/win32.d b/libphobos/libdruntime/gcc/sections/win32.d deleted file mode 100644 index b355dfc..0000000 --- a/libphobos/libdruntime/gcc/sections/win32.d +++ /dev/null @@ -1,183 +0,0 @@ -// Win32-specific support for sections. -// Copyright (C) 2019-2021 Free Software Foundation, Inc. - -// GCC is free software; you can redistribute it and/or modify it under -// the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 3, or (at your option) any later -// version. - -// GCC is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. - -// Under Section 7 of GPL version 3, you are granted additional -// permissions described in the GCC Runtime Library Exception, version -// 3.1, as published by the Free Software Foundation. - -// You should have received a copy of the GNU General Public License and -// a copy of the GCC Runtime Library Exception along with this program; -// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -// . - -module gcc.sections.win32; - -version (CRuntime_DigitalMars): - -// debug = PRINTF; -debug(PRINTF) import core.stdc.stdio; -import rt.minfo; -import core.stdc.stdlib : malloc, free; - -struct SectionGroup -{ - static int opApply(scope int delegate(ref SectionGroup) dg) - { - return dg(_sections); - } - - static int opApplyReverse(scope int delegate(ref SectionGroup) dg) - { - return dg(_sections); - } - - @property immutable(ModuleInfo*)[] modules() const nothrow @nogc - { - return _moduleGroup.modules; - } - - @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc - { - return _moduleGroup; - } - - @property inout(void[])[] gcRanges() inout nothrow @nogc - { - return _gcRanges[]; - } - -private: - ModuleGroup _moduleGroup; - void[][] _gcRanges; -} - -shared(bool) conservative; - -void initSections() nothrow @nogc -{ - _sections._moduleGroup = ModuleGroup(getModuleInfos()); - - import rt.sections; - conservative = !scanDataSegPrecisely(); - - if (conservative) - { - _sections._gcRanges = (cast(void[]*) malloc(2 * (void[]).sizeof))[0..2]; - - auto databeg = cast(void*)&_xi_a; - auto dataend = cast(void*)_moduleinfo_array.ptr; - _sections._gcRanges[0] = databeg[0 .. dataend - databeg]; - - // skip module info and CONST segment - auto bssbeg = cast(void*)&_edata; - auto bssend = cast(void*)&_end; - _sections._gcRanges[1] = bssbeg[0 .. bssend - bssbeg]; - } - else - { - size_t count = &_DPend - &_DPbegin; - auto ranges = cast(void[]*) malloc(count * (void[]).sizeof); - size_t r = 0; - void* prev = null; - for (size_t i = 0; i < count; i++) - { - void* addr = (&_DPbegin)[i]; - if (prev + (void*).sizeof == addr) - ranges[r-1] = ranges[r-1].ptr[0 .. ranges[r-1].length + (void*).sizeof]; - else - ranges[r++] = (cast(void**)addr)[0..1]; - prev = addr; - } - _sections._gcRanges = ranges[0..r]; - } -} - -void finiSections() nothrow @nogc -{ - free(_sections._gcRanges.ptr); -} - -void[] initTLSRanges() nothrow @nogc -{ - auto pbeg = cast(void*)&_tlsstart; - auto pend = cast(void*)&_tlsend; - return pbeg[0 .. pend - pbeg]; -} - -void finiTLSRanges(void[] rng) nothrow @nogc -{ -} - -void scanTLSRanges(void[] rng, scope void delegate(void* pbeg, void* pend) nothrow dg) nothrow -{ - if (conservative) - { - dg(rng.ptr, rng.ptr + rng.length); - } - else - { - for (auto p = &_TPbegin; p < &_TPend; ) - { - uint beg = *p++; - uint end = beg + cast(uint)((void*).sizeof); - while (p < &_TPend && *p == end) - { - end += (void*).sizeof; - p++; - } - dg(rng.ptr + beg, rng.ptr + end); - } - } -} - -private: - -__gshared SectionGroup _sections; - -// Windows: this gets initialized by minit.asm -extern(C) __gshared immutable(ModuleInfo*)[] _moduleinfo_array; -extern(C) void _minit() nothrow @nogc; - -immutable(ModuleInfo*)[] getModuleInfos() nothrow @nogc -out (result) -{ - foreach (m; result) - assert(m !is null); -} -body -{ - // _minit directly alters the global _moduleinfo_array - _minit(); - return _moduleinfo_array; -} - -extern(C) -{ - extern __gshared - { - int _xi_a; // &_xi_a just happens to be start of data segment - int _edata; // &_edata is start of BSS segment - int _end; // &_end is past end of BSS - - void* _DPbegin; // first entry in the array of pointers addresses - void* _DPend; // &_DPend points after last entry of array - uint _TPbegin; // first entry in the array of TLS offsets of pointers - uint _TPend; // &_DPend points after last entry of array - } - - extern - { - int _tlsstart; - int _tlsend; - } -} diff --git a/libphobos/libdruntime/gcc/sections/win64.d b/libphobos/libdruntime/gcc/sections/win64.d deleted file mode 100644 index 357940b..0000000 --- a/libphobos/libdruntime/gcc/sections/win64.d +++ /dev/null @@ -1,321 +0,0 @@ -// Win64-specific support for sections. -// Copyright (C) 2019-2021 Free Software Foundation, Inc. - -// GCC is free software; you can redistribute it and/or modify it under -// the terms of the GNU General Public License as published by the Free -// Software Foundation; either version 3, or (at your option) any later -// version. - -// GCC is distributed in the hope that it will be useful, but WITHOUT ANY -// WARRANTY; without even the implied warranty of MERCHANTABILITY or -// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -// for more details. - -// Under Section 7 of GPL version 3, you are granted additional -// permissions described in the GCC Runtime Library Exception, version -// 3.1, as published by the Free Software Foundation. - -// You should have received a copy of the GNU General Public License and -// a copy of the GCC Runtime Library Exception along with this program; -// see the files COPYING3 and COPYING.RUNTIME respectively. If not, see -// . - -module gcc.sections.win64; - -version (CRuntime_Microsoft): - -// debug = PRINTF; -debug(PRINTF) import core.stdc.stdio; -import core.stdc.stdlib : malloc, free; -import rt.deh, rt.minfo; - -struct SectionGroup -{ - static int opApply(scope int delegate(ref SectionGroup) dg) - { - return dg(_sections); - } - - static int opApplyReverse(scope int delegate(ref SectionGroup) dg) - { - return dg(_sections); - } - - @property immutable(ModuleInfo*)[] modules() const nothrow @nogc - { - return _moduleGroup.modules; - } - - @property ref inout(ModuleGroup) moduleGroup() inout nothrow @nogc - { - return _moduleGroup; - } - - version (Win64) - @property immutable(FuncTable)[] ehTables() const nothrow @nogc - { - auto pbeg = cast(immutable(FuncTable)*)&_deh_beg; - auto pend = cast(immutable(FuncTable)*)&_deh_end; - return pbeg[0 .. pend - pbeg]; - } - - @property inout(void[])[] gcRanges() inout nothrow @nogc - { - return _gcRanges[]; - } - -private: - ModuleGroup _moduleGroup; - void[][] _gcRanges; -} - -shared(bool) conservative; - -void initSections() nothrow @nogc -{ - _sections._moduleGroup = ModuleGroup(getModuleInfos()); - - // the ".data" image section includes both object file sections ".data" and ".bss" - void[] dataSection = findImageSection(".data"); - debug(PRINTF) printf("found .data section: [%p,+%llx]\n", dataSection.ptr, - cast(ulong)dataSection.length); - - import rt.sections; - conservative = !scanDataSegPrecisely(); - - if (conservative) - { - _sections._gcRanges = (cast(void[]*) malloc((void[]).sizeof))[0..1]; - _sections._gcRanges[0] = dataSection; - } - else - { - size_t count = &_DP_end - &_DP_beg; - auto ranges = cast(void[]*) malloc(count * (void[]).sizeof); - size_t r = 0; - void* prev = null; - for (size_t i = 0; i < count; i++) - { - auto off = (&_DP_beg)[i]; - if (off == 0) // skip zero entries added by incremental linking - continue; // assumes there is no D-pointer at the very beginning of .data - void* addr = dataSection.ptr + off; - debug(PRINTF) printf(" scan %p\n", addr); - // combine consecutive pointers into single range - if (prev + (void*).sizeof == addr) - ranges[r-1] = ranges[r-1].ptr[0 .. ranges[r-1].length + (void*).sizeof]; - else - ranges[r++] = (cast(void**)addr)[0..1]; - prev = addr; - } - _sections._gcRanges = ranges[0..r]; - } -} - -void finiSections() nothrow @nogc -{ - .free(cast(void*)_sections.modules.ptr); - .free(_sections._gcRanges.ptr); -} - -void[] initTLSRanges() nothrow @nogc -{ - void* pbeg; - void* pend; - // with VS2017 15.3.1, the linker no longer puts TLS segments into a - // separate image section. That way _tls_start and _tls_end no - // longer generate offsets into .tls, but DATA. - // Use the TEB entry to find the start of TLS instead and read the - // length from the TLS directory - version (D_InlineAsm_X86) - { - asm @nogc nothrow - { - mov EAX, _tls_index; - mov ECX, FS:[0x2C]; // _tls_array - mov EAX, [ECX+4*EAX]; - mov pbeg, EAX; - add EAX, [_tls_used+4]; // end - sub EAX, [_tls_used+0]; // start - mov pend, EAX; - } - } - else version (D_InlineAsm_X86_64) - { - asm @nogc nothrow - { - xor RAX, RAX; - mov EAX, _tls_index; - mov RCX, 0x58; - mov RCX, GS:[RCX]; // _tls_array (immediate value causes fixup) - mov RAX, [RCX+8*RAX]; - mov pbeg, RAX; - add RAX, [_tls_used+8]; // end - sub RAX, [_tls_used+0]; // start - mov pend, RAX; - } - } - else - static assert(false, "Architecture not supported."); - - return pbeg[0 .. pend - pbeg]; -} - -void finiTLSRanges(void[] rng) nothrow @nogc -{ -} - -void scanTLSRanges(void[] rng, scope void delegate(void* pbeg, void* pend) nothrow dg) nothrow -{ - if (conservative) - { - dg(rng.ptr, rng.ptr + rng.length); - } - else - { - for (auto p = &_TP_beg; p < &_TP_end; ) - { - uint beg = *p++; - uint end = beg + cast(uint)((void*).sizeof); - while (p < &_TP_end && *p == end) - { - end += (void*).sizeof; - p++; - } - dg(rng.ptr + beg, rng.ptr + end); - } - } -} - -private: -__gshared SectionGroup _sections; - -extern(C) -{ - extern __gshared void* _minfo_beg; - extern __gshared void* _minfo_end; -} - -immutable(ModuleInfo*)[] getModuleInfos() nothrow @nogc -out (result) -{ - foreach (m; result) - assert(m !is null); -} -body -{ - auto m = (cast(immutable(ModuleInfo*)*)&_minfo_beg)[1 .. &_minfo_end - &_minfo_beg]; - /* Because of alignment inserted by the linker, various null pointers - * are there. We need to filter them out. - */ - auto p = m.ptr; - auto pend = m.ptr + m.length; - - // count non-null pointers - size_t cnt; - for (; p < pend; ++p) - { - if (*p !is null) ++cnt; - } - - auto result = (cast(immutable(ModuleInfo)**).malloc(cnt * size_t.sizeof))[0 .. cnt]; - - p = m.ptr; - cnt = 0; - for (; p < pend; ++p) - if (*p !is null) result[cnt++] = *p; - - return cast(immutable)result; -} - -extern(C) -{ - /* Symbols created by the compiler/linker and inserted into the - * object file that 'bracket' sections. - */ - extern __gshared - { - void* __ImageBase; - - void* _deh_beg; - void* _deh_end; - - uint _DP_beg; - uint _DP_end; - uint _TP_beg; - uint _TP_end; - - void*[2] _tls_used; // start, end - int _tls_index; - } -} - -///////////////////////////////////////////////////////////////////// - -enum IMAGE_DOS_SIGNATURE = 0x5A4D; // MZ - -struct IMAGE_DOS_HEADER // DOS .EXE header -{ - ushort e_magic; // Magic number - ushort[29] e_res2; // Reserved ushorts - int e_lfanew; // File address of new exe header -} - -struct IMAGE_FILE_HEADER -{ - ushort Machine; - ushort NumberOfSections; - uint TimeDateStamp; - uint PointerToSymbolTable; - uint NumberOfSymbols; - ushort SizeOfOptionalHeader; - ushort Characteristics; -} - -struct IMAGE_NT_HEADERS -{ - uint Signature; - IMAGE_FILE_HEADER FileHeader; - // optional header follows -} - -struct IMAGE_SECTION_HEADER -{ - char[8] Name = 0; - union { - uint PhysicalAddress; - uint VirtualSize; - } - uint VirtualAddress; - uint SizeOfRawData; - uint PointerToRawData; - uint PointerToRelocations; - uint PointerToLinenumbers; - ushort NumberOfRelocations; - ushort NumberOfLinenumbers; - uint Characteristics; -} - -bool compareSectionName(ref IMAGE_SECTION_HEADER section, string name) nothrow @nogc -{ - if (name[] != section.Name[0 .. name.length]) - return false; - return name.length == 8 || section.Name[name.length] == 0; -} - -void[] findImageSection(string name) nothrow @nogc -{ - if (name.length > 8) // section name from string table not supported - return null; - IMAGE_DOS_HEADER* doshdr = cast(IMAGE_DOS_HEADER*) &__ImageBase; - if (doshdr.e_magic != IMAGE_DOS_SIGNATURE) - return null; - - auto nthdr = cast(IMAGE_NT_HEADERS*)(cast(void*)doshdr + doshdr.e_lfanew); - auto sections = cast(IMAGE_SECTION_HEADER*)(cast(void*)nthdr + IMAGE_NT_HEADERS.sizeof + nthdr.FileHeader.SizeOfOptionalHeader); - for (ushort i = 0; i < nthdr.FileHeader.NumberOfSections; i++) - if (compareSectionName (sections[i], name)) - return (cast(void*)&__ImageBase + sections[i].VirtualAddress)[0 .. sections[i].VirtualSize]; - - return null; -} -- cgit v1.1