/* $Id: kLdrModLX.c 117 2020-03-15 15:23:36Z bird $ */
/** @file
 * kLdr - The Module Interpreter for the Linear eXecutable (LX) Format.
 */

/*
 * Copyright (c) 2006-2007 Knut St. Osmundsen <bird-kStuff-spamix@anduin.net>
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include <k/kLdr.h>
#include "kLdrInternal.h"
#include <k/kLdrFmts/lx.h>


/*******************************************************************************
*   Defined Constants And Macros                                               *
*******************************************************************************/
/** @def KLDRMODLX_STRICT
 * Define KLDRMODLX_STRICT to enabled strict checks in KLDRMODLX. */
#define KLDRMODLX_STRICT 1

/** @def KLDRMODLX_ASSERT
 * Assert that an expression is true when KLDR_STRICT is defined.
 */
#ifdef KLDRMODLX_STRICT
# define KLDRMODLX_ASSERT(expr)  kHlpAssert(expr)
#else
# define KLDRMODLX_ASSERT(expr)  do {} while (0)
#endif


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/**
 * Instance data for the LX module interpreter.
 */
typedef struct KLDRMODLX
{
    /** Pointer to the module. (Follows the section table.) */
    PKLDRMOD                pMod;
    /** Pointer to the user mapping. */
    const void             *pvMapping;
    /** The size of the mapped LX image. */
    KSIZE                   cbMapped;
    /** Reserved flags. */
    KU32                    f32Reserved;

    /** The offset of the LX header. */
    KLDRFOFF                offHdr;
    /** Copy of the LX header. */
    struct e32_exe          Hdr;

    /** Pointer to the loader section.
     * Allocated together with this strcture. */
    const KU8              *pbLoaderSection;
    /** Pointer to the last byte in the loader section. */
    const KU8              *pbLoaderSectionLast;
    /** Pointer to the object table in the loader section. */
    const struct o32_obj   *paObjs;
    /** Pointer to the object page map table in the loader section. */
    const struct o32_map   *paPageMappings;
    /** Pointer to the resource table in the loader section. */
    const struct rsrc32    *paRsrcs;
    /** Pointer to the resident name table in the loader section. */
    const KU8              *pbResNameTab;
    /** Pointer to the entry table in the loader section. */
    const KU8              *pbEntryTab;

    /** Pointer to the non-resident name table. */
    KU8                    *pbNonResNameTab;
    /** Pointer to the last byte in the non-resident name table. */
    const KU8              *pbNonResNameTabLast;

    /** Pointer to the fixup section. */
    KU8                    *pbFixupSection;
    /** Pointer to the last byte in the fixup section. */
    const KU8              *pbFixupSectionLast;
    /** Pointer to the fixup page table within pvFixupSection. */
    const KU32             *paoffPageFixups;
    /** Pointer to the fixup record table within pvFixupSection. */
    const KU8              *pbFixupRecs;
    /** Pointer to the import module name table within pvFixupSection. */
    const KU8              *pbImportMods;
    /** Pointer to the import module name table within pvFixupSection. */
    const KU8              *pbImportProcs;
} KLDRMODLX, *PKLDRMODLX;


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/
static int kldrModLXHasDbgInfo(PKLDRMOD pMod, const void *pvBits);
static int kldrModLXRelocateBits(PKLDRMOD pMod, void *pvBits, KLDRADDR NewBaseAddress, KLDRADDR OldBaseAddress,
                                 PFNKLDRMODGETIMPORT pfnGetImport, void *pvUser);
static int kldrModLXDoCreate(PKRDR pRdr, KLDRFOFF offNewHdr, PKLDRMODLX *ppModLX);
static const KU8 *kldrModLXDoNameTableLookupByOrdinal(const KU8 *pbNameTable, KSSIZE cbNameTable, KU32 iOrdinal);
static int kldrModLXDoNameLookup(PKLDRMODLX pModLX, const char *pchSymbol, KSIZE cchSymbol, KU32 *piSymbol);
static const KU8 *kldrModLXDoNameTableLookupByName(const KU8 *pbNameTable, KSSIZE cbNameTable,
                                                       const char *pchSymbol, KSIZE cchSymbol);
static int kldrModLXDoLoadBits(PKLDRMODLX pModLX, void *pvBits);
static int kldrModLXDoIterDataUnpacking(KU8 *pbDst, const KU8 *pbSrc, int cbSrc);
static int kldrModLXDoIterData2Unpacking(KU8 *pbDst, const KU8 *pbSrc, int cbSrc);
static void kLdrModLXMemCopyW(KU8 *pbDst, const KU8 *pbSrc, int cb);
static int kldrModLXDoProtect(PKLDRMODLX pModLX, void *pvBits, unsigned fUnprotectOrProtect);
static int kldrModLXDoCallDLL(PKLDRMODLX pModLX, void *pvMapping, unsigned uOp, KUPTR uHandle);
static int kldrModLXDoForwarderQuery(PKLDRMODLX pModLX, const struct e32_entry *pEntry,
                                     PFNKLDRMODGETIMPORT pfnGetForwarder, void *pvUser, PKLDRADDR puValue, KU32 *pfKind);
static int kldrModLXDoLoadFixupSection(PKLDRMODLX pModLX);
static int kldrModLXDoReloc(KU8 *pbPage, int off, KLDRADDR PageAddress, const struct r32_rlc *prlc,
                            int iSelector, KLDRADDR uValue, KU32 fKind);


/**
 * Create a loader module instance interpreting the executable image found
 * in the specified file provider instance.
 *
 * @returns 0 on success and *ppMod pointing to a module instance.
 *          On failure, a non-zero OS specific error code is returned.
 * @param   pOps            Pointer to the registered method table.
 * @param   pRdr            The file provider instance to use.
 * @param   fFlags          Flags, MBZ.
 * @param   enmCpuArch      The desired CPU architecture. KCPUARCH_UNKNOWN means
 *                          anything goes, but with a preference for the current
 *                          host architecture.
 * @param   offNewHdr       The offset of the new header in MZ files. -1 if not found.
 * @param   ppMod           Where to store the module instance pointer.
 */
static int kldrModLXCreate(PCKLDRMODOPS pOps, PKRDR pRdr, KU32 fFlags, KCPUARCH enmCpuArch, KLDRFOFF offNewHdr, PPKLDRMOD ppMod)
{
    PKLDRMODLX pModLX;
    int rc;
    K_NOREF(fFlags);

    /*
     * Create the instance data and do a minimal header validation.
     */
    rc = kldrModLXDoCreate(pRdr, offNewHdr, &pModLX);
    if (!rc)
    {
        /*
         * Match up against the requested CPU architecture.
         */
        if (    enmCpuArch == KCPUARCH_UNKNOWN
            ||  pModLX->pMod->enmArch == enmCpuArch)
        {
            pModLX->pMod->pOps = pOps;
            pModLX->pMod->u32Magic = KLDRMOD_MAGIC;
            *ppMod = pModLX->pMod;
            return 0;
        }
        rc = KLDR_ERR_CPU_ARCH_MISMATCH;
    }
    kHlpFree(pModLX);
    return rc;
}


/**
 * Separate function for reading creating the LX module instance to
 * simplify cleanup on failure.
 */
static int kldrModLXDoCreate(PKRDR pRdr, KLDRFOFF offNewHdr, PKLDRMODLX *ppModLX)
{
    struct e32_exe Hdr;
    PKLDRMODLX pModLX;
    PKLDRMOD pMod;
    KSIZE cb;
    KSIZE cchFilename;
    KSIZE offLdrStuff;
    KU32 off, offEnd;
    KU32 i;
    int rc;
    int fCanOptimizeMapping;
    KU32 NextRVA;
    *ppModLX = NULL;

    /*
     * Read the signature and file header.
     */
    rc = kRdrRead(pRdr, &Hdr, sizeof(Hdr), offNewHdr > 0 ? offNewHdr : 0);
    if (rc)
        return rc;
    if (    Hdr.e32_magic[0] != E32MAGIC1
        ||  Hdr.e32_magic[1] != E32MAGIC2)
        return KLDR_ERR_UNKNOWN_FORMAT;

    /* We're not interested in anything but x86 images. */
    if (    Hdr.e32_level != E32LEVEL
        ||  Hdr.e32_border != E32LEBO
        ||  Hdr.e32_worder != E32LEWO
        ||  Hdr.e32_cpu < E32CPU286
        ||  Hdr.e32_cpu > E32CPU486
        ||  Hdr.e32_pagesize != OBJPAGELEN
        )
        return KLDR_ERR_LX_BAD_HEADER;

    /* Some rough sanity checks. */
    offEnd = kRdrSize(pRdr) >= (KLDRFOFF)~(KU32)16 ? ~(KU32)16 : (KU32)kRdrSize(pRdr);
    if (    Hdr.e32_itermap > offEnd
        ||  Hdr.e32_datapage > offEnd
        ||  Hdr.e32_nrestab > offEnd
        ||  Hdr.e32_nrestab + Hdr.e32_cbnrestab > offEnd
        ||  Hdr.e32_ldrsize > offEnd - offNewHdr - sizeof(Hdr)
        ||  Hdr.e32_fixupsize > offEnd - offNewHdr - sizeof(Hdr)
        ||  Hdr.e32_fixupsize + Hdr.e32_ldrsize > offEnd - offNewHdr - sizeof(Hdr))
        return KLDR_ERR_LX_BAD_HEADER;

    /* Verify the loader section. */
    offEnd = Hdr.e32_objtab + Hdr.e32_ldrsize;
    if (Hdr.e32_objtab < sizeof(Hdr))
        return KLDR_ERR_LX_BAD_LOADER_SECTION;
    off = Hdr.e32_objtab + sizeof(struct o32_obj) * Hdr.e32_objcnt;
    if (off > offEnd)
        return KLDR_ERR_LX_BAD_LOADER_SECTION;
    if (    Hdr.e32_objmap
        &&  (Hdr.e32_objmap < off || Hdr.e32_objmap > offEnd))
        return KLDR_ERR_LX_BAD_LOADER_SECTION;
    if (    Hdr.e32_rsrccnt
        && (   Hdr.e32_rsrctab < off
            || Hdr.e32_rsrctab > offEnd
            || Hdr.e32_rsrctab + sizeof(struct rsrc32) * Hdr.e32_rsrccnt > offEnd))
        return KLDR_ERR_LX_BAD_LOADER_SECTION;
    if (    Hdr.e32_restab
        &&  (Hdr.e32_restab < off || Hdr.e32_restab > offEnd - 2))
        return KLDR_ERR_LX_BAD_LOADER_SECTION;
    if (    Hdr.e32_enttab
        &&  (Hdr.e32_enttab < off || Hdr.e32_enttab >= offEnd))
        return KLDR_ERR_LX_BAD_LOADER_SECTION;
    if (    Hdr.e32_dircnt
        && (Hdr.e32_dirtab < off || Hdr.e32_dirtab > offEnd - 2))
        return KLDR_ERR_LX_BAD_LOADER_SECTION;

    /* Verify the fixup section. */
    off = offEnd;
    offEnd = off + Hdr.e32_fixupsize;
    if (    Hdr.e32_fpagetab
        &&  (Hdr.e32_fpagetab < off || Hdr.e32_fpagetab > offEnd))
    {
        /*
         * wlink mixes the fixup section and the loader section.
         */
        off = Hdr.e32_fpagetab;
        offEnd = off + Hdr.e32_fixupsize;
        Hdr.e32_ldrsize = off - Hdr.e32_objtab;
    }
    if (    Hdr.e32_frectab
        &&  (Hdr.e32_frectab < off || Hdr.e32_frectab > offEnd))
        return KLDR_ERR_LX_BAD_FIXUP_SECTION;
    if (    Hdr.e32_impmod
        &&  (Hdr.e32_impmod < off || Hdr.e32_impmod > offEnd || Hdr.e32_impmod + Hdr.e32_impmodcnt > offEnd))
        return KLDR_ERR_LX_BAD_FIXUP_SECTION;
    if (    Hdr.e32_impproc
        &&  (Hdr.e32_impproc < off || Hdr.e32_impproc > offEnd))
        return KLDR_ERR_LX_BAD_FIXUP_SECTION;

    /*
     * Calc the instance size, allocate and initialize it.
     */
    cchFilename = kHlpStrLen(kRdrName(pRdr));
    cb = K_ALIGN_Z(sizeof(KLDRMODLX), 8)
       + K_ALIGN_Z(K_OFFSETOF(KLDRMOD, aSegments[Hdr.e32_objcnt + 1]), 8)
       + K_ALIGN_Z(cchFilename + 1, 8);
    offLdrStuff = cb;
    cb += Hdr.e32_ldrsize + 2; /* +2 for two extra zeros. */
    pModLX = (PKLDRMODLX)kHlpAlloc(cb);
    if (!pModLX)
        return KERR_NO_MEMORY;
    *ppModLX = pModLX;

    /* KLDRMOD */
    pMod = (PKLDRMOD)((KU8 *)pModLX + K_ALIGN_Z(sizeof(KLDRMODLX), 8));
    pMod->pvData = pModLX;
    pMod->pRdr = pRdr;
    pMod->pOps = NULL;      /* set upon success. */
    pMod->cSegments = Hdr.e32_objcnt;
    pMod->cchFilename = (KU32)cchFilename;
    pMod->pszFilename = (char *)K_ALIGN_P(&pMod->aSegments[pMod->cSegments], 8);
    kHlpMemCopy((char *)pMod->pszFilename, kRdrName(pRdr), cchFilename + 1);
    pMod->pszName = NULL; /* finalized further down */
    pMod->cchName = 0;
    pMod->fFlags = 0;
    switch (Hdr.e32_cpu)
    {
        case E32CPU286:
            pMod->enmCpu = KCPU_I80286;
            pMod->enmArch = KCPUARCH_X86_16;
            break;
        case E32CPU386:
            pMod->enmCpu = KCPU_I386;
            pMod->enmArch = KCPUARCH_X86_32;
            break;
        case E32CPU486:
            pMod->enmCpu = KCPU_I486;
            pMod->enmArch = KCPUARCH_X86_32;
            break;
    }
    pMod->enmEndian = KLDRENDIAN_LITTLE;
    pMod->enmFmt = KLDRFMT_LX;
    switch (Hdr.e32_mflags & E32MODMASK)
    {
        case E32MODEXE:
            pMod->enmType = !(Hdr.e32_mflags & E32NOINTFIX)
                ? KLDRTYPE_EXECUTABLE_RELOCATABLE
                : KLDRTYPE_EXECUTABLE_FIXED;
            break;

        case E32MODDLL:
        case E32PROTDLL:
        case E32MODPROTDLL:
            pMod->enmType = !(Hdr.e32_mflags & E32SYSDLL)
                ? KLDRTYPE_SHARED_LIBRARY_RELOCATABLE
                : KLDRTYPE_SHARED_LIBRARY_FIXED;
            break;

        case E32MODPDEV:
        case E32MODVDEV:
            pMod->enmType = KLDRTYPE_SHARED_LIBRARY_RELOCATABLE;
            break;
    }
    pMod->u32Magic = 0;     /* set upon success. */

    /* KLDRMODLX */
    pModLX->pMod = pMod;
    pModLX->pvMapping = 0;
    pModLX->cbMapped = 0;
    pModLX->f32Reserved = 0;

    pModLX->offHdr = offNewHdr >= 0 ? offNewHdr : 0;
    kHlpMemCopy(&pModLX->Hdr, &Hdr, sizeof(Hdr));

    pModLX->pbLoaderSection = (KU8 *)pModLX + offLdrStuff;
    pModLX->pbLoaderSectionLast = pModLX->pbLoaderSection + pModLX->Hdr.e32_ldrsize - 1;
    pModLX->paObjs = NULL;
    pModLX->paPageMappings = NULL;
    pModLX->paRsrcs = NULL;
    pModLX->pbResNameTab = NULL;
    pModLX->pbEntryTab = NULL;

    pModLX->pbNonResNameTab = NULL;
    pModLX->pbNonResNameTabLast = NULL;

    pModLX->pbFixupSection = NULL;
    pModLX->pbFixupSectionLast = NULL;
    pModLX->paoffPageFixups = NULL;
    pModLX->pbFixupRecs = NULL;
    pModLX->pbImportMods = NULL;
    pModLX->pbImportProcs = NULL;

    /*
     * Read the loader data.
     */
    rc = kRdrRead(pRdr, (void *)pModLX->pbLoaderSection, pModLX->Hdr.e32_ldrsize, pModLX->Hdr.e32_objtab + pModLX->offHdr);
    if (rc)
        return rc;
    ((KU8 *)pModLX->pbLoaderSectionLast)[1] = 0;
    ((KU8 *)pModLX->pbLoaderSectionLast)[2] = 0;
    if (pModLX->Hdr.e32_objcnt)
        pModLX->paObjs = (const struct o32_obj *)pModLX->pbLoaderSection;
    if (pModLX->Hdr.e32_objmap)
        pModLX->paPageMappings = (const struct o32_map *)(pModLX->pbLoaderSection + pModLX->Hdr.e32_objmap - pModLX->Hdr.e32_objtab);
    if (pModLX->Hdr.e32_rsrccnt)
        pModLX->paRsrcs = (const struct rsrc32 *)(pModLX->pbLoaderSection + pModLX->Hdr.e32_rsrctab - pModLX->Hdr.e32_objtab);
    if (pModLX->Hdr.e32_restab)
        pModLX->pbResNameTab = pModLX->pbLoaderSection + pModLX->Hdr.e32_restab - pModLX->Hdr.e32_objtab;
    if (pModLX->Hdr.e32_enttab)
        pModLX->pbEntryTab = pModLX->pbLoaderSection + pModLX->Hdr.e32_enttab - pModLX->Hdr.e32_objtab;

    /*
     * Get the soname from the resident name table.
     * Very convenient that it's the 0 ordinal, because then we get a
     * free string terminator.
     * (The table entry consists of a pascal string followed by a 16-bit ordinal.)
     */
    if (pModLX->pbResNameTab)
        pMod->pszName = (const char *)kldrModLXDoNameTableLookupByOrdinal(pModLX->pbResNameTab,
                                                                          pModLX->pbLoaderSectionLast - pModLX->pbResNameTab + 1,
                                                                          0);
    if (!pMod->pszName)
        return KLDR_ERR_LX_NO_SONAME;
    pMod->cchName = *(const KU8 *)pMod->pszName++;
    if (pMod->cchName != kHlpStrLen(pMod->pszName))
        return KLDR_ERR_LX_BAD_SONAME;

    /*
     * Quick validation of the object table.
     */
    cb = 0;
    for (i = 0; i < pMod->cSegments; i++)
    {
        if (pModLX->paObjs[i].o32_base & (OBJPAGELEN - 1))
            return KLDR_ERR_LX_BAD_OBJECT_TABLE;
        if (pModLX->paObjs[i].o32_base + pModLX->paObjs[i].o32_size <= pModLX->paObjs[i].o32_base)
            return KLDR_ERR_LX_BAD_OBJECT_TABLE;
        if (pModLX->paObjs[i].o32_mapsize > (pModLX->paObjs[i].o32_size + (OBJPAGELEN - 1)))
            return KLDR_ERR_LX_BAD_OBJECT_TABLE;
        if (    pModLX->paObjs[i].o32_mapsize
            &&  (   (KU8 *)&pModLX->paPageMappings[pModLX->paObjs[i].o32_pagemap] > pModLX->pbLoaderSectionLast
                 || (KU8 *)&pModLX->paPageMappings[pModLX->paObjs[i].o32_pagemap + pModLX->paObjs[i].o32_mapsize]
                     > pModLX->pbLoaderSectionLast))
            return KLDR_ERR_LX_BAD_OBJECT_TABLE;
        if (i > 0 && !(pModLX->paObjs[i].o32_flags & OBJRSRC))
        {
            if (pModLX->paObjs[i].o32_base <= pModLX->paObjs[i - 1].o32_base)
                return KLDR_ERR_LX_BAD_OBJECT_TABLE;
            if (pModLX->paObjs[i].o32_base < pModLX->paObjs[i - 1].o32_base + pModLX->paObjs[i - 1].o32_mapsize)
                return KLDR_ERR_LX_BAD_OBJECT_TABLE;
        }
    }

    /*
     * Check if we can optimize the mapping by using a different
     * object alignment. The linker typically uses 64KB alignment,
     * we can easily get away with page alignment in most cases.
     */
    fCanOptimizeMapping = !(Hdr.e32_mflags & (E32NOINTFIX | E32SYSDLL));
    NextRVA = 0;

    /*
     * Setup the KLDRMOD segment array.
     */
    for (i = 0; i < pMod->cSegments; i++)
    {
        /* unused */
        pMod->aSegments[i].pvUser = NULL;
        pMod->aSegments[i].MapAddress = 0;
        pMod->aSegments[i].pchName = NULL;
        pMod->aSegments[i].cchName = 0;
        pMod->aSegments[i].offFile = -1;
        pMod->aSegments[i].cbFile = -1;
        pMod->aSegments[i].SelFlat = 0;
        pMod->aSegments[i].Sel16bit = 0;

        /* flags */
        pMod->aSegments[i].fFlags = 0;
        if (pModLX->paObjs[i].o32_flags & OBJBIGDEF)
            pMod->aSegments[i].fFlags = KLDRSEG_FLAG_16BIT;
        if (pModLX->paObjs[i].o32_flags & OBJALIAS16)
            pMod->aSegments[i].fFlags = KLDRSEG_FLAG_OS2_ALIAS16;
        if (pModLX->paObjs[i].o32_flags & OBJCONFORM)
            pMod->aSegments[i].fFlags = KLDRSEG_FLAG_OS2_CONFORM;
        if (pModLX->paObjs[i].o32_flags & OBJIOPL)
            pMod->aSegments[i].fFlags = KLDRSEG_FLAG_OS2_IOPL;

        /* size and addresses */
        pMod->aSegments[i].Alignment   = OBJPAGELEN;
        pMod->aSegments[i].cb          = pModLX->paObjs[i].o32_size;
        pMod->aSegments[i].LinkAddress = pModLX->paObjs[i].o32_base;
        pMod->aSegments[i].RVA         = NextRVA;
        if (    fCanOptimizeMapping
            ||  i + 1 >= pMod->cSegments
            ||  (pModLX->paObjs[i].o32_flags & OBJRSRC)
            ||  (pModLX->paObjs[i + 1].o32_flags & OBJRSRC))
            pMod->aSegments[i].cbMapped = K_ALIGN_Z(pModLX->paObjs[i].o32_size, OBJPAGELEN);
        else
            pMod->aSegments[i].cbMapped = pModLX->paObjs[i + 1].o32_base - pModLX->paObjs[i].o32_base;
        NextRVA += (KU32)pMod->aSegments[i].cbMapped;

        /* protection */
        switch (  pModLX->paObjs[i].o32_flags
                & (OBJSHARED | OBJREAD | OBJWRITE | OBJEXEC))
        {
            case 0:
            case OBJSHARED:
                pMod->aSegments[i].enmProt = KPROT_NOACCESS;
                break;
            case OBJREAD:
            case OBJREAD | OBJSHARED:
                pMod->aSegments[i].enmProt = KPROT_READONLY;
                break;
            case OBJWRITE:
            case OBJWRITE | OBJREAD:
                pMod->aSegments[i].enmProt = KPROT_WRITECOPY;
                break;
            case OBJWRITE | OBJSHARED:
            case OBJWRITE | OBJSHARED | OBJREAD:
                pMod->aSegments[i].enmProt = KPROT_READWRITE;
                break;
            case OBJEXEC:
            case OBJEXEC | OBJSHARED:
                pMod->aSegments[i].enmProt = KPROT_EXECUTE;
                break;
            case OBJEXEC | OBJREAD:
            case OBJEXEC | OBJREAD | OBJSHARED:
                pMod->aSegments[i].enmProt = KPROT_EXECUTE_READ;
                break;
            case OBJEXEC | OBJWRITE:
            case OBJEXEC | OBJWRITE | OBJREAD:
                pMod->aSegments[i].enmProt = KPROT_EXECUTE_WRITECOPY;
                break;
            case OBJEXEC | OBJWRITE | OBJSHARED:
            case OBJEXEC | OBJWRITE | OBJSHARED | OBJREAD:
                pMod->aSegments[i].enmProt = KPROT_EXECUTE_READWRITE;
                break;
        }
        if ((pModLX->paObjs[i].o32_flags & (OBJREAD | OBJWRITE | OBJEXEC | OBJRSRC)) == OBJRSRC)
            pMod->aSegments[i].enmProt = KPROT_READONLY;
        /*pMod->aSegments[i].f16bit = !(pModLX->paObjs[i].o32_flags & OBJBIGDEF)
        pMod->aSegments[i].fIOPL = !(pModLX->paObjs[i].o32_flags & OBJIOPL)
        pMod->aSegments[i].fConforming = !(pModLX->paObjs[i].o32_flags & OBJCONFORM) */
    }

    /* set the mapping size */
    pModLX->cbMapped = NextRVA;

    /*
     * We're done.
     */
    *ppModLX = pModLX;
    return 0;
}


/** @copydoc KLDRMODOPS::pfnDestroy */
static int kldrModLXDestroy(PKLDRMOD pMod)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    int rc = 0;
    KLDRMODLX_ASSERT(!pModLX->pvMapping);

    if (pMod->pRdr)
    {
        rc = kRdrClose(pMod->pRdr);
        pMod->pRdr = NULL;
    }
    if (pModLX->pbNonResNameTab)
    {
        kHlpFree(pModLX->pbNonResNameTab);
        pModLX->pbNonResNameTab = NULL;
    }
    if (pModLX->pbFixupSection)
    {
        kHlpFree(pModLX->pbFixupSection);
        pModLX->pbFixupSection = NULL;
    }
    pMod->u32Magic = 0;
    pMod->pOps = NULL;
    kHlpFree(pModLX);
    return rc;
}


/**
 * Resolved base address aliases.
 *
 * @param   pModLX          The interpreter module instance
 * @param   pBaseAddress    The base address, IN & OUT.
 */
static void kldrModLXResolveBaseAddress(PKLDRMODLX pModLX, PKLDRADDR pBaseAddress)
{
    if (*pBaseAddress == KLDRMOD_BASEADDRESS_MAP)
        *pBaseAddress = pModLX->pMod->aSegments[0].MapAddress;
    else if (*pBaseAddress == KLDRMOD_BASEADDRESS_LINK)
        *pBaseAddress = pModLX->pMod->aSegments[0].LinkAddress;
}


/** @copydoc kLdrModQuerySymbol */
static int kldrModLXQuerySymbol(PKLDRMOD pMod, const void *pvBits, KLDRADDR BaseAddress, KU32 iSymbol,
                                const char *pchSymbol, KSIZE cchSymbol, const char *pszVersion,
                                PFNKLDRMODGETIMPORT pfnGetForwarder, void *pvUser, PKLDRADDR puValue, KU32 *pfKind)
{
    PKLDRMODLX                  pModLX = (PKLDRMODLX)pMod->pvData;
    KU32                        iOrdinal;
    int                         rc;
    const struct b32_bundle     *pBundle;
    K_NOREF(pvBits);
    K_NOREF(pszVersion);

    /*
     * Give up at once if there is no entry table.
     */
    if (!pModLX->Hdr.e32_enttab)
        return KLDR_ERR_SYMBOL_NOT_FOUND;

    /*
     * Translate the symbol name into an ordinal.
     */
    if (pchSymbol)
    {
        rc = kldrModLXDoNameLookup(pModLX, pchSymbol, cchSymbol, &iSymbol);
        if (rc)
            return rc;
    }

    /*
     * Iterate the entry table.
     * (The entry table is made up of bundles of similar exports.)
     */
    iOrdinal = 1;
    pBundle = (const struct b32_bundle *)pModLX->pbEntryTab;
    while (pBundle->b32_cnt && iOrdinal <= iSymbol)
    {
        static const KSIZE s_cbEntry[] = { 0, 3, 5, 5, 7 };

        /*
         * Check for a hit first.
         */
        iOrdinal += pBundle->b32_cnt;
        if (iSymbol < iOrdinal)
        {
            KU32 offObject;
            const struct e32_entry *pEntry = (const struct e32_entry *)((KUPTR)(pBundle + 1)
                                                                        +   (iSymbol - (iOrdinal - pBundle->b32_cnt))
                                                                          * s_cbEntry[pBundle->b32_type]);

            /*
             * Calculate the return address.
             */
            kldrModLXResolveBaseAddress(pModLX, &BaseAddress);
            switch (pBundle->b32_type)
            {
                /* empty bundles are place holders unused ordinal ranges. */
                case EMPTY:
                    return KLDR_ERR_SYMBOL_NOT_FOUND;

                /* e32_flags + a 16-bit offset. */
                case ENTRY16:
                    offObject = pEntry->e32_variant.e32_offset.offset16;
                    if (pfKind)
                        *pfKind = KLDRSYMKIND_16BIT | KLDRSYMKIND_NO_TYPE;
                    break;

                /* e32_flags + a 16-bit offset + a 16-bit callgate selector. */
                case GATE16:
                    offObject = pEntry->e32_variant.e32_callgate.offset;
                    if (pfKind)
                        *pfKind = KLDRSYMKIND_16BIT | KLDRSYMKIND_CODE;
                    break;

                /* e32_flags + a 32-bit offset. */
                case ENTRY32:
                    offObject = pEntry->e32_variant.e32_offset.offset32;
                    if (pfKind)
                        *pfKind = KLDRSYMKIND_32BIT;
                    break;

                /* e32_flags + 16-bit import module ordinal + a 32-bit procname or ordinal. */
                case ENTRYFWD:
                    return kldrModLXDoForwarderQuery(pModLX, pEntry, pfnGetForwarder, pvUser, puValue, pfKind);

                default:
                    /* anyone actually using TYPEINFO will end up here. */
                    KLDRMODLX_ASSERT(!"Bad bundle type");
                    return KLDR_ERR_LX_BAD_BUNDLE;
            }

            /*
             * Validate the object number and calc the return address.
             */
            if (    pBundle->b32_obj <= 0
                ||  pBundle->b32_obj > pMod->cSegments)
                return KLDR_ERR_LX_BAD_BUNDLE;
            if (puValue)
                *puValue = BaseAddress
                         + offObject
                         + pMod->aSegments[pBundle->b32_obj - 1].RVA;
            return 0;
        }

        /*
         * Skip the bundle.
         */
        if (pBundle->b32_type > ENTRYFWD)
        {
            KLDRMODLX_ASSERT(!"Bad type"); /** @todo figure out TYPEINFO. */
            return KLDR_ERR_LX_BAD_BUNDLE;
        }
        if (pBundle->b32_type == 0)
            pBundle = (const struct b32_bundle *)((const KU8 *)pBundle + 2);
        else
            pBundle = (const struct b32_bundle *)((const KU8 *)(pBundle + 1) + s_cbEntry[pBundle->b32_type] * pBundle->b32_cnt);
    }

    return KLDR_ERR_SYMBOL_NOT_FOUND;
}


/**
 * Do name lookup.
 *
 * @returns See kLdrModQuerySymbol.
 * @param   pModLX      The module to lookup the symbol in.
 * @param   pchSymbol   The symbol to lookup.
 * @param   cchSymbol   The symbol name length.
 * @param   piSymbol    Where to store the symbol ordinal.
 */
static int kldrModLXDoNameLookup(PKLDRMODLX pModLX, const char *pchSymbol, KSIZE cchSymbol, KU32 *piSymbol)
{

    /*
     * First do a hash table lookup.
     */
    /** @todo hash name table for speed. */

    /*
     * Search the name tables.
     */
    const KU8 *pbName = kldrModLXDoNameTableLookupByName(pModLX->pbResNameTab,
                                                         pModLX->pbLoaderSectionLast - pModLX->pbResNameTab + 1,
                                                         pchSymbol, cchSymbol);
    if (!pbName)
    {
        if (!pModLX->pbNonResNameTab)
        {
            /* lazy load it */
            /** @todo non-resident name table. */
        }
        if (pModLX->pbNonResNameTab)
            pbName = kldrModLXDoNameTableLookupByName(pModLX->pbResNameTab,
                                                      pModLX->pbNonResNameTabLast - pModLX->pbResNameTab + 1,
                                                      pchSymbol, cchSymbol);
    }
    if (!pbName)
        return KLDR_ERR_SYMBOL_NOT_FOUND;

    *piSymbol = *(const KU16 *)(pbName + 1 + *pbName);
    return 0;
}


#if 0
/**
 * Hash a symbol using the algorithm from sdbm.
 *
 * The following was is the documenation of the orignal sdbm functions:
 *
 * This algorithm was created for sdbm (a public-domain reimplementation of
 * ndbm) database library. it was found to do well in scrambling bits,
 * causing better distribution of the keys and fewer splits. it also happens
 * to be a good general hashing function with good distribution. the actual
 * function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below
 * is the faster version used in gawk. [there is even a faster, duff-device
 * version] the magic constant 65599 was picked out of thin air while
 * experimenting with different constants, and turns out to be a prime.
 * this is one of the algorithms used in berkeley db (see sleepycat) and
 * elsewhere.
 */
static KU32 kldrModLXDoHash(const char *pchSymbol, KU8 cchSymbol)
{
    KU32 hash = 0;
    int ch;

    while (     cchSymbol-- > 0
           &&   (ch = *(unsigned const char *)pchSymbol++))
        hash = ch + (hash << 6) + (hash << 16) - hash;

    return hash;
}
#endif


/**
 * Lookup a name table entry by name.
 *
 * @returns Pointer to the name table entry if found.
 * @returns NULL if not found.
 * @param   pbNameTable     Pointer to the name table that should be searched.
 * @param   cbNameTable     The size of the name table.
 * @param   pchSymbol       The name of the symbol we're looking for.
 * @param   cchSymbol       The length of the symbol name.
 */
static const KU8 *kldrModLXDoNameTableLookupByName(const KU8 *pbNameTable, KSSIZE cbNameTable,
                                                   const char *pchSymbol, KSIZE cchSymbol)
{
    /*
     * Determin the namelength up front so we can skip anything which doesn't matches the length.
     */
    KU8 cbSymbol8Bit = (KU8)cchSymbol;
    if (cbSymbol8Bit != cchSymbol)
        return NULL; /* too long. */

    /*
     * Walk the name table.
     */
    while (*pbNameTable != 0 && cbNameTable > 0)
    {
        const KU8 cbName = *pbNameTable;

        cbNameTable -= cbName + 1 + 2;
        if (cbNameTable < 0)
            break;

        if (    cbName == cbSymbol8Bit
            &&  !kHlpMemComp(pbNameTable + 1, pchSymbol, cbName))
            return pbNameTable;

        /* next entry */
        pbNameTable += cbName + 1 + 2;
    }

    return NULL;
}


/**
 * Deal with a forwarder entry.
 *
 * @returns See kLdrModQuerySymbol.
 * @param   pModLX          The PE module interpreter instance.
 * @param   pEntry          The forwarder entry.
 * @param   pfnGetForwarder The callback for resolving forwarder symbols. (optional)
 * @param   pvUser          The user argument for the callback.
 * @param   puValue         Where to put the value. (optional)
 * @param   pfKind          Where to put the symbol kind. (optional)
 */
static int kldrModLXDoForwarderQuery(PKLDRMODLX pModLX, const struct e32_entry *pEntry,
                                     PFNKLDRMODGETIMPORT pfnGetForwarder, void *pvUser, PKLDRADDR puValue, KU32 *pfKind)
{
    int rc;
    KU32 iSymbol;
    const char *pchSymbol;
    KU8 cchSymbol;

    if (!pfnGetForwarder)
        return KLDR_ERR_FORWARDER_SYMBOL;

    /*
     * Validate the entry import module ordinal.
     */
    if (    !pEntry->e32_variant.e32_fwd.modord
        ||  pEntry->e32_variant.e32_fwd.modord > pModLX->Hdr.e32_impmodcnt)
        return KLDR_ERR_LX_BAD_FORWARDER;

    /*
     * Figure out the parameters.
     */
    if (pEntry->e32_flags & FWD_ORDINAL)
    {
        iSymbol = pEntry->e32_variant.e32_fwd.value;
        pchSymbol = NULL;                   /* no symbol name. */
        cchSymbol = 0;
    }
    else
    {
        const KU8 *pbName;

        /* load the fixup section if necessary. */
        if (!pModLX->pbImportProcs)
        {
            rc = kldrModLXDoLoadFixupSection(pModLX);
            if (rc)
                return rc;
        }

        /* Make name pointer. */
        pbName = pModLX->pbImportProcs + pEntry->e32_variant.e32_fwd.value;
        if (    pbName >= pModLX->pbFixupSectionLast
            ||  pbName < pModLX->pbFixupSection
            || !*pbName)
            return KLDR_ERR_LX_BAD_FORWARDER;


        /* check for '#' name. */
        if (pbName[1] == '#')
        {
            KU8         cbLeft = *pbName;
            const KU8  *pb = pbName + 1;
            unsigned    uBase;

            /* base detection */
            uBase = 10;
            if (    cbLeft > 1
                &&  pb[1] == '0'
                &&  (pb[2] == 'x' || pb[2] == 'X'))
            {
                uBase = 16;
                pb += 2;
                cbLeft -= 2;
            }

            /* ascii to integer */
            iSymbol = 0;
            while (cbLeft-- > 0)
            {
                /* convert char to digit. */
                unsigned uDigit = *pb++;
                if (uDigit >= '0' && uDigit <= '9')
                    uDigit -= '0';
                else if (uDigit >= 'a' && uDigit <= 'z')
                    uDigit -= 'a' + 10;
                else if (uDigit >= 'A' && uDigit <= 'Z')
                    uDigit -= 'A' + 10;
                else if (!uDigit)
                    break;
                else
                    return KLDR_ERR_LX_BAD_FORWARDER;
                if (uDigit >= uBase)
                    return KLDR_ERR_LX_BAD_FORWARDER;

                /* insert the digit */
                iSymbol *= uBase;
                iSymbol += uDigit;
            }
            if (!iSymbol)
                return KLDR_ERR_LX_BAD_FORWARDER;

            pchSymbol = NULL;               /* no symbol name. */
            cchSymbol = 0;
        }
        else
        {
            pchSymbol = (char *)pbName + 1;
            cchSymbol = *pbName;
            iSymbol = NIL_KLDRMOD_SYM_ORDINAL;
        }
    }

    /*
     * Resolve the forwarder.
     */
    rc = pfnGetForwarder(pModLX->pMod, pEntry->e32_variant.e32_fwd.modord - 1, iSymbol, pchSymbol, cchSymbol, NULL, puValue, pfKind, pvUser);
    if (!rc && pfKind)
        *pfKind |= KLDRSYMKIND_FORWARDER;
    return rc;
}


/**
 * Loads the fixup section from the executable image.
 *
 * The fixup section isn't loaded until it's accessed. It's also freed by kLdrModDone().
 *
 * @returns 0 on success, non-zero kLdr or native status code on failure.
 * @param   pModLX          The PE module interpreter instance.
 */
static int kldrModLXDoLoadFixupSection(PKLDRMODLX pModLX)
{
    int rc;
    KU32 off;
    void *pv;

    pv = kHlpAlloc(pModLX->Hdr.e32_fixupsize);
    if (!pv)
        return KERR_NO_MEMORY;

    off = pModLX->Hdr.e32_objtab + pModLX->Hdr.e32_ldrsize;
    rc = kRdrRead(pModLX->pMod->pRdr, pv, pModLX->Hdr.e32_fixupsize,
                     off + pModLX->offHdr);
    if (!rc)
    {
        pModLX->pbFixupSection = pv;
        pModLX->pbFixupSectionLast = pModLX->pbFixupSection + pModLX->Hdr.e32_fixupsize;
        KLDRMODLX_ASSERT(!pModLX->paoffPageFixups);
        if (pModLX->Hdr.e32_fpagetab)
            pModLX->paoffPageFixups = (const KU32 *)(pModLX->pbFixupSection + pModLX->Hdr.e32_fpagetab - off);
        KLDRMODLX_ASSERT(!pModLX->pbFixupRecs);
        if (pModLX->Hdr.e32_frectab)
            pModLX->pbFixupRecs = pModLX->pbFixupSection + pModLX->Hdr.e32_frectab - off;
        KLDRMODLX_ASSERT(!pModLX->pbImportMods);
        if (pModLX->Hdr.e32_impmod)
            pModLX->pbImportMods = pModLX->pbFixupSection + pModLX->Hdr.e32_impmod - off;
        KLDRMODLX_ASSERT(!pModLX->pbImportProcs);
        if (pModLX->Hdr.e32_impproc)
            pModLX->pbImportProcs = pModLX->pbFixupSection + pModLX->Hdr.e32_impproc - off;
    }
    else
        kHlpFree(pv);
    return rc;
}


/** @copydoc kLdrModEnumSymbols */
static int kldrModLXEnumSymbols(PKLDRMOD pMod, const void *pvBits, KLDRADDR BaseAddress,
                                KU32 fFlags, PFNKLDRMODENUMSYMS pfnCallback, void *pvUser)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    const struct b32_bundle *pBundle;
    KU32 iOrdinal;
    int rc = 0;
    K_NOREF(pvBits);
    K_NOREF(fFlags);

    kldrModLXResolveBaseAddress(pModLX, &BaseAddress);

    /*
     * Enumerate the entry table.
     * (The entry table is made up of bundles of similar exports.)
     */
    iOrdinal = 1;
    pBundle = (const struct b32_bundle *)pModLX->pbEntryTab;
    while (pBundle->b32_cnt && iOrdinal)
    {
        static const KSIZE s_cbEntry[] = { 0, 3, 5, 5, 7 };

        /*
         * Enum the entries in the bundle.
         */
        if (pBundle->b32_type != EMPTY)
        {
            const struct e32_entry *pEntry;
            KSIZE cbEntry;
            KLDRADDR BundleRVA;
            unsigned cLeft;


            /* Validate the bundle. */
            switch (pBundle->b32_type)
            {
                case ENTRY16:
                case GATE16:
                case ENTRY32:
                    if (    pBundle->b32_obj <= 0
                        ||  pBundle->b32_obj > pMod->cSegments)
                        return KLDR_ERR_LX_BAD_BUNDLE;
                    BundleRVA = pMod->aSegments[pBundle->b32_obj - 1].RVA;
                    break;

                case ENTRYFWD:
                    BundleRVA = 0;
                    break;

                default:
                    /* anyone actually using TYPEINFO will end up here. */
                    KLDRMODLX_ASSERT(!"Bad bundle type");
                    return KLDR_ERR_LX_BAD_BUNDLE;
            }

            /* iterate the bundle entries. */
            cbEntry = s_cbEntry[pBundle->b32_type];
            pEntry = (const struct e32_entry *)(pBundle + 1);
            cLeft = pBundle->b32_cnt;
            while (cLeft-- > 0)
            {
                KLDRADDR uValue;
                KU32 fKind;
                int fFoundName;
                const KU8 *pbName;

                /*
                 * Calc the symbol value and kind.
                 */
                switch (pBundle->b32_type)
                {
                    /* e32_flags + a 16-bit offset. */
                    case ENTRY16:
                        uValue = BaseAddress + BundleRVA + pEntry->e32_variant.e32_offset.offset16;
                        fKind = KLDRSYMKIND_16BIT | KLDRSYMKIND_NO_TYPE;
                        break;

                    /* e32_flags + a 16-bit offset + a 16-bit callgate selector. */
                    case GATE16:
                        uValue = BaseAddress + BundleRVA + pEntry->e32_variant.e32_callgate.offset;
                        fKind = KLDRSYMKIND_16BIT | KLDRSYMKIND_CODE;
                        break;

                    /* e32_flags + a 32-bit offset. */
                    case ENTRY32:
                        uValue = BaseAddress + BundleRVA + pEntry->e32_variant.e32_offset.offset32;
                        fKind = KLDRSYMKIND_32BIT;
                        break;

                    /* e32_flags + 16-bit import module ordinal + a 32-bit procname or ordinal. */
                    case ENTRYFWD:
                        uValue = 0; /** @todo implement enumeration of forwarders properly. */
                        fKind = KLDRSYMKIND_FORWARDER;
                        break;

                    default: /* shut up gcc. */
                        uValue = 0;
                        fKind = KLDRSYMKIND_NO_BIT | KLDRSYMKIND_NO_TYPE;
                        break;
                }

                /*
                 * Any symbol names?
                 */
                fFoundName = 0;

                /* resident name table. */
                pbName = pModLX->pbResNameTab;
                if (pbName)
                {
                    do
                    {
                        pbName = kldrModLXDoNameTableLookupByOrdinal(pbName, pModLX->pbLoaderSectionLast - pbName + 1, iOrdinal);
                        if (!pbName)
                            break;
                        fFoundName = 1;
                        rc = pfnCallback(pMod, iOrdinal, (const char *)pbName + 1, *pbName, NULL, uValue, fKind, pvUser);
                        if (rc)
                            return rc;

                        /* skip to the next entry */
                        pbName += 1 + *pbName + 2;
                    } while (pbName < pModLX->pbLoaderSectionLast);
                }

                /* resident name table. */
                pbName = pModLX->pbNonResNameTab;
                /** @todo lazy load the non-resident name table. */
                if (pbName)
                {
                    do
                    {
                        pbName = kldrModLXDoNameTableLookupByOrdinal(pbName, pModLX->pbNonResNameTabLast - pbName + 1, iOrdinal);
                        if (!pbName)
                            break;
                        fFoundName = 1;
                        rc = pfnCallback(pMod, iOrdinal, (const char *)pbName + 1, *pbName, NULL, uValue, fKind, pvUser);
                        if (rc)
                            return rc;

                        /* skip to the next entry */
                        pbName += 1 + *pbName + 2;
                    } while (pbName < pModLX->pbLoaderSectionLast);
                }

                /*
                 * If no names, call once with the ordinal only.
                 */
                if (!fFoundName)
                {
                    rc = pfnCallback(pMod, iOrdinal, NULL, 0, NULL, uValue, fKind, pvUser);
                    if (rc)
                        return rc;
                }

                /* next */
                iOrdinal++;
                pEntry = (const struct e32_entry *)((KUPTR)pEntry + cbEntry);
            }
        }

        /*
         * The next bundle.
         */
        if (pBundle->b32_type > ENTRYFWD)
        {
            KLDRMODLX_ASSERT(!"Bad type"); /** @todo figure out TYPEINFO. */
            return KLDR_ERR_LX_BAD_BUNDLE;
        }
        if (pBundle->b32_type == 0)
            pBundle = (const struct b32_bundle *)((const KU8 *)pBundle + 2);
        else
            pBundle = (const struct b32_bundle *)((const KU8 *)(pBundle + 1) + s_cbEntry[pBundle->b32_type] * pBundle->b32_cnt);
    }

    return 0;
}


/**
 * Lookup a name table entry by ordinal.
 *
 * @returns Pointer to the name table entry if found.
 * @returns NULL if not found.
 * @param   pbNameTable Pointer to the name table that should be searched.
 * @param   cbNameTable The size of the name table.
 * @param   iOrdinal    The ordinal to search for.
 */
static const KU8 *kldrModLXDoNameTableLookupByOrdinal(const KU8 *pbNameTable, KSSIZE cbNameTable, KU32 iOrdinal)
{
    while (*pbNameTable != 0 && cbNameTable > 0)
    {
        const KU8   cbName = *pbNameTable;
        KU32        iName;

        cbNameTable -= cbName + 1 + 2;
        if (cbNameTable < 0)
            break;

        iName = *(pbNameTable + cbName + 1)
              | ((unsigned)*(pbNameTable + cbName + 2) << 8);
        if (iName == iOrdinal)
            return pbNameTable;

        /* next entry */
        pbNameTable += cbName + 1 + 2;
    }

    return NULL;
}


/** @copydoc kLdrModGetImport */
static int kldrModLXGetImport(PKLDRMOD pMod, const void *pvBits, KU32 iImport, char *pszName, KSIZE cchName)
{
    PKLDRMODLX  pModLX = (PKLDRMODLX)pMod->pvData;
    const KU8  *pb;
    int         rc;
    K_NOREF(pvBits);

    /*
     * Validate
     */
    if (iImport >= pModLX->Hdr.e32_impmodcnt)
        return KLDR_ERR_IMPORT_ORDINAL_OUT_OF_BOUNDS;

    /*
     * Lazy loading the fixup section.
     */
    if (!pModLX->pbImportMods)
    {
        rc = kldrModLXDoLoadFixupSection(pModLX);
        if (rc)
            return rc;
    }

    /*
     * Iterate the module import table until we reach the requested import ordinal.
     */
    pb = pModLX->pbImportMods;
    while (iImport-- > 0)
        pb += *pb + 1;

    /*
     * Copy out the result.
     */
    if (*pb < cchName)
    {
        kHlpMemCopy(pszName, pb + 1, *pb);
        pszName[*pb] = '\0';
        rc = 0;
    }
    else
    {
        kHlpMemCopy(pszName, pb + 1, cchName);
        if (cchName)
            pszName[cchName - 1] = '\0';
        rc = KERR_BUFFER_OVERFLOW;
    }

    return rc;
}


/** @copydoc kLdrModNumberOfImports */
static KI32 kldrModLXNumberOfImports(PKLDRMOD pMod, const void *pvBits)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    K_NOREF(pvBits);
    return pModLX->Hdr.e32_impmodcnt;
}


/** @copydoc kLdrModGetStackInfo */
static int kldrModLXGetStackInfo(PKLDRMOD pMod, const void *pvBits, KLDRADDR BaseAddress, PKLDRSTACKINFO pStackInfo)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    const KU32 i = pModLX->Hdr.e32_stackobj;
    K_NOREF(pvBits);

    if (    i
        &&  i <= pMod->cSegments
        &&  pModLX->Hdr.e32_esp <= pMod->aSegments[i - 1].LinkAddress + pMod->aSegments[i - 1].cb
        &&  pModLX->Hdr.e32_stacksize
        &&  pModLX->Hdr.e32_esp - pModLX->Hdr.e32_stacksize >= pMod->aSegments[i - 1].LinkAddress)
    {

        kldrModLXResolveBaseAddress(pModLX, &BaseAddress);
        pStackInfo->LinkAddress = pModLX->Hdr.e32_esp - pModLX->Hdr.e32_stacksize;
        pStackInfo->Address = BaseAddress
                            + pMod->aSegments[i - 1].RVA
                            + pModLX->Hdr.e32_esp - pModLX->Hdr.e32_stacksize - pMod->aSegments[i - 1].LinkAddress;
    }
    else
    {
        pStackInfo->Address = NIL_KLDRADDR;
        pStackInfo->LinkAddress = NIL_KLDRADDR;
    }
    pStackInfo->cbStack = pModLX->Hdr.e32_stacksize;
    pStackInfo->cbStackThread = 0;

    return 0;
}


/** @copydoc kLdrModQueryMainEntrypoint */
static int kldrModLXQueryMainEntrypoint(PKLDRMOD pMod, const void *pvBits, KLDRADDR BaseAddress, PKLDRADDR pMainEPAddress)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    K_NOREF(pvBits);

    /*
     * Convert the address from the header.
     */
    kldrModLXResolveBaseAddress(pModLX, &BaseAddress);
    *pMainEPAddress = pModLX->Hdr.e32_startobj
                   && pModLX->Hdr.e32_startobj <= pMod->cSegments
                   && pModLX->Hdr.e32_eip < pMod->aSegments[pModLX->Hdr.e32_startobj - 1].cb
        ? BaseAddress + pMod->aSegments[pModLX->Hdr.e32_startobj - 1].RVA + pModLX->Hdr.e32_eip
        : NIL_KLDRADDR;
    return 0;
}


/** @copydoc kLdrModEnumDbgInfo */
static int kldrModLXEnumDbgInfo(PKLDRMOD pMod, const void *pvBits, PFNKLDRENUMDBG pfnCallback, void *pvUser)
{
    /*PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;*/
    K_NOREF(pfnCallback);
    K_NOREF(pvUser);

    /*
     * Quit immediately if no debug info.
     */
    if (kldrModLXHasDbgInfo(pMod, pvBits))
        return 0;
#if 0
    /*
     * Read the debug info and look for familiar magics and structures.
     */
    /** @todo */
#endif

    return 0;
}


/** @copydoc kLdrModHasDbgInfo */
static int kldrModLXHasDbgInfo(PKLDRMOD pMod, const void *pvBits)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    K_NOREF(pvBits);

    /*
     * Don't curretnly bother with linkers which doesn't advertise it in the header.
     */
    if (    !pModLX->Hdr.e32_debuginfo
        ||  !pModLX->Hdr.e32_debuglen)
        return KLDR_ERR_NO_DEBUG_INFO;
    return 0;
}


/** @copydoc kLdrModMap */
static int kldrModLXMap(PKLDRMOD pMod)
{
    PKLDRMODLX  pModLX = (PKLDRMODLX)pMod->pvData;
    unsigned    fFixed;
    void       *pvBase;
    int         rc;

    /*
     * Already mapped?
     */
    if (pModLX->pvMapping)
        return KLDR_ERR_ALREADY_MAPPED;

    /*
     * Allocate memory for it.
     */
    /* fixed image? */
    fFixed = pMod->enmType == KLDRTYPE_EXECUTABLE_FIXED
          || pMod->enmType == KLDRTYPE_SHARED_LIBRARY_FIXED;
    if (!fFixed)
        pvBase = NULL;
    else
    {
        pvBase = (void *)(KUPTR)pMod->aSegments[0].LinkAddress;
        if ((KUPTR)pvBase != pMod->aSegments[0].LinkAddress)
            return KLDR_ERR_ADDRESS_OVERFLOW;
    }
    rc = kHlpPageAlloc(&pvBase, pModLX->cbMapped, KPROT_EXECUTE_READWRITE, fFixed);
    if (rc)
        return rc;

    /*
     * Load the bits, apply page protection, and update the segment table.
     */
    rc = kldrModLXDoLoadBits(pModLX, pvBase);
    if (!rc)
        rc = kldrModLXDoProtect(pModLX, pvBase, 0 /* protect */);
    if (!rc)
    {
        KU32 i;
        for (i = 0; i < pMod->cSegments; i++)
        {
            if (pMod->aSegments[i].RVA != NIL_KLDRADDR)
                pMod->aSegments[i].MapAddress = (KUPTR)pvBase + (KUPTR)pMod->aSegments[i].RVA;
        }
        pModLX->pvMapping = pvBase;
    }
    else
        kHlpPageFree(pvBase, pModLX->cbMapped);
    return rc;
}


/**
 * Loads the LX pages into the specified memory mapping.
 *
 * @returns 0 on success.
 * @returns non-zero kLdr or OS status code on failure.
 *
 * @param   pModLX  The LX module interpreter instance.
 * @param   pvBits  Where to load the bits.
 */
static int kldrModLXDoLoadBits(PKLDRMODLX pModLX, void *pvBits)
{
    const PKRDR pRdr = pModLX->pMod->pRdr;
    KU8 *pbTmpPage = NULL;
    int rc = 0;
    KU32 i;

    /*
     * Iterate the segments.
     */
    for (i = 0; i < pModLX->Hdr.e32_objcnt; i++)
    {
        const struct o32_obj * const pObj = &pModLX->paObjs[i];
        const KU32      cPages = (KU32)(pModLX->pMod->aSegments[i].cbMapped / OBJPAGELEN);
        KU32            iPage;
        KU8            *pbPage = (KU8 *)pvBits + (KUPTR)pModLX->pMod->aSegments[i].RVA;

        /*
         * Iterate the page map pages.
         */
        for (iPage = 0; !rc && iPage < pObj->o32_mapsize; iPage++, pbPage += OBJPAGELEN)
        {
            const struct o32_map *pMap = &pModLX->paPageMappings[iPage + pObj->o32_pagemap - 1];
            switch (pMap->o32_pageflags)
            {
                case VALID:
                    if (pMap->o32_pagesize == OBJPAGELEN)
                        rc = kRdrRead(pRdr, pbPage, OBJPAGELEN,
                                         pModLX->Hdr.e32_datapage + (pMap->o32_pagedataoffset << pModLX->Hdr.e32_pageshift));
                    else if (pMap->o32_pagesize < OBJPAGELEN)
                    {
                        rc = kRdrRead(pRdr, pbPage, pMap->o32_pagesize,
                                         pModLX->Hdr.e32_datapage + (pMap->o32_pagedataoffset << pModLX->Hdr.e32_pageshift));
                        kHlpMemSet(pbPage + pMap->o32_pagesize, 0, OBJPAGELEN - pMap->o32_pagesize);
                    }
                    else
                        rc = KLDR_ERR_LX_BAD_PAGE_MAP;
                    break;

                case ITERDATA:
                case ITERDATA2:
                    /* make sure we've got a temp page .*/
                    if (!pbTmpPage)
                    {
                        pbTmpPage = kHlpAlloc(OBJPAGELEN + 256);
                        if (!pbTmpPage)
                            break;
                    }
                    /* validate the size. */
                    if (pMap->o32_pagesize > OBJPAGELEN + 252)
                    {
                        rc = KLDR_ERR_LX_BAD_PAGE_MAP;
                        break;
                    }

                    /* read it and ensure 4 extra zero bytes. */
                    rc = kRdrRead(pRdr, pbTmpPage, pMap->o32_pagesize,
                                     pModLX->Hdr.e32_datapage + (pMap->o32_pagedataoffset << pModLX->Hdr.e32_pageshift));
                    if (rc)
                        break;
                    kHlpMemSet(pbTmpPage + pMap->o32_pagesize, 0, 4);

                    /* unpack it into the image page. */
                    if (pMap->o32_pageflags == ITERDATA2)
                        rc = kldrModLXDoIterData2Unpacking(pbPage, pbTmpPage, pMap->o32_pagesize);
                    else
                        rc = kldrModLXDoIterDataUnpacking(pbPage, pbTmpPage, pMap->o32_pagesize);
                    break;

                case INVALID: /* we're probably not dealing correctly with INVALID pages... */
                case ZEROED:
                    kHlpMemSet(pbPage, 0, OBJPAGELEN);
                    break;

                case RANGE:
                    KLDRMODLX_ASSERT(!"RANGE");
                    /* Falls through. */
                default:
                    rc = KLDR_ERR_LX_BAD_PAGE_MAP;
                    break;
            }
        }
        if (rc)
            break;

        /*
         * Zero the remaining pages.
         */
        if (iPage < cPages)
            kHlpMemSet(pbPage, 0, (cPages - iPage) * OBJPAGELEN);
    }

    if (pbTmpPage)
        kHlpFree(pbTmpPage);
    return rc;
}


/**
 * Unpacks iterdata (aka EXEPACK).
 *
 * @returns 0 on success, non-zero kLdr status code on failure.
 * @param   pbDst       Where to put the uncompressed data. (Assumes OBJPAGELEN size.)
 * @param   pbSrc       The compressed source data.
 * @param   cbSrc       The file size of the compressed data. The source buffer
 *                      contains 4 additional zero bytes.
 */
static int kldrModLXDoIterDataUnpacking(KU8 *pbDst, const KU8 *pbSrc, int cbSrc)
{
    const struct LX_Iter   *pIter = (const struct LX_Iter *)pbSrc;
    int                     cbDst = OBJPAGELEN;

    /* Validate size of data. */
    if (cbSrc >= (int)OBJPAGELEN - 2)
        return KLDR_ERR_LX_BAD_ITERDATA;

    /*
     * Expand the page.
     */
    while (cbSrc > 0 && pIter->LX_nIter)
    {
        if (pIter->LX_nBytes == 1)
        {
            /*
             * Special case - one databyte.
             */
            cbDst -= pIter->LX_nIter;
            if (cbDst < 0)
                return KLDR_ERR_LX_BAD_ITERDATA;

            cbSrc -= 4 + 1;
            if (cbSrc < -4)
                return KLDR_ERR_LX_BAD_ITERDATA;

            kHlpMemSet(pbDst, pIter->LX_Iterdata, pIter->LX_nIter);
            pbDst += pIter->LX_nIter;
            pIter++;
        }
        else
        {
            /*
             * General.
             */
            int i;

            cbDst -= pIter->LX_nIter * pIter->LX_nBytes;
            if (cbDst < 0)
                return KLDR_ERR_LX_BAD_ITERDATA;

            cbSrc -= 4 + pIter->LX_nBytes;
            if (cbSrc < -4)
                return KLDR_ERR_LX_BAD_ITERDATA;

            for (i = pIter->LX_nIter; i > 0; i--, pbDst += pIter->LX_nBytes)
                kHlpMemCopy(pbDst, &pIter->LX_Iterdata, pIter->LX_nBytes);
            pIter   = (struct LX_Iter *)((char*)pIter + 4 + pIter->LX_nBytes);
        }
    }

    /*
     * Zero remainder of the page.
     */
    if (cbDst > 0)
        kHlpMemSet(pbDst, 0, cbDst);

    return 0;
}


/**
 * Unpacks iterdata (aka EXEPACK).
 *
 * @returns 0 on success, non-zero kLdr status code on failure.
 * @param   pbDst       Where to put the uncompressed data. (Assumes OBJPAGELEN size.)
 * @param   pbSrc       The compressed source data.
 * @param   cbSrc       The file size of the compressed data. The source buffer
 *                      contains 4 additional zero bytes.
 */
static int kldrModLXDoIterData2Unpacking(KU8 *pbDst, const KU8 *pbSrc, int cbSrc)
{
    int cbDst = OBJPAGELEN;

    while (cbSrc > 0)
    {
        /*
         * Bit 0 and 1 is the encoding type.
         */
        switch (*pbSrc & 0x03)
        {
            /*
             *
             *  0  1  2  3  4  5  6  7
             *  type  |              |
             *        ----------------
             *             cb         <cb bytes of data>
             *
             * Bits 2-7 is, if not zero, the length of an uncompressed run
             * starting at the following byte.
             *
             *  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
             *  type  |              |  |                    | |                     |
             *        ----------------  ---------------------- -----------------------
             *             zero                 cb                 char to multiply
             *
             * If the bits are zero, the following two bytes describes a 1 byte interation
             * run. First byte is count, second is the byte to copy. A count of zero is
             * means end of data, and we simply stops. In that case the rest of the data
             * should be zero.
             */
            case 0:
            {
                if (*pbSrc)
                {
                    const int cb = *pbSrc >> 2;
                    cbDst -= cb;
                    if (cbDst < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    cbSrc -= cb + 1;
                    if (cbSrc < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    kHlpMemCopy(pbDst, ++pbSrc, cb);
                    pbDst += cb;
                    pbSrc += cb;
                }
                else if (cbSrc < 2)
                    return KLDR_ERR_LX_BAD_ITERDATA2;
                else
                {
                    const int cb = pbSrc[1];
                    if (!cb)
                        goto l_endloop;
                    cbDst -= cb;
                    if (cbDst < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    cbSrc -= 3;
                    if (cbSrc < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    kHlpMemSet(pbDst, pbSrc[2], cb);
                    pbDst += cb;
                    pbSrc += 3;
                }
                break;
            }


            /*
             *  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
             *  type  |  |  |     |  |                       |
             *        ----  -------  -------------------------
             *        cb1   cb2 - 3          offset            <cb1 bytes of data>
             *
             * Two bytes layed out as described above, followed by cb1 bytes of data to be copied.
             * The cb2(+3) and offset describes an amount of data to be copied from the expanded
             * data relative to the current position. The data copied as you would expect it to be.
             */
            case 1:
            {
                cbSrc -= 2;
                if (cbSrc < 0)
                    return KLDR_ERR_LX_BAD_ITERDATA2;
                else
                {
                    const unsigned  off = ((unsigned)pbSrc[1] << 1) | (*pbSrc >> 7);
                    const int       cb1 = (*pbSrc >> 2) & 3;
                    const int       cb2 = ((*pbSrc >> 4) & 7) + 3;

                    pbSrc += 2;
                    cbSrc -= cb1;
                    if (cbSrc < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    cbDst -= cb1;
                    if (cbDst < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    kHlpMemCopy(pbDst, pbSrc, cb1);
                    pbDst += cb1;
                    pbSrc += cb1;

                    if (off > OBJPAGELEN - (unsigned)cbDst)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    cbDst -= cb2;
                    if (cbDst < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    kHlpMemMove(pbDst, pbDst - off, cb2);
                    pbDst += cb2;
                }
                break;
            }


            /*
             *  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
             *  type  |  |  |                                |
             *        ----  ----------------------------------
             *       cb-3               offset
             *
             * Two bytes layed out as described above.
             * The cb(+3) and offset describes an amount of data to be copied from the expanded
             * data relative to the current position.
             *
             * If offset == 1 the data is not copied as expected, but in the memcpyw manner.
             */
            case 2:
            {
                cbSrc -= 2;
                if (cbSrc < 0)
                    return KLDR_ERR_LX_BAD_ITERDATA2;
                else
                {
                    const unsigned  off = ((unsigned)pbSrc[1] << 4) | (*pbSrc >> 4);
                    const int       cb = ((*pbSrc >> 2) & 3) + 3;

                    pbSrc += 2;
                    if (off > OBJPAGELEN - (unsigned)cbDst)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    cbDst -= cb;
                    if (cbDst < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    kLdrModLXMemCopyW(pbDst, pbDst - off, cb);
                    pbDst += cb;
                }
                break;
            }


            /*
             *  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
             *  type  |        |  |              |  |                                |
             *        ----------  ----------------  ----------------------------------
             *           cb1            cb2                      offset                <cb1 bytes of data>
             *
             * Three bytes layed out as described above, followed by cb1 bytes of data to be copied.
             * The cb2 and offset describes an amount of data to be copied from the expanded
             * data relative to the current position.
             *
             * If offset == 1 the data is not copied as expected, but in the memcpyw manner.
             */
            case 3:
            {
                cbSrc -= 3;
                if (cbSrc < 0)
                    return KLDR_ERR_LX_BAD_ITERDATA2;
                else
                {
                    const int       cb1 = (*pbSrc >> 2) & 0xf;
                    const int       cb2 = ((pbSrc[1] & 0xf) << 2) | (*pbSrc >> 6);
                    const unsigned  off = ((unsigned)pbSrc[2] << 4) | (pbSrc[1] >> 4);

                    pbSrc += 3;
                    cbSrc -= cb1;
                    if (cbSrc < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    cbDst -= cb1;
                    if (cbDst < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    kHlpMemCopy(pbDst, pbSrc, cb1);
                    pbDst += cb1;
                    pbSrc += cb1;

                    if (off > OBJPAGELEN - (unsigned)cbDst)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    cbDst -= cb2;
                    if (cbDst < 0)
                        return KLDR_ERR_LX_BAD_ITERDATA2;
                    kLdrModLXMemCopyW(pbDst, pbDst - off, cb2);
                    pbDst += cb2;
                }
                break;
            }
        } /* type switch. */
    } /* unpack loop */

l_endloop:


    /*
     * Zero remainder of the page.
     */
    if (cbDst > 0)
        kHlpMemSet(pbDst, 0, cbDst);

    return 0;
}


/**
 * Special memcpy employed by the iterdata2 algorithm.
 *
 * Emulate a 16-bit memcpy (copying 16-bit at a time) and the effects this
 * has if src is very close to the destination.
 *
 * @param   pbDst   Destination pointer.
 * @param   pbSrc   Source pointer. Will always be <= pbDst.
 * @param   cb      Amount of data to be copied.
 * @remark  This assumes that unaligned word and dword access is fine.
 */
static void kLdrModLXMemCopyW(KU8 *pbDst, const KU8 *pbSrc, int cb)
{
    switch (pbDst - pbSrc)
    {
        case 0:
        case 1:
        case 2:
        case 3:
            /* 16-bit copy (unaligned) */
            if (cb & 1)
                *pbDst++ = *pbSrc++;
            for (cb >>= 1; cb > 0; cb--, pbDst += 2, pbSrc += 2)
                *(KU16 *)pbDst = *(const KU16 *)pbSrc;
            break;

        default:
            /* 32-bit copy (unaligned) */
            if (cb & 1)
                *pbDst++ = *pbSrc++;
            if (cb & 2)
            {
                *(KU16 *)pbDst = *(const KU16 *)pbSrc;
                pbDst += 2;
                pbSrc += 2;
            }
            for (cb >>= 2; cb > 0; cb--, pbDst += 4, pbSrc += 4)
                *(KU32 *)pbDst = *(const KU32 *)pbSrc;
            break;
    }
}


/**
 * Unprotects or protects the specified image mapping.
 *
 * @returns 0 on success.
 * @returns non-zero kLdr or OS status code on failure.
 *
 * @param   pModLX  The LX module interpreter instance.
 * @param   pvBits  The mapping to protect.
 * @param   UnprotectOrProtect  If 1 unprotect (i.e. make all writable), otherwise
 *          protect according to the object table.
 */
static int kldrModLXDoProtect(PKLDRMODLX pModLX, void *pvBits, unsigned fUnprotectOrProtect)
{
    KU32 i;
    PKLDRMOD pMod = pModLX->pMod;

    /*
     * Change object protection.
     */
    for (i = 0; i < pMod->cSegments; i++)
    {
        int rc;
        void *pv;
        KPROT enmProt;

        /* calc new protection. */
        enmProt = pMod->aSegments[i].enmProt;
        if (fUnprotectOrProtect)
        {
            switch (enmProt)
            {
                case KPROT_NOACCESS:
                case KPROT_READONLY:
                case KPROT_READWRITE:
                case KPROT_WRITECOPY:
                    enmProt = KPROT_READWRITE;
                    break;
                case KPROT_EXECUTE:
                case KPROT_EXECUTE_READ:
                case KPROT_EXECUTE_READWRITE:
                case KPROT_EXECUTE_WRITECOPY:
                    enmProt = KPROT_EXECUTE_READWRITE;
                    break;
                default:
                    KLDRMODLX_ASSERT(!"bad enmProt");
                    return -1;
            }
        }
        else
        {
            /* copy on write -> normal write. */
            if (enmProt == KPROT_EXECUTE_WRITECOPY)
                enmProt = KPROT_EXECUTE_READWRITE;
            else if (enmProt == KPROT_WRITECOPY)
                enmProt = KPROT_READWRITE;
        }


        /* calc the address and set page protection. */
        pv = (KU8 *)pvBits + pMod->aSegments[i].RVA;

        rc = kHlpPageProtect(pv, pMod->aSegments[i].cbMapped, enmProt);
        if (rc)
            break;

        /** @todo the gap page should be marked NOACCESS! */
    }

    return 0;
}


/** @copydoc kLdrModUnmap */
static int kldrModLXUnmap(PKLDRMOD pMod)
{
    PKLDRMODLX  pModLX = (PKLDRMODLX)pMod->pvData;
    KU32        i;
    int         rc;

    /*
     * Mapped?
     */
    if (!pModLX->pvMapping)
        return KLDR_ERR_NOT_MAPPED;

    /*
     * Free the mapping and update the segments.
     */
    rc = kHlpPageFree((void *)pModLX->pvMapping, pModLX->cbMapped);
    KLDRMODLX_ASSERT(!rc);
    pModLX->pvMapping = NULL;

    for (i = 0; i < pMod->cSegments; i++)
        pMod->aSegments[i].MapAddress = 0;

    return rc;
}


/** @copydoc kLdrModAllocTLS */
static int kldrModLXAllocTLS(PKLDRMOD pMod, void *pvMapping)
{
    PKLDRMODLX  pModLX = (PKLDRMODLX)pMod->pvData;

    /* no tls, just do the error checking. */
    if (   pvMapping == KLDRMOD_INT_MAP
        && pModLX->pvMapping)
        return KLDR_ERR_NOT_MAPPED;
    return 0;
}


/** @copydoc kLdrModFreeTLS */
static void kldrModLXFreeTLS(PKLDRMOD pMod, void *pvMapping)
{
    /* no tls. */
    K_NOREF(pMod);
    K_NOREF(pvMapping);

}


/** @copydoc kLdrModReload */
static int kldrModLXReload(PKLDRMOD pMod)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    int rc, rc2;

    /*
     * Mapped?
     */
    if (!pModLX->pvMapping)
        return KLDR_ERR_NOT_MAPPED;

    /*
     * Before doing anything we'll have to make all pages writable.
     */
    rc = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 1 /* unprotect */);
    if (rc)
        return rc;

    /*
     * Load the bits again.
     */
    rc = kldrModLXDoLoadBits(pModLX, (void *)pModLX->pvMapping);

    /*
     * Restore protection.
     */
    rc2 = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 0 /* protect */);
    if (!rc && rc2)
        rc = rc2;
    return rc;
}


/** @copydoc kLdrModFixupMapping */
static int kldrModLXFixupMapping(PKLDRMOD pMod, PFNKLDRMODGETIMPORT pfnGetImport, void *pvUser)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    int rc, rc2;

    /*
     * Mapped?
     */
    if (!pModLX->pvMapping)
        return KLDR_ERR_NOT_MAPPED;

    /*
     * Before doing anything we'll have to make all pages writable.
     */
    rc = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 1 /* unprotect */);
    if (rc)
        return rc;

    /*
     * Apply fixups and resolve imports.
     */
    rc = kldrModLXRelocateBits(pMod, (void *)pModLX->pvMapping, (KUPTR)pModLX->pvMapping,
                               pMod->aSegments[0].LinkAddress, pfnGetImport, pvUser);

    /*
     * Restore protection.
     */
    rc2 = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 0 /* protect */);
    if (!rc && rc2)
        rc = rc2;
    return rc;
}


/** @copydoc kLdrModCallInit */
static int kldrModLXCallInit(PKLDRMOD pMod, void *pvMapping, KUPTR uHandle)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    int rc;

    /*
     * Mapped?
     */
    if (pvMapping == KLDRMOD_INT_MAP)
    {
        pvMapping = (void *)pModLX->pvMapping;
        if (!pvMapping)
            return KLDR_ERR_NOT_MAPPED;
    }

    /*
     * Do TLS callbacks first and then call the init/term function if it's a DLL.
     */
    if ((pModLX->Hdr.e32_mflags & E32MODMASK) == E32MODDLL)
        rc = kldrModLXDoCallDLL(pModLX, pvMapping, 0 /* attach */, uHandle);
    else
        rc = 0;
    return rc;
}


/**
 * Call the DLL entrypoint.
 *
 * @returns 0 on success.
 * @returns KLDR_ERR_MODULE_INIT_FAILED  or KLDR_ERR_THREAD_ATTACH_FAILED on failure.
 * @param   pModLX          The LX module interpreter instance.
 * @param   pvMapping       The module mapping to use (resolved).
 * @param   uOp             The operation (DLL_*).
 * @param   uHandle         The module handle to present.
 */
static int kldrModLXDoCallDLL(PKLDRMODLX pModLX, void *pvMapping, unsigned uOp, KUPTR uHandle)
{
    int rc;

    /*
     * If no entrypoint there isn't anything to be done.
     */
    if (    !pModLX->Hdr.e32_startobj
        ||  pModLX->Hdr.e32_startobj > pModLX->Hdr.e32_objcnt)
        return 0;

    /*
     * Invoke the entrypoint and convert the boolean result to a kLdr status code.
     */
    rc = kldrModLXDoCall((KUPTR)pvMapping
                         + (KUPTR)pModLX->pMod->aSegments[pModLX->Hdr.e32_startobj - 1].RVA
                         + pModLX->Hdr.e32_eip,
                         uHandle, uOp, NULL);
    if (rc)
        rc = 0;
    else if (uOp == 0 /* attach */)
        rc = KLDR_ERR_MODULE_INIT_FAILED;
    else /* detach: ignore failures */
        rc = 0;
    return rc;
}


/**
 * Do a 3 parameter callback.
 *
 * @returns 32-bit callback return.
 * @param   uEntrypoint     The address of the function to be called.
 * @param   uHandle         The first argument, the module handle.
 * @param   uOp             The second argumnet, the reason we're calling.
 * @param   pvReserved      The third argument, reserved argument. (figure this one out)
 */
KI32 kldrModLXDoCall(KUPTR uEntrypoint, KUPTR uHandle, KU32 uOp, void *pvReserved)
{
#if defined(__X86__) || defined(__i386__) || defined(_M_IX86)
    KI32 rc;
/** @todo try/except */

    /*
     * Paranoia.
     */
# ifdef __GNUC__
    __asm__ __volatile__(
        "pushl  %2\n\t"
        "pushl  %1\n\t"
        "pushl  %0\n\t"
        "lea   12(%%esp), %2\n\t"
        "call  *%3\n\t"
        "movl   %2, %%esp\n\t"
        : "=a" (rc)
        : "d" (uOp),
          "S" (0),
          "c" (uEntrypoint),
          "0" (uHandle));
# elif defined(_MSC_VER)
    __asm {
        mov     eax, [uHandle]
        mov     edx, [uOp]
        mov     ecx, 0
        mov     ebx, [uEntrypoint]
        push    edi
        mov     edi, esp
        push    ecx
        push    edx
        push    eax
        call    ebx
        mov     esp, edi
        pop     edi
        mov     [rc], eax
    }
# else
#  error "port me!"
# endif
    K_NOREF(pvReserved);
    return rc;

#else
    K_NOREF(uEntrypoint);
    K_NOREF(uHandle);
    K_NOREF(uOp);
    K_NOREF(pvReserved);
    return KCPU_ERR_ARCH_CPU_NOT_COMPATIBLE;
#endif
}


/** @copydoc kLdrModCallTerm */
static int kldrModLXCallTerm(PKLDRMOD pMod, void *pvMapping, KUPTR uHandle)
{
    PKLDRMODLX  pModLX = (PKLDRMODLX)pMod->pvData;

    /*
     * Mapped?
     */
    if (pvMapping == KLDRMOD_INT_MAP)
    {
        pvMapping = (void *)pModLX->pvMapping;
        if (!pvMapping)
            return KLDR_ERR_NOT_MAPPED;
    }

    /*
     * Do the call.
     */
    if ((pModLX->Hdr.e32_mflags & E32MODMASK) == E32MODDLL)
        kldrModLXDoCallDLL(pModLX, pvMapping, 1 /* detach */, uHandle);

    return 0;
}


/** @copydoc kLdrModCallThread */
static int kldrModLXCallThread(PKLDRMOD pMod, void *pvMapping, KUPTR uHandle, unsigned fAttachingOrDetaching)
{
    /* no thread attach/detach callout. */
    K_NOREF(pMod);
    K_NOREF(pvMapping);
    K_NOREF(uHandle);
    K_NOREF(fAttachingOrDetaching);
    return 0;
}


/** @copydoc kLdrModSize */
static KLDRADDR kldrModLXSize(PKLDRMOD pMod)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    return pModLX->cbMapped;
}


/** @copydoc kLdrModGetBits */
static int kldrModLXGetBits(PKLDRMOD pMod, void *pvBits, KLDRADDR BaseAddress, PFNKLDRMODGETIMPORT pfnGetImport, void *pvUser)
{
    PKLDRMODLX  pModLX = (PKLDRMODLX)pMod->pvData;
    int         rc;

    /*
     * Load the image bits.
     */
    rc = kldrModLXDoLoadBits(pModLX, pvBits);
    if (rc)
        return rc;

    /*
     * Perform relocations.
     */
    return kldrModLXRelocateBits(pMod, pvBits, BaseAddress, pMod->aSegments[0].LinkAddress, pfnGetImport, pvUser);

}


/** @copydoc kLdrModRelocateBits */
static int kldrModLXRelocateBits(PKLDRMOD pMod, void *pvBits, KLDRADDR NewBaseAddress, KLDRADDR OldBaseAddress,
                                 PFNKLDRMODGETIMPORT pfnGetImport, void *pvUser)
{
    PKLDRMODLX pModLX = (PKLDRMODLX)pMod->pvData;
    KU32 iSeg;
    int rc;

    /*
     * Do we need to to *anything*?
     */
    if (    NewBaseAddress == OldBaseAddress
        &&  NewBaseAddress == pModLX->paObjs[0].o32_base
        &&  !pModLX->Hdr.e32_impmodcnt)
        return 0;

    /*
     * Load the fixup section.
     */
    if (!pModLX->pbFixupSection)
    {
        rc = kldrModLXDoLoadFixupSection(pModLX);
        if (rc)
            return rc;
    }

    /*
     * Iterate the segments.
     */
    for (iSeg = 0; iSeg < pModLX->Hdr.e32_objcnt; iSeg++)
    {
        const struct o32_obj * const pObj = &pModLX->paObjs[iSeg];
        KLDRADDR        PageAddress = NewBaseAddress + pModLX->pMod->aSegments[iSeg].RVA;
        KU32            iPage;
        KU8            *pbPage = (KU8 *)pvBits + (KUPTR)pModLX->pMod->aSegments[iSeg].RVA;

        /*
         * Iterate the page map pages.
         */
        for (iPage = 0, rc = 0; !rc && iPage < pObj->o32_mapsize; iPage++, pbPage += OBJPAGELEN, PageAddress += OBJPAGELEN)
        {
            const KU8 * const   pbFixupRecEnd = pModLX->pbFixupRecs + pModLX->paoffPageFixups[iPage + pObj->o32_pagemap];
            const KU8          *pb            = pModLX->pbFixupRecs + pModLX->paoffPageFixups[iPage + pObj->o32_pagemap - 1];
            KLDRADDR            uValue        = NIL_KLDRADDR;
            KU32                fKind         = 0;
            int                 iSelector;

            /* sanity */
            if (pbFixupRecEnd < pb)
                return KLDR_ERR_BAD_FIXUP;
            if (pbFixupRecEnd - 1 > pModLX->pbFixupSectionLast)
                return KLDR_ERR_BAD_FIXUP;
            if (pb < pModLX->pbFixupSection)
                return KLDR_ERR_BAD_FIXUP;

            /*
             * Iterate the fixup record.
             */
            while (pb < pbFixupRecEnd)
            {
                union _rel
                {
                    const KU8 *             pb;
                    const struct r32_rlc   *prlc;
                } u;

                u.pb = pb;
                pb += 3 + (u.prlc->nr_stype & NRCHAIN ? 0 : 1); /* place pch at the 4th member. */

                /*
                 * Figure out the target.
                 */
                switch (u.prlc->nr_flags & NRRTYP)
                {
                    /*
                     * Internal fixup.
                     */
                    case NRRINT:
                    {
                        KU16 iTrgObject;
                        KU32 offTrgObject;

                        /* the object */
                        if (u.prlc->nr_flags & NR16OBJMOD)
                        {
                            iTrgObject = *(const KU16 *)pb;
                            pb += 2;
                        }
                        else
                            iTrgObject = *pb++;
                        iTrgObject--;
                        if (iTrgObject >= pModLX->Hdr.e32_objcnt)
                            return KLDR_ERR_BAD_FIXUP;

                        /* the target */
                        if ((u.prlc->nr_stype & NRSRCMASK) != NRSSEG)
                        {
                            if (u.prlc->nr_flags & NR32BITOFF)
                            {
                                offTrgObject = *(const KU32 *)pb;
                                pb += 4;
                            }
                            else
                            {
                                offTrgObject = *(const KU16 *)pb;
                                pb += 2;
                            }

                            /* calculate the symbol info. */
                            uValue = offTrgObject + NewBaseAddress + pMod->aSegments[iTrgObject].RVA;
                        }
                        else
                            uValue = NewBaseAddress + pMod->aSegments[iTrgObject].RVA;
                        if (    (u.prlc->nr_stype & NRALIAS)
                            ||  (pMod->aSegments[iTrgObject].fFlags & KLDRSEG_FLAG_16BIT))
                            iSelector = pMod->aSegments[iTrgObject].Sel16bit;
                        else
                            iSelector = pMod->aSegments[iTrgObject].SelFlat;
                        fKind = 0;
                        break;
                    }

                    /*
                     * Import by symbol ordinal.
                     */
                    case NRRORD:
                    {
                        KU16 iModule;
                        KU32 iSymbol;

                        /* the module ordinal */
                        if (u.prlc->nr_flags & NR16OBJMOD)
                        {
                            iModule = *(const KU16 *)pb;
                            pb += 2;
                        }
                        else
                            iModule = *pb++;
                        iModule--;
                        if (iModule >= pModLX->Hdr.e32_impmodcnt)
                            return KLDR_ERR_BAD_FIXUP;
#if 1
                        if (u.prlc->nr_flags & NRICHAIN)
                            return KLDR_ERR_BAD_FIXUP;
#endif

                        /* . */
                        if (u.prlc->nr_flags & NR32BITOFF)
                        {
                            iSymbol = *(const KU32 *)pb;
                            pb += 4;
                        }
                        else if (!(u.prlc->nr_flags & NR8BITORD))
                        {
                            iSymbol = *(const KU16 *)pb;
                            pb += 2;
                        }
                        else
                            iSymbol = *pb++;

                        /* resolve it. */
                        rc = pfnGetImport(pMod, iModule, iSymbol, NULL, 0, NULL, &uValue, &fKind, pvUser);
                        if (rc)
                            return rc;
                        iSelector = -1;
                        break;
                    }

                    /*
                     * Import by symbol name.
                     */
                    case NRRNAM:
                    {
                        KU32 iModule;
                        KU16 offSymbol;
                        const KU8 *pbSymbol;

                        /* the module ordinal */
                        if (u.prlc->nr_flags & NR16OBJMOD)
                        {
                            iModule = *(const KU16 *)pb;
                            pb += 2;
                        }
                        else
                            iModule = *pb++;
                        iModule--;
                        if (iModule >= pModLX->Hdr.e32_impmodcnt)
                            return KLDR_ERR_BAD_FIXUP;
#if 1
                        if (u.prlc->nr_flags & NRICHAIN)
                            return KLDR_ERR_BAD_FIXUP;
#endif

                        /* . */
                        if (u.prlc->nr_flags & NR32BITOFF)
                        {
                            offSymbol = *(const KU32 *)pb;
                            pb += 4;
                        }
                        else if (!(u.prlc->nr_flags & NR8BITORD))
                        {
                            offSymbol = *(const KU16 *)pb;
                            pb += 2;
                        }
                        else
                            offSymbol = *pb++;
                        pbSymbol = pModLX->pbImportProcs + offSymbol;
                        if (    pbSymbol < pModLX->pbImportProcs
                            ||  pbSymbol > pModLX->pbFixupSectionLast)
                            return KLDR_ERR_BAD_FIXUP;

                        /* resolve it. */
                        rc = pfnGetImport(pMod, iModule, NIL_KLDRMOD_SYM_ORDINAL, (const char *)pbSymbol + 1, *pbSymbol, NULL,
                                          &uValue, &fKind, pvUser);
                        if (rc)
                            return rc;
                        iSelector = -1;
                        break;
                    }

                    case NRRENT:
                        KLDRMODLX_ASSERT(!"NRRENT");
                        /* Falls through. */
                    default:
                        iSelector = -1;
                        break;
                }

                /* addend */
                if (u.prlc->nr_flags & NRADD)
                {
                    if (u.prlc->nr_flags & NR32BITADD)
                    {
                        uValue += *(const KU32 *)pb;
                        pb += 4;
                    }
                    else
                    {
                        uValue += *(const KU16 *)pb;
                        pb += 2;
                    }
                }


                /*
                 * Deal with the 'source' (i.e. the place that should be modified - very logical).
                 */
                if (!(u.prlc->nr_stype & NRCHAIN))
                {
                    int off = u.prlc->r32_soff;

                    /* common / simple */
                    if (    (u.prlc->nr_stype & NRSRCMASK) == NROFF32
                        &&  off >= 0
                        &&  off <= (int)OBJPAGELEN - 4)
                        *(KU32 *)&pbPage[off] = (KU32)uValue;
                    else if (    (u.prlc->nr_stype & NRSRCMASK) == NRSOFF32
                            &&  off >= 0
                            &&  off <= (int)OBJPAGELEN - 4)
                        *(KU32 *)&pbPage[off] = (KU32)(uValue - (PageAddress + off + 4));
                    else
                    {
                        /* generic */
                        rc = kldrModLXDoReloc(pbPage, off, PageAddress, u.prlc, iSelector, uValue, fKind);
                        if (rc)
                            return rc;
                    }
                }
                else if (!(u.prlc->nr_flags & NRICHAIN))
                {
                    const KI16 *poffSrc = (const KI16 *)pb;
                    KU8 c = u.pb[2];

                    /* common / simple */
                    if ((u.prlc->nr_stype & NRSRCMASK) == NROFF32)
                    {
                        while (c-- > 0)
                        {
                            int off = *poffSrc++;
                            if (off >= 0 && off <= (int)OBJPAGELEN - 4)
                                *(KU32 *)&pbPage[off] = (KU32)uValue;
                            else
                            {
                                rc = kldrModLXDoReloc(pbPage, off, PageAddress, u.prlc, iSelector, uValue, fKind);
                                if (rc)
                                    return rc;
                            }
                        }
                    }
                    else if ((u.prlc->nr_stype & NRSRCMASK) == NRSOFF32)
                    {
                        while (c-- > 0)
                        {
                            int off = *poffSrc++;
                            if (off >= 0 && off <= (int)OBJPAGELEN - 4)
                                *(KU32 *)&pbPage[off] = (KU32)(uValue - (PageAddress + off + 4));
                            else
                            {
                                rc = kldrModLXDoReloc(pbPage, off, PageAddress, u.prlc, iSelector, uValue, fKind);
                                if (rc)
                                    return rc;
                            }
                        }
                    }
                    else
                    {
                        while (c-- > 0)
                        {
                            rc = kldrModLXDoReloc(pbPage, *poffSrc++, PageAddress, u.prlc, iSelector, uValue, fKind);
                            if (rc)
                                return rc;
                        }
                    }
                    pb = (const KU8 *)poffSrc;
                }
                else
                {
                    /* This is a pain because it will require virgin pages on a relocation. */
                    KLDRMODLX_ASSERT(!"NRICHAIN");
                    return KLDR_ERR_LX_NRICHAIN_NOT_SUPPORTED;
                }
            }
        }
    }

    return 0;
}


/**
 * Applies the relocation to one 'source' in a page.
 *
 * This takes care of the more esotic case while the common cases
 * are dealt with seperately.
 *
 * @returns 0 on success, non-zero kLdr status code on failure.
 * @param   pbPage      The page in which to apply the fixup.
 * @param   off         Page relative offset of where to apply the offset.
 * @param   uValue      The target value.
 * @param   fKind       The target kind.
 */
static int kldrModLXDoReloc(KU8 *pbPage, int off, KLDRADDR PageAddress, const struct r32_rlc *prlc,
                            int iSelector, KLDRADDR uValue, KU32 fKind)
{
#pragma pack(1) /* just to be sure */
    union
    {
        KU8         ab[6];
        KU32        off32;
        KU16        off16;
        KU8         off8;
        struct
        {
            KU16    off;
            KU16    Sel;
        }           Far16;
        struct
        {
            KU32    off;
            KU16    Sel;
        }           Far32;
    }               uData;
#pragma pack()
    const KU8      *pbSrc;
    KU8            *pbDst;
    KU8             cb;

    K_NOREF(fKind);

    /*
     * Compose the fixup data.
     */
    switch (prlc->nr_stype & NRSRCMASK)
    {
        case NRSBYT:
            uData.off8 = (KU8)uValue;
            cb = 1;
            break;
        case NRSSEG:
            if (iSelector == -1)
            {
                /* fixme */
            }
            uData.off16 = iSelector;
            cb = 2;
            break;
        case NRSPTR:
            if (iSelector == -1)
            {
                /* fixme */
            }
            uData.Far16.off = (KU16)uValue;
            uData.Far16.Sel = iSelector;
            cb = 4;
            break;
        case NRSOFF:
            uData.off16 = (KU16)uValue;
            cb = 2;
            break;
        case NRPTR48:
            if (iSelector == -1)
            {
                /* fixme */
            }
            uData.Far32.off = (KU32)uValue;
            uData.Far32.Sel = iSelector;
            cb = 6;
            break;
        case NROFF32:
            uData.off32 = (KU32)uValue;
            cb = 4;
            break;
        case NRSOFF32:
            uData.off32 = (KU32)(uValue - (PageAddress + off + 4));
            cb = 4;
            break;
        default:
            return KLDR_ERR_LX_BAD_FIXUP_SECTION; /** @todo fix error, add more checks! */
    }

    /*
     * Apply it. This is sloooow...
     */
    pbSrc = &uData.ab[0];
    pbDst = pbPage + off;
    while (cb-- > 0)
    {
        if (off > (int)OBJPAGELEN)
            break;
        if (off >= 0)
            *pbDst = *pbSrc;
        pbSrc++;
        pbDst++;
    }

    return 0;
}


/**
 * The LX module interpreter method table.
 */
KLDRMODOPS g_kLdrModLXOps =
{
    "LX",
    NULL,
    kldrModLXCreate,
    kldrModLXDestroy,
    kldrModLXQuerySymbol,
    kldrModLXEnumSymbols,
    kldrModLXGetImport,
    kldrModLXNumberOfImports,
    NULL /* can execute one is optional */,
    kldrModLXGetStackInfo,
    kldrModLXQueryMainEntrypoint,
    NULL /* pfnQueryImageUuid */,
    NULL /* fixme */,
    NULL /* fixme */,
    kldrModLXEnumDbgInfo,
    kldrModLXHasDbgInfo,
    kldrModLXMap,
    kldrModLXUnmap,
    kldrModLXAllocTLS,
    kldrModLXFreeTLS,
    kldrModLXReload,
    kldrModLXFixupMapping,
    kldrModLXCallInit,
    kldrModLXCallTerm,
    kldrModLXCallThread,
    kldrModLXSize,
    kldrModLXGetBits,
    kldrModLXRelocateBits,
    NULL /* fixme: pfnMostlyDone */,
    42 /* the end */
};

