// PE/COFF-specific support for sections.
// Copyright (C) 2021-2023 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
// <http://www.gnu.org/licenses/>.
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 core.internal.container.array;
import core.internal.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 return 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;
}