/* $Id: dbgas.cpp $ */
/** @file
 * IPRT - Debug Address Space.
 */

/*
 * Copyright (C) 2009-2012 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */


/*******************************************************************************
*   Header Files                                                               *
*******************************************************************************/
#include <iprt/dbg.h>
#include "internal/iprt.h"

#include <iprt/asm.h>
#include <iprt/avl.h>
#include <iprt/assert.h>
#include <iprt/err.h>
#include <iprt/mem.h>
#include <iprt/param.h>
#include <iprt/string.h>
#include <iprt/semaphore.h>
#include "internal/magics.h"


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/** Pointer to a module table entry. */
typedef struct RTDBGASMOD *PRTDBGASMOD;
/** Pointer to an address space mapping node. */
typedef struct RTDBGASMAP *PRTDBGASMAP;
/** Pointer to a name head. */
typedef struct RTDBGASNAME *PRTDBGASNAME;

/**
 * Module entry.
 */
typedef struct RTDBGASMOD
{
    /** Node core, the module handle is the key. */
    AVLPVNODECORE       Core;
    /** Pointer to the first mapping of the module or a segment within it. */
    PRTDBGASMAP         pMapHead;
    /** Pointer to the next module with an identical name. */
    PRTDBGASMOD         pNextName;
    /** The index into RTDBGASINT::papModules. */
    uint32_t            iOrdinal;
} RTDBGASMOD;

/**
 * An address space mapping, either of a full module or a segment.
 */
typedef struct RTDBGASMAP
{
    /** The AVL node core. Contains the address range. */
    AVLRUINTPTRNODECORE Core;
    /** Pointer to the next mapping of the module. */
    PRTDBGASMAP         pNext;
    /** Pointer to the module. */
    PRTDBGASMOD         pMod;
    /** Which segment in the module.
     * This is NIL_RTDBGSEGIDX when the entire module is mapped. */
    RTDBGSEGIDX         iSeg;
} RTDBGASMAP;

/**
 * Name in the address space.
 */
typedef struct RTDBGASNAME
{
    /** The string space node core.*/
    RTSTRSPACECORE      StrCore;
    /** The list of nodes  */
    PRTDBGASMOD         pHead;
} RTDBGASNAME;

/**
 * Debug address space instance.
 */
typedef struct RTDBGASINT
{
    /** Magic value (RTDBGAS_MAGIC). */
    uint32_t            u32Magic;
    /** The number of reference to this address space. */
    uint32_t volatile   cRefs;
    /** Handle of the read-write lock. */
    RTSEMRW             hLock;
    /** Number of modules in the module address space. */
    uint32_t            cModules;
    /** Pointer to the module table.
     * The valid array length is given by cModules. */
    PRTDBGASMOD        *papModules;
    /** AVL tree translating module handles to module entries. */
    AVLPVTREE           ModTree;
    /** AVL tree mapping addresses to modules. */
    AVLRUINTPTRTREE     MapTree;
    /** Names of the modules in the name space. */
    RTSTRSPACE          NameSpace;
    /** The first address the AS. */
    RTUINTPTR           FirstAddr;
    /** The last address in the AS. */
    RTUINTPTR           LastAddr;
    /** The name of the address space. (variable length) */
    char                szName[1];
} RTDBGASINT;
/** Pointer to an a debug address space instance. */
typedef RTDBGASINT *PRTDBGASINT;


/*******************************************************************************
*   Defined Constants And Macros                                               *
*******************************************************************************/
/** Validates an address space handle and returns rc if not valid. */
#define RTDBGAS_VALID_RETURN_RC(pDbgAs, rc) \
    do { \
        AssertPtrReturn((pDbgAs), (rc)); \
        AssertReturn((pDbgAs)->u32Magic == RTDBGAS_MAGIC, (rc)); \
        AssertReturn((pDbgAs)->cRefs > 0, (rc)); \
    } while (0)

/** Locks the address space for reading. */
#define RTDBGAS_LOCK_READ(pDbgAs) \
    do { \
        int rcLock = RTSemRWRequestRead((pDbgAs)->hLock, RT_INDEFINITE_WAIT); \
        AssertRC(rcLock); \
    } while (0)

/** Unlocks the address space after reading. */
#define RTDBGAS_UNLOCK_READ(pDbgAs) \
    do { \
        int rcLock = RTSemRWReleaseRead((pDbgAs)->hLock); \
        AssertRC(rcLock); \
    } while (0)

/** Locks the address space for writing. */
#define RTDBGAS_LOCK_WRITE(pDbgAs) \
    do { \
        int rcLock = RTSemRWRequestWrite((pDbgAs)->hLock, RT_INDEFINITE_WAIT); \
        AssertRC(rcLock); \
    } while (0)

/** Unlocks the address space after writing. */
#define RTDBGAS_UNLOCK_WRITE(pDbgAs) \
    do { \
        int rcLock = RTSemRWReleaseWrite((pDbgAs)->hLock); \
        AssertRC(rcLock); \
    } while (0)


/*******************************************************************************
*   Internal Functions                                                         *
*******************************************************************************/
static void rtDbgAsModuleUnlinkMod(PRTDBGASINT pDbgAs, PRTDBGASMOD pMod);
static void rtDbgAsModuleUnlinkByMap(PRTDBGASINT pDbgAs, PRTDBGASMAP pMap);


/**
 * Creates an empty address space.
 *
 * @returns IPRT status code.
 *
 * @param   phDbgAs         Where to store the address space handle on success.
 * @param   FirstAddr       The first address in the address space.
 * @param   LastAddr        The last address in the address space.
 * @param   pszName         The name of the address space.
 */
RTDECL(int) RTDbgAsCreate(PRTDBGAS phDbgAs, RTUINTPTR FirstAddr, RTUINTPTR LastAddr, const char *pszName)
{
    /*
     * Input validation.
     */
    AssertPtrReturn(phDbgAs, VERR_INVALID_POINTER);
    AssertPtrReturn(pszName, VERR_INVALID_POINTER);
    AssertReturn(FirstAddr < LastAddr, VERR_INVALID_PARAMETER);

    /*
     * Allocate memory for the instance data.
     */
    size_t cchName = strlen(pszName);
    PRTDBGASINT pDbgAs = (PRTDBGASINT)RTMemAllocVar(RT_OFFSETOF(RTDBGASINT, szName[cchName + 1]));
    if (!pDbgAs)
        return VERR_NO_MEMORY;

    /* initialize it. */
    pDbgAs->u32Magic    = RTDBGAS_MAGIC;
    pDbgAs->cRefs       = 1;
    pDbgAs->hLock       = NIL_RTSEMRW;
    pDbgAs->cModules    = 0;
    pDbgAs->papModules  = NULL;
    pDbgAs->ModTree     = NULL;
    pDbgAs->MapTree     = NULL;
    pDbgAs->NameSpace   = NULL;
    pDbgAs->FirstAddr   = FirstAddr;
    pDbgAs->LastAddr    = LastAddr;
    memcpy(pDbgAs->szName, pszName, cchName + 1);
    int rc = RTSemRWCreate(&pDbgAs->hLock);
    if (RT_SUCCESS(rc))
    {
        *phDbgAs = pDbgAs;
        return VINF_SUCCESS;
    }

    pDbgAs->u32Magic = 0;
    RTMemFree(pDbgAs);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsCreate);


/**
 * Variant of RTDbgAsCreate that takes a name format string.
 *
 * @returns IPRT status code.
 *
 * @param   phDbgAs         Where to store the address space handle on success.
 * @param   FirstAddr       The first address in the address space.
 * @param   LastAddr        The last address in the address space.
 * @param   pszNameFmt      The name format of the address space.
 * @param   va              Format arguments.
 */
RTDECL(int) RTDbgAsCreateV(PRTDBGAS phDbgAs, RTUINTPTR FirstAddr, RTUINTPTR LastAddr, const char *pszNameFmt, va_list va)
{
    AssertPtrReturn(pszNameFmt, VERR_INVALID_POINTER);

    char *pszName;
    RTStrAPrintfV(&pszName, pszNameFmt, va);
    if (!pszName)
        return VERR_NO_MEMORY;

    int rc = RTDbgAsCreate(phDbgAs, FirstAddr, LastAddr, pszName);

    RTStrFree(pszName);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsCreateV);


/**
 * Variant of RTDbgAsCreate that takes a name format string.
 *
 * @returns IPRT status code.
 *
 * @param   phDbgAs         Where to store the address space handle on success.
 * @param   FirstAddr       The first address in the address space.
 * @param   LastAddr        The last address in the address space.
 * @param   pszNameFmt      The name format of the address space.
 * @param   ...             Format arguments.
 */
RTDECL(int) RTDbgAsCreateF(PRTDBGAS phDbgAs, RTUINTPTR FirstAddr, RTUINTPTR LastAddr, const char *pszNameFmt, ...)
{
    va_list va;
    va_start(va, pszNameFmt);
    int rc = RTDbgAsCreateV(phDbgAs, FirstAddr, LastAddr, pszNameFmt, va);
    va_end(va);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsCreateF);


/**
 * Callback used by RTDbgAsDestroy to free all mapping nodes.
 *
 * @returns 0
 * @param   pNode           The map node.
 * @param   pvUser          NULL.
 */
static DECLCALLBACK(int) rtDbgAsDestroyMapCallback(PAVLRUINTPTRNODECORE pNode, void *pvUser)
{
    RTMemFree(pNode);
    NOREF(pvUser);
    return 0;
}


/**
 * Callback used by RTDbgAsDestroy to free all name space nodes.
 *
 * @returns 0
 * @param   pStr            The name node.
 * @param   pvUser          NULL.
 */
static DECLCALLBACK(int) rtDbgAsDestroyNameCallback(PRTSTRSPACECORE pStr, void *pvUser)
{
    RTMemFree(pStr);
    NOREF(pvUser);
    return 0;
}


/**
 * Destroys the address space.
 *
 * This means unlinking all the modules it currently contains, potentially
 * causing some or all of them to be destroyed as they are managed by
 * reference counting.
 *
 * @param   pDbgAs          The address space instance to be destroyed.
 */
static void rtDbgAsDestroy(PRTDBGASINT pDbgAs)
{
    /*
     * Mark the address space invalid and release all the modules.
     */
    ASMAtomicWriteU32(&pDbgAs->u32Magic, ~RTDBGAS_MAGIC);

    RTAvlrUIntPtrDestroy(&pDbgAs->MapTree, rtDbgAsDestroyMapCallback, NULL);
    RTStrSpaceDestroy(&pDbgAs->NameSpace, rtDbgAsDestroyNameCallback, NULL);

    uint32_t i = pDbgAs->cModules;
    while (i-- > 0)
    {
        PRTDBGASMOD pMod = pDbgAs->papModules[i];
        AssertPtr(pMod);
        if (VALID_PTR(pMod))
        {
            Assert(pMod->iOrdinal == i);
            RTDbgModRelease((RTDBGMOD)pMod->Core.Key);
            pMod->Core.Key = NIL_RTDBGMOD;
            pMod->iOrdinal = UINT32_MAX;
            RTMemFree(pMod);
        }
        pDbgAs->papModules[i] = NULL;
    }
    RTMemFree(pDbgAs->papModules);
    pDbgAs->papModules = NULL;

    RTMemFree(pDbgAs);
}


/**
 * Retains another reference to the address space.
 *
 * @returns New reference count, UINT32_MAX on invalid handle (asserted).
 *
 * @param   hDbgAs          The address space handle.
 *
 * @remarks Will not take any locks.
 */
RTDECL(uint32_t) RTDbgAsRetain(RTDBGAS hDbgAs)
{
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, UINT32_MAX);
    return ASMAtomicIncU32(&pDbgAs->cRefs);
}
RT_EXPORT_SYMBOL(RTDbgAsRetain);


/**
 * Release a reference to the address space.
 *
 * When the reference count reaches zero, the address space is destroyed.
 * That means unlinking all the modules it currently contains, potentially
 * causing some or all of them to be destroyed as they are managed by
 * reference counting.
 *
 * @returns New reference count, UINT32_MAX on invalid handle (asserted).
 *
 * @param   hDbgAs          The address space handle. The NIL handle is quietly
 *                          ignored and 0 is returned.
 *
 * @remarks Will not take any locks.
 */
RTDECL(uint32_t) RTDbgAsRelease(RTDBGAS hDbgAs)
{
    if (hDbgAs == NIL_RTDBGAS)
        return 0;
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, UINT32_MAX);

    uint32_t cRefs = ASMAtomicDecU32(&pDbgAs->cRefs);
    if (!cRefs)
        rtDbgAsDestroy(pDbgAs);
    return cRefs;
}
RT_EXPORT_SYMBOL(RTDbgAsRelease);


RTDECL(int) RTDbgAsLockExcl(RTDBGAS hDbgAs)
{
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    RTDBGAS_LOCK_WRITE(pDbgAs);
    return VINF_SUCCESS;
}
RT_EXPORT_SYMBOL(RTDbgAsLockExcl);


RTDECL(int) RTDbgAsUnlockExcl(RTDBGAS hDbgAs)
{
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    RTDBGAS_UNLOCK_WRITE(pDbgAs);
    return VINF_SUCCESS;
}
RT_EXPORT_SYMBOL(RTDbgAsUnlockExcl);


/**
 * Gets the name of an address space.
 *
 * @returns read only address space name.
 *          NULL if hDbgAs is invalid.
 *
 * @param   hDbgAs          The address space handle.
 *
 * @remarks Will not take any locks.
 */
RTDECL(const char *) RTDbgAsName(RTDBGAS hDbgAs)
{
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, NULL);
    return pDbgAs->szName;
}
RT_EXPORT_SYMBOL(RTDbgAsName);


/**
 * Gets the first address in an address space.
 *
 * @returns The address.
 *          0 if hDbgAs is invalid.
 *
 * @param   hDbgAs          The address space handle.
 *
 * @remarks Will not take any locks.
 */
RTDECL(RTUINTPTR) RTDbgAsFirstAddr(RTDBGAS hDbgAs)
{
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, 0);
    return pDbgAs->FirstAddr;
}
RT_EXPORT_SYMBOL(RTDbgAsFirstAddr);


/**
 * Gets the last address in an address space.
 *
 * @returns The address.
 *          0 if hDbgAs is invalid.
 *
 * @param   hDbgAs          The address space handle.
 *
 * @remarks Will not take any locks.
 */
RTDECL(RTUINTPTR) RTDbgAsLastAddr(RTDBGAS hDbgAs)
{
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, 0);
    return pDbgAs->LastAddr;
}
RT_EXPORT_SYMBOL(RTDbgAsLastAddr);

/**
 * Gets the number of modules in the address space.
 *
 * This can be used together with RTDbgAsModuleByIndex
 * to enumerate the modules.
 *
 * @returns The number of modules.
 *
 * @param   hDbgAs          The address space handle.
 *
 * @remarks Will not take any locks.
 */
RTDECL(uint32_t) RTDbgAsModuleCount(RTDBGAS hDbgAs)
{
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, 0);
    return pDbgAs->cModules;
}
RT_EXPORT_SYMBOL(RTDbgAsModuleCount);


/**
 * Common worker for RTDbgAsModuleLink and RTDbgAsModuleLinkSeg.
 *
 * @returns IPRT status.
 * @param   pDbgAs          Pointer to the address space instance data.
 * @param   hDbgMod         The module to link.
 * @param   iSeg            The segment to link or NIL if all.
 * @param   Addr            The address we're linking it at.
 * @param   cb              The size of what we're linking.
 * @param   pszName         The name of the module.
 * @param   fFlags          See RTDBGASLINK_FLAGS_*.
 *
 * @remarks The caller must have locked the address space for writing.
 */
int rtDbgAsModuleLinkCommon(PRTDBGASINT pDbgAs, RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg,
                            RTUINTPTR Addr, RTUINTPTR cb, const char *pszName, uint32_t fFlags)
{
    /*
     * Check that the requested space is undisputed.
     */
    for (;;)
    {
        PRTDBGASMAP pAdjMod = (PRTDBGASMAP)RTAvlrUIntPtrGetBestFit(&pDbgAs->MapTree, Addr, false /* fAbove */);
        if (     pAdjMod
            &&   pAdjMod->Core.KeyLast >= Addr)
        {
            if (!(fFlags & RTDBGASLINK_FLAGS_REPLACE))
                return VERR_ADDRESS_CONFLICT;
            rtDbgAsModuleUnlinkByMap(pDbgAs, pAdjMod);
            continue;
        }
        pAdjMod = (PRTDBGASMAP)RTAvlrUIntPtrGetBestFit(&pDbgAs->MapTree, Addr, true /* fAbove */);
        if (     pAdjMod
            &&   pAdjMod->Core.Key <= Addr + cb - 1)
        {
            if (!(fFlags & RTDBGASLINK_FLAGS_REPLACE))
                return VERR_ADDRESS_CONFLICT;
            rtDbgAsModuleUnlinkByMap(pDbgAs, pAdjMod);
            continue;
        }
        break;
    }

    /*
     * First, create or find the module table entry.
     */
    PRTDBGASMOD pMod = (PRTDBGASMOD)RTAvlPVGet(&pDbgAs->ModTree, hDbgMod);
    if (!pMod)
    {
        /*
         * Ok, we need a new entry. Grow the table if necessary.
         */
        if (!(pDbgAs->cModules % 32))
        {
            void *pvNew = RTMemRealloc(pDbgAs->papModules, sizeof(pDbgAs->papModules[0]) * (pDbgAs->cModules + 32));
            if (!pvNew)
                return VERR_NO_MEMORY;
            pDbgAs->papModules = (PRTDBGASMOD *)pvNew;
        }
        pMod = (PRTDBGASMOD)RTMemAlloc(sizeof(*pMod));
        if (!pMod)
            return VERR_NO_MEMORY;
        pMod->Core.Key  = hDbgMod;
        pMod->pMapHead  = NULL;
        pMod->pNextName = NULL;
        if (RT_UNLIKELY(!RTAvlPVInsert(&pDbgAs->ModTree, &pMod->Core)))
        {
            AssertFailed();
            pDbgAs->cModules--;
            RTMemFree(pMod);
            return VERR_INTERNAL_ERROR;
        }
        pMod->iOrdinal = pDbgAs->cModules;
        pDbgAs->papModules[pDbgAs->cModules] = pMod;
        pDbgAs->cModules++;
        RTDbgModRetain(hDbgMod);

        /*
         * Add it to the name space.
         */
        PRTDBGASNAME pName = (PRTDBGASNAME)RTStrSpaceGet(&pDbgAs->NameSpace, pszName);
        if (!pName)
        {
            size_t cchName = strlen(pszName);
            pName = (PRTDBGASNAME)RTMemAlloc(sizeof(*pName) + cchName + 1);
            if (!pName)
            {
                RTDbgModRelease(hDbgMod);
                pDbgAs->cModules--;
                RTAvlPVRemove(&pDbgAs->ModTree, hDbgMod);
                RTMemFree(pMod);
                return VERR_NO_MEMORY;
            }
            pName->StrCore.cchString = cchName;
            pName->StrCore.pszString = (char *)memcpy(pName + 1, pszName, cchName + 1);
            pName->pHead = pMod;
            if (!RTStrSpaceInsert(&pDbgAs->NameSpace, &pName->StrCore))
                AssertFailed();
        }
        else
        {
            /* quick, but unfair. */
            pMod->pNextName = pName->pHead;
            pName->pHead = pMod;
        }
    }

    /*
     * Create a mapping node.
     */
    int rc;
    PRTDBGASMAP pMap = (PRTDBGASMAP)RTMemAlloc(sizeof(*pMap));
    if (pMap)
    {
        pMap->Core.Key      = Addr;
        pMap->Core.KeyLast  = Addr + cb - 1;
        pMap->pMod          = pMod;
        pMap->iSeg          = iSeg;
        if (RTAvlrUIntPtrInsert(&pDbgAs->MapTree, &pMap->Core))
        {
            PRTDBGASMAP *pp = &pMod->pMapHead;
            while (*pp && (*pp)->Core.Key < Addr)
                pp = &(*pp)->pNext;
            pMap->pNext = *pp;
            *pp = pMap;
            return VINF_SUCCESS;
        }

        AssertFailed();
        RTMemFree(pMap);
        rc = VERR_ADDRESS_CONFLICT;
    }
    else
        rc = VERR_NO_MEMORY;

    /*
     * Unlink the module if this was the only mapping.
     */
    if (!pMod->pMapHead)
        rtDbgAsModuleUnlinkMod(pDbgAs, pMod);
    return rc;
}


/**
 * Links a module into the address space at the give address.
 *
 * The size of the mapping is determined using RTDbgModImageSize().
 *
 * @returns IPRT status code.
 * @retval  VERR_OUT_OF_RANGE if the specified address will put the module
 *          outside the address space.
 * @retval  VERR_ADDRESS_CONFLICT if the mapping clashes with existing mappings.
 *
 * @param   hDbgAs          The address space handle.
 * @param   hDbgMod         The module handle of the module to be linked in.
 * @param   ImageAddr       The address to link the module at.
 * @param   fFlags          See RTDBGASLINK_FLAGS_*.
 */
RTDECL(int) RTDbgAsModuleLink(RTDBGAS hDbgAs, RTDBGMOD hDbgMod, RTUINTPTR ImageAddr, uint32_t fFlags)
{
    /*
     * Validate input.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    const char *pszName = RTDbgModName(hDbgMod);
    if (!pszName)
        return VERR_INVALID_HANDLE;
    RTUINTPTR cb = RTDbgModImageSize(hDbgMod);
    if (!cb)
        return VERR_OUT_OF_RANGE;
    if (    ImageAddr           < pDbgAs->FirstAddr
        ||  ImageAddr           > pDbgAs->LastAddr
        ||  ImageAddr + cb - 1  < pDbgAs->FirstAddr
        ||  ImageAddr + cb - 1  > pDbgAs->LastAddr
        ||  ImageAddr + cb - 1  < ImageAddr)
        return VERR_OUT_OF_RANGE;
    AssertReturn(!(fFlags & ~RTDBGASLINK_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);

    /*
     * Invoke worker common with RTDbgAsModuleLinkSeg.
     */
    RTDBGAS_LOCK_WRITE(pDbgAs);
    int rc = rtDbgAsModuleLinkCommon(pDbgAs, hDbgMod, NIL_RTDBGSEGIDX, ImageAddr, cb, pszName, fFlags);
    RTDBGAS_UNLOCK_WRITE(pDbgAs);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsModuleLink);


/**
 * Links a segment into the address space at the give address.
 *
 * The size of the mapping is determined using RTDbgModSegmentSize().
 *
 * @returns IPRT status code.
 * @retval  VERR_OUT_OF_RANGE if the specified address will put the module
 *          outside the address space.
 * @retval  VERR_ADDRESS_CONFLICT if the mapping clashes with existing mappings.
 *
 * @param   hDbgAs          The address space handle.
 * @param   hDbgMod         The module handle.
 * @param   iSeg            The segment number (0-based) of the segment to be
 *                          linked in.
 * @param   SegAddr         The address to link the segment at.
 * @param   fFlags          See RTDBGASLINK_FLAGS_*.
 */
RTDECL(int) RTDbgAsModuleLinkSeg(RTDBGAS hDbgAs, RTDBGMOD hDbgMod, RTDBGSEGIDX iSeg, RTUINTPTR SegAddr, uint32_t fFlags)
{
    /*
     * Validate input.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    const char *pszName = RTDbgModName(hDbgMod);
    if (!pszName)
        return VERR_INVALID_HANDLE;
    RTUINTPTR cb = RTDbgModSegmentSize(hDbgMod, iSeg);
    if (!cb)
        return VERR_OUT_OF_RANGE;
    if (    SegAddr           < pDbgAs->FirstAddr
        ||  SegAddr           > pDbgAs->LastAddr
        ||  SegAddr + cb - 1  < pDbgAs->FirstAddr
        ||  SegAddr + cb - 1  > pDbgAs->LastAddr
        ||  SegAddr + cb - 1  < SegAddr)
        return VERR_OUT_OF_RANGE;
    AssertReturn(!(fFlags & ~RTDBGASLINK_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER);

    /*
     * Invoke worker common with RTDbgAsModuleLinkSeg.
     */
    RTDBGAS_LOCK_WRITE(pDbgAs);
    int rc = rtDbgAsModuleLinkCommon(pDbgAs, hDbgMod, iSeg, SegAddr, cb, pszName, fFlags);
    RTDBGAS_UNLOCK_WRITE(pDbgAs);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsModuleLinkSeg);


/**
 * Worker for RTDbgAsModuleUnlink, RTDbgAsModuleUnlinkByAddr and rtDbgAsModuleLinkCommon.
 *
 * @param   pDbgAs          Pointer to the address space instance data.
 * @param   pMod            The module to unlink.
 *
 * @remarks The caller must have locked the address space for writing.
 */
static void rtDbgAsModuleUnlinkMod(PRTDBGASINT pDbgAs, PRTDBGASMOD pMod)
{
    Assert(!pMod->pMapHead);

    /*
     * Unlink it from the name.
     */
    const char  *pszName = RTDbgModName((RTDBGMOD)pMod->Core.Key);
    PRTDBGASNAME pName   = (PRTDBGASNAME)RTStrSpaceGet(&pDbgAs->NameSpace, pszName);
    AssertReturnVoid(pName);

    if (pName->pHead == pMod)
        pName->pHead = pMod->pNextName;
    else
        for (PRTDBGASMOD pCur = pName->pHead; pCur; pCur = pCur->pNextName)
            if (pCur->pNextName == pMod)
            {
                pCur->pNextName = pMod->pNextName;
                break;
            }
    pMod->pNextName = NULL;

    /*
     * Free the name if this was the last reference to it.
     */
    if (!pName->pHead)
    {
        pName = (PRTDBGASNAME)RTStrSpaceRemove(&pDbgAs->NameSpace, pName->StrCore.pszString);
        Assert(pName);
        RTMemFree(pName);
    }

    /*
     * Remove it from the module handle tree.
     */
    PAVLPVNODECORE pNode = RTAvlPVRemove(&pDbgAs->ModTree, pMod->Core.Key);
    Assert(pNode == &pMod->Core); NOREF(pNode);

    /*
     * Remove it from the module table by replacing it by the last entry.
     */
    pDbgAs->cModules--;
    uint32_t iMod = pMod->iOrdinal;
    Assert(iMod <= pDbgAs->cModules);
    if (iMod != pDbgAs->cModules)
    {
        PRTDBGASMOD pTailMod = pDbgAs->papModules[pDbgAs->cModules];
        pTailMod->iOrdinal = iMod;
        pDbgAs->papModules[iMod] = pTailMod;
    }
    pMod->iOrdinal = UINT32_MAX;

    /*
     * Free it.
     */
    RTMemFree(pMod);
}


/**
 * Worker for RTDbgAsModuleUnlink and RTDbgAsModuleUnlinkByAddr.
 *
 * @param   pDbgAs          Pointer to the address space instance data.
 * @param   pMap            The map to unlink and free.
 *
 * @remarks The caller must have locked the address space for writing.
 */
static void rtDbgAsModuleUnlinkMap(PRTDBGASINT pDbgAs, PRTDBGASMAP pMap)
{
    /* remove from the tree */
    PAVLRUINTPTRNODECORE pNode = RTAvlrUIntPtrRemove(&pDbgAs->MapTree, pMap->Core.Key);
    Assert(pNode == &pMap->Core); NOREF(pNode);

    /* unlink */
    PRTDBGASMOD pMod = pMap->pMod;
    if (pMod->pMapHead == pMap)
        pMod->pMapHead = pMap->pNext;
    else
    {
        bool fFound = false;
        for (PRTDBGASMAP pCur = pMod->pMapHead; pCur; pCur = pCur->pNext)
            if (pCur->pNext == pMap)
            {
                pCur->pNext = pMap->pNext;
                fFound = true;
                break;
            }
        Assert(fFound);
    }

    /* free it */
    pMap->Core.Key = pMap->Core.KeyLast = 0;
    pMap->pNext = NULL;
    pMap->pMod = NULL;
    RTMemFree(pMap);
}


/**
 * Worker for RTDbgAsModuleUnlinkByAddr and rtDbgAsModuleLinkCommon that
 * unlinks a single mapping and releases the module if it's the last one.
 *
 * @param   pDbgAs      The address space instance.
 * @param   pMap        The mapping to unlink.
 *
 * @remarks The caller must have locked the address space for writing.
 */
static void rtDbgAsModuleUnlinkByMap(PRTDBGASINT pDbgAs, PRTDBGASMAP pMap)
{
    /*
     * Unlink it from the address space.
     * Unlink the module as well if it's the last mapping it has.
     */
    PRTDBGASMOD pMod = pMap->pMod;
    rtDbgAsModuleUnlinkMap(pDbgAs, pMap);
    if (!pMod->pMapHead)
        rtDbgAsModuleUnlinkMod(pDbgAs, pMod);
}


/**
 * Unlinks all the mappings of a module from the address space.
 *
 * @returns IPRT status code.
 * @retval  VERR_NOT_FOUND if the module wasn't found.
 *
 * @param   hDbgAs          The address space handle.
 * @param   hDbgMod         The module handle of the module to be unlinked.
 */
RTDECL(int) RTDbgAsModuleUnlink(RTDBGAS hDbgAs, RTDBGMOD hDbgMod)
{
    /*
     * Validate input.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    if (hDbgMod == NIL_RTDBGMOD)
        return VINF_SUCCESS;

    RTDBGAS_LOCK_WRITE(pDbgAs);
    PRTDBGASMOD pMod = (PRTDBGASMOD)RTAvlPVGet(&pDbgAs->ModTree, hDbgMod);
    if (!pMod)
    {
        RTDBGAS_UNLOCK_WRITE(pDbgAs);
        return VERR_NOT_FOUND;
    }

    /*
     * Unmap all everything and release the module.
     */
    while (pMod->pMapHead)
        rtDbgAsModuleUnlinkMap(pDbgAs, pMod->pMapHead);
    rtDbgAsModuleUnlinkMod(pDbgAs, pMod);

    RTDBGAS_UNLOCK_WRITE(pDbgAs);
    return VINF_SUCCESS;
}
RT_EXPORT_SYMBOL(RTDbgAsModuleUnlink);


/**
 * Unlinks the mapping at the specified address.
 *
 * @returns IPRT status code.
 * @retval  VERR_NOT_FOUND if no module or segment is mapped at that address.
 *
 * @param   hDbgAs          The address space handle.
 * @param   Addr            The address within the mapping to be unlinked.
 */
RTDECL(int) RTDbgAsModuleUnlinkByAddr(RTDBGAS hDbgAs, RTUINTPTR Addr)
{
    /*
     * Validate input.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);

    RTDBGAS_LOCK_WRITE(pDbgAs);
    PRTDBGASMAP pMap = (PRTDBGASMAP)RTAvlrUIntPtrRangeGet(&pDbgAs->MapTree, Addr);
    if (!pMap)
    {
        RTDBGAS_UNLOCK_WRITE(pDbgAs);
        return VERR_NOT_FOUND;
    }

    /*
     * Hand it to
     */
    rtDbgAsModuleUnlinkByMap(pDbgAs, pMap);

    RTDBGAS_UNLOCK_WRITE(pDbgAs);
    return VINF_SUCCESS;
}
RT_EXPORT_SYMBOL(RTDbgAsModuleUnlinkByAddr);


/**
 * Get a the handle of a module in the address space by is index.
 *
 * @returns A retained handle to the specified module. The caller must release
 *          the returned reference.
 *          NIL_RTDBGMOD if invalid index or handle.
 *
 * @param   hDbgAs          The address space handle.
 * @param   iModule         The index of the module to get.
 *
 * @remarks The module indexes may change after calls to RTDbgAsModuleLink,
 *          RTDbgAsModuleLinkSeg, RTDbgAsModuleUnlink and
 *          RTDbgAsModuleUnlinkByAddr.
 */
RTDECL(RTDBGMOD) RTDbgAsModuleByIndex(RTDBGAS hDbgAs, uint32_t iModule)
{
    /*
     * Validate input.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, NIL_RTDBGMOD);

    RTDBGAS_LOCK_READ(pDbgAs);
    if (iModule >= pDbgAs->cModules)
    {
        RTDBGAS_UNLOCK_READ(pDbgAs);
        return NIL_RTDBGMOD;
    }

    /*
     * Get, retain and return it.
     */
    RTDBGMOD hMod = (RTDBGMOD)pDbgAs->papModules[iModule]->Core.Key;
    RTDbgModRetain(hMod);

    RTDBGAS_UNLOCK_READ(pDbgAs);
    return hMod;
}
RT_EXPORT_SYMBOL(RTDbgAsModuleByIndex);


/**
 * Queries mapping module information by handle.
 *
 * @returns IPRT status code.
 * @retval  VERR_NOT_FOUND if no mapping was found at the specified address.
 *
 * @param   hDbgAs          The address space handle.
 * @param   Addr            Address within the mapping of the module or segment.
 * @param   phMod           Where to the return the retained module handle.
 *                          Optional.
 * @param   pAddr           Where to return the base address of the mapping.
 *                          Optional.
 * @param   piSeg           Where to return the segment index. This is set to
 *                          NIL if the entire module is mapped as a single
 *                          mapping. Optional.
 */
RTDECL(int) RTDbgAsModuleByAddr(RTDBGAS hDbgAs, RTUINTPTR Addr, PRTDBGMOD phMod, PRTUINTPTR pAddr, PRTDBGSEGIDX piSeg)
{
    /*
     * Validate input.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);

    RTDBGAS_LOCK_READ(pDbgAs);
    PRTDBGASMAP pMap = (PRTDBGASMAP)RTAvlrUIntPtrRangeGet(&pDbgAs->MapTree, Addr);
    if (!pMap)
    {
        RTDBGAS_UNLOCK_READ(pDbgAs);
        return VERR_NOT_FOUND;
    }

    /*
     * Set up the return values.
     */
    if (phMod)
    {
        RTDBGMOD hMod = (RTDBGMOD)pMap->pMod->Core.Key;
        RTDbgModRetain(hMod);
        *phMod = hMod;
    }
    if (pAddr)
        *pAddr = pMap->Core.Key;
    if (piSeg)
        *piSeg = pMap->iSeg;

    RTDBGAS_UNLOCK_READ(pDbgAs);
    return VINF_SUCCESS;
}
RT_EXPORT_SYMBOL(RTDbgAsModuleByAddr);


/**
 * Queries mapping module information by name.
 *
 * @returns IPRT status code.
 * @retval  VERR_NOT_FOUND if no mapping was found at the specified address.
 * @retval  VERR_OUT_OF_RANGE if the name index was out of range.
 *
 * @param   hDbgAs          The address space handle.
 * @param   pszName         The module name.
 * @param   iName           There can be more than one module by the same name
 *                          in an address space. This argument indicates which
 *                          is meant. (0 based)
 * @param   phMod           Where to the return the retained module handle.
 */
RTDECL(int) RTDbgAsModuleByName(RTDBGAS hDbgAs, const char *pszName, uint32_t iName, PRTDBGMOD phMod)
{
    /*
     * Validate input.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    AssertPtrReturn(phMod, VERR_INVALID_POINTER);

    RTDBGAS_LOCK_READ(pDbgAs);
    PRTDBGASNAME pName = (PRTDBGASNAME)RTStrSpaceGet(&pDbgAs->NameSpace, pszName);
    if (!pName)
    {
        RTDBGAS_UNLOCK_READ(pDbgAs);
        return VERR_NOT_FOUND;
    }

    PRTDBGASMOD pMod = pName->pHead;
    while (iName-- > 0)
    {
        pMod = pMod->pNextName;
        if (!pMod)
        {
            RTDBGAS_UNLOCK_READ(pDbgAs);
            return VERR_OUT_OF_RANGE;
        }
    }

    /*
     * Get, retain and return it.
     */
    RTDBGMOD hMod = (RTDBGMOD)pMod->Core.Key;
    RTDbgModRetain(hMod);
    *phMod = hMod;

    RTDBGAS_UNLOCK_READ(pDbgAs);
    return VINF_SUCCESS;
}
RT_EXPORT_SYMBOL(RTDbgAsModuleByName);


/**
 * Queries mapping information for a module given by index.
 *
 * @returns IRPT status code.
 * @retval  VERR_INVALID_HANDLE if hDbgAs is invalid.
 * @retval  VERR_OUT_OF_RANGE if the name index was out of range.
 * @retval  VINF_BUFFER_OVERFLOW if the array is too small and the returned
 *          information is incomplete.
 *
 * @param   hDbgAs          The address space handle.
 * @param   iModule         The index of the module to get.
 * @param   paMappings      Where to return the mapping information.  The buffer
 *                          size is given by *pcMappings.
 * @param   pcMappings      IN: Size of the paMappings array. OUT: The number of
 *                          entries returned.
 * @param   fFlags          Flags for reserved for future use. MBZ.
 *
 * @remarks See remarks for RTDbgAsModuleByIndex regarding the volatility of the
 *          iModule parameter.
 */
RTDECL(int) RTDbgAsModuleQueryMapByIndex(RTDBGAS hDbgAs, uint32_t iModule, PRTDBGASMAPINFO paMappings, uint32_t *pcMappings, uint32_t fFlags)
{
    /*
     * Validate input.
     */
    uint32_t const  cMappings = *pcMappings;
    PRTDBGASINT     pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    AssertReturn(!fFlags, VERR_INVALID_PARAMETER);

    RTDBGAS_LOCK_READ(pDbgAs);
    if (iModule >= pDbgAs->cModules)
    {
        RTDBGAS_UNLOCK_READ(pDbgAs);
        return VERR_OUT_OF_RANGE;
    }

    /*
     * Copy the mapping information about the module.
     */
    int         rc    = VINF_SUCCESS;
    PRTDBGASMAP pMap  = pDbgAs->papModules[iModule]->pMapHead;
    uint32_t    cMaps = 0;
    while (pMap)
    {
        if (cMaps >= cMappings)
        {
            rc = VINF_BUFFER_OVERFLOW;
            break;
        }
        paMappings[cMaps].Address = pMap->Core.Key;
        paMappings[cMaps].iSeg    = pMap->iSeg;
        cMaps++;
        pMap = pMap->pNext;
    }

    RTDBGAS_UNLOCK_READ(pDbgAs);
    *pcMappings = cMaps;
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsModuleQueryMapByIndex);


/**
 * Internal worker that looks up and retains a module.
 *
 * @returns Module handle, NIL_RTDBGMOD if not found.
 * @param   pDbgAs          The address space instance data.
 * @param   Addr            Address within the module.
 * @param   piSeg           where to return the segment index.
 * @param   poffSeg         Where to return the segment offset.
 * @param   pMapAddr        The mapping address (RTDBGASMAP::Core.Key).
 */
DECLINLINE(RTDBGMOD) rtDbgAsModuleByAddr(PRTDBGASINT pDbgAs, RTUINTPTR Addr, PRTDBGSEGIDX piSeg, PRTUINTPTR poffSeg, PRTUINTPTR pMapAddr)
{
    RTDBGMOD hMod = NIL_RTDBGMOD;

    RTDBGAS_LOCK_READ(pDbgAs);
    PRTDBGASMAP pMap = (PRTDBGASMAP)RTAvlrUIntPtrRangeGet(&pDbgAs->MapTree, Addr);
    if (pMap)
    {
        hMod = (RTDBGMOD)pMap->pMod->Core.Key;
        RTDbgModRetain(hMod);
        *piSeg = pMap->iSeg != NIL_RTDBGSEGIDX ? pMap->iSeg : RTDBGSEGIDX_RVA;
        *poffSeg = Addr - pMap->Core.Key;
        if (pMapAddr)
            *pMapAddr = pMap->Core.Key;
    }
    RTDBGAS_UNLOCK_READ(pDbgAs);

    return hMod;
}


/**
 * Adjusts the address to correspond to the mapping of the module/segment.
 *
 * @param   pAddr           The address to adjust (in/out).
 * @param   iSeg            The related segment.
 * @param   hDbgMod         The module handle.
 * @param   MapAddr         The mapping address.
 * @param   iMapSeg         The segment that's mapped, NIL_RTDBGSEGIDX or
 *                          RTDBGSEGIDX_RVA if the whole module is mapped here.
 */
DECLINLINE(void) rtDbgAsAdjustAddressByMapping(PRTUINTPTR pAddr, RTDBGSEGIDX iSeg,
                                               RTDBGMOD hDbgMod, RTUINTPTR MapAddr, RTDBGSEGIDX iMapSeg)
{
    if (iSeg == RTDBGSEGIDX_ABS)
        return;

    if (iSeg == RTDBGSEGIDX_RVA)
    {
        if (    iMapSeg == RTDBGSEGIDX_RVA
            ||  iMapSeg == NIL_RTDBGSEGIDX)
            *pAddr += MapAddr;
        else
        {
            RTUINTPTR SegRva = RTDbgModSegmentRva(hDbgMod, iMapSeg);
            AssertReturnVoid(SegRva != RTUINTPTR_MAX);
            AssertMsg(SegRva <= *pAddr, ("SegRva=%RTptr *pAddr=%RTptr\n", SegRva, *pAddr));
            *pAddr += MapAddr - SegRva;
        }
    }
    else
    {
        if (    iMapSeg != RTDBGSEGIDX_RVA
            &&  iMapSeg != NIL_RTDBGSEGIDX)
        {
            Assert(iMapSeg == iSeg);
            *pAddr += MapAddr;
        }
        else
        {
            RTUINTPTR SegRva = RTDbgModSegmentRva(hDbgMod, iSeg);
            AssertReturnVoid(SegRva != RTUINTPTR_MAX);
            *pAddr += MapAddr + SegRva;
        }
    }
}


/**
 * Adjusts the symbol value to correspond to the mapping of the module/segment.
 *
 * @param   pSymbol         The returned symbol info.
 * @param   hDbgMod         The module handle.
 * @param   MapAddr         The mapping address.
 * @param   iMapSeg         The segment that's mapped, NIL_RTDBGSEGIDX if the
 *                          whole module is mapped here.
 */
DECLINLINE(void) rtDbgAsAdjustSymbolValue(PRTDBGSYMBOL pSymbol, RTDBGMOD hDbgMod, RTUINTPTR MapAddr, RTDBGSEGIDX iMapSeg)
{
    Assert(pSymbol->iSeg   != NIL_RTDBGSEGIDX);
    Assert(pSymbol->offSeg == pSymbol->Value);
    rtDbgAsAdjustAddressByMapping(&pSymbol->Value, pSymbol->iSeg, hDbgMod, MapAddr, iMapSeg);
}


/**
 * Adjusts the line number address to correspond to the mapping of the module/segment.
 *
 * @param   pLine           The returned line number info.
 * @param   hDbgMod         The module handle.
 * @param   MapAddr         The mapping address.
 * @param   iMapSeg         The segment that's mapped, NIL_RTDBGSEGIDX if the
 *                          whole module is mapped here.
 */
DECLINLINE(void) rtDbgAsAdjustLineAddress(PRTDBGLINE pLine, RTDBGMOD hDbgMod, RTUINTPTR MapAddr, RTDBGSEGIDX iMapSeg)
{
    Assert(pLine->iSeg   != NIL_RTDBGSEGIDX);
    Assert(pLine->offSeg == pLine->Address);
    rtDbgAsAdjustAddressByMapping(&pLine->Address, pLine->iSeg, hDbgMod, MapAddr, iMapSeg);
}


/**
 * Adds a symbol to a module in the address space.
 *
 * @returns IPRT status code. See RTDbgModSymbolAdd for more specific ones.
 * @retval  VERR_INVALID_HANDLE if hDbgAs is invalid.
 * @retval  VERR_NOT_FOUND if no module was found at the specified address.
 * @retval  VERR_NOT_SUPPORTED if the module interpret doesn't support adding
 *          custom symbols.
 *
 * @param   hDbgAs          The address space handle.
 * @param   pszSymbol       The symbol name.
 * @param   Addr            The address of the symbol.
 * @param   cb              The size of the symbol.
 * @param   fFlags          Symbol flags.
 * @param   piOrdinal       Where to return the symbol ordinal on success. If
 *                          the interpreter doesn't do ordinals, this will be set to
 *                          UINT32_MAX. Optional
 */
RTDECL(int) RTDbgAsSymbolAdd(RTDBGAS hDbgAs, const char *pszSymbol, RTUINTPTR Addr, RTUINTPTR cb, uint32_t fFlags, uint32_t *piOrdinal)
{
    /*
    * Validate input and resolve the address.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);

    RTDBGSEGIDX iSeg   = NIL_RTDBGSEGIDX; /* shut up gcc */
    RTUINTPTR   offSeg = 0;
    RTDBGMOD    hMod   = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, NULL);
    if (hMod == NIL_RTDBGMOD)
        return VERR_NOT_FOUND;

    /*
     * Forward the call.
     */
    int rc = RTDbgModSymbolAdd(hMod, pszSymbol, iSeg, offSeg, cb, fFlags, piOrdinal);
    RTDbgModRelease(hMod);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsSymbolAdd);


/**
 * Creates a snapshot of the module table on the temporary heap.
 *
 * The caller must release all the module handles before freeing the table
 * using RTMemTmpFree.
 *
 * @returns Module table snaphot.
 * @param   pDbgAs          The address space instance data.
 * @param   pcModules       Where to return the number of modules.
 */
static PRTDBGMOD rtDbgAsSnapshotModuleTable(PRTDBGASINT pDbgAs, uint32_t *pcModules)
{
    RTDBGAS_LOCK_READ(pDbgAs);

    uint32_t iMod = *pcModules = pDbgAs->cModules;
    PRTDBGMOD pahModules = (PRTDBGMOD)RTMemTmpAlloc(sizeof(pahModules[0]) * RT_MAX(iMod, 1));
    if (pahModules)
    {
        while (iMod-- > 0)
        {
            RTDBGMOD hMod = (RTDBGMOD)pDbgAs->papModules[iMod]->Core.Key;
            pahModules[iMod] = hMod;
            RTDbgModRetain(hMod);
        }
    }

    RTDBGAS_UNLOCK_READ(pDbgAs);
    return pahModules;
}


/**
 * Query a symbol by address.
 *
 * @returns IPRT status code. See RTDbgModSymbolAddr for more specific ones.
 * @retval  VERR_INVALID_HANDLE if hDbgAs is invalid.
 * @retval  VERR_NOT_FOUND if the address couldn't be mapped to a module.
 * @retval  VERR_INVALID_PARAMETER if incorrect flags.
 *
 * @param   hDbgAs          The address space handle.
 * @param   Addr            The address which closest symbol is requested.
 * @param   fFlags          Symbol search flags, see RTDBGSYMADDR_FLAGS_XXX.
 * @param   poffDisp        Where to return the distance between the symbol and
 *                          address. Optional.
 * @param   pSymbol         Where to return the symbol info.
 * @param   phMod           Where to return the module handle. Optional.
 */
RTDECL(int) RTDbgAsSymbolByAddr(RTDBGAS hDbgAs, RTUINTPTR Addr, uint32_t fFlags,
                                PRTINTPTR poffDisp, PRTDBGSYMBOL pSymbol, PRTDBGMOD phMod)
{
    /*
     * Validate input and resolve the address.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    if (phMod)
        *phMod = NIL_RTDBGMOD;

    RTDBGSEGIDX iSeg    = NIL_RTDBGSEGIDX; /* shut up gcc */
    RTUINTPTR   offSeg  = 0;
    RTUINTPTR   MapAddr = 0;
    RTDBGMOD    hMod    = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, &MapAddr);
    if (hMod == NIL_RTDBGMOD)
    {
        /*
         * Check for absolute symbols.  Requires iterating all modules.
         */
        uint32_t cModules;
        PRTDBGMOD pahModules = rtDbgAsSnapshotModuleTable(pDbgAs, &cModules);
        if (!pahModules)
            return VERR_NO_TMP_MEMORY;

        int      rc;
        RTINTPTR offBestDisp = RTINTPTR_MAX;
        uint32_t iBest       = UINT32_MAX;
        for (uint32_t i = 0; i < cModules; i++)
        {
            RTINTPTR offDisp;
            rc = RTDbgModSymbolByAddr(pahModules[i], RTDBGSEGIDX_ABS, Addr, fFlags, &offDisp, pSymbol);
            if (RT_SUCCESS(rc) && RT_ABS(offDisp) < offBestDisp)
            {
                offBestDisp = RT_ABS(offDisp);
                iBest = i;
            }
        }

        if (iBest == UINT32_MAX)
            rc = VERR_NOT_FOUND;
        else
        {
            hMod = pahModules[iBest];
            rc = RTDbgModSymbolByAddr(hMod, RTDBGSEGIDX_ABS, Addr, fFlags, poffDisp, pSymbol);
            if (RT_SUCCESS(rc))
            {
                rtDbgAsAdjustSymbolValue(pSymbol, hMod, MapAddr, iSeg);
                if (phMod)
                    RTDbgModRetain(*phMod = hMod);
            }
        }

        for (uint32_t i = 0; i < cModules; i++)
            RTDbgModRelease(pahModules[i]);
        RTMemTmpFree(pahModules);
        return rc;
    }

    /*
     * Forward the call.
     */
    int rc = RTDbgModSymbolByAddr(hMod, iSeg, offSeg, fFlags, poffDisp, pSymbol);
    if (RT_SUCCESS(rc))
        rtDbgAsAdjustSymbolValue(pSymbol, hMod, MapAddr, iSeg);
    if (phMod)
        *phMod = hMod;
    else
        RTDbgModRelease(hMod);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsSymbolByAddr);


/**
 * Query a symbol by address.
 *
 * @returns IPRT status code. See RTDbgModSymbolAddrA for more specific ones.
 * @retval  VERR_INVALID_HANDLE if hDbgAs is invalid.
 * @retval  VERR_NOT_FOUND if the address couldn't be mapped to a module.
 * @retval  VERR_INVALID_PARAMETER if incorrect flags.
 *
 * @param   hDbgAs          The address space handle.
 * @param   Addr            The address which closest symbol is requested.
 * @param   fFlags              Symbol search flags, see RTDBGSYMADDR_FLAGS_XXX.
 * @param   poffDisp        Where to return the distance between the symbol
 *                          and address. Optional.
 * @param   ppSymInfo       Where to return the pointer to the allocated symbol
 *                          info. Always set. Free with RTDbgSymbolFree.
 * @param   phMod           Where to return the module handle. Optional.
 */
RTDECL(int) RTDbgAsSymbolByAddrA(RTDBGAS hDbgAs, RTUINTPTR Addr, uint32_t fFlags,
                                 PRTINTPTR poffDisp, PRTDBGSYMBOL *ppSymInfo, PRTDBGMOD phMod)
{
    /*
     * Validate input and resolve the address.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);

    RTDBGSEGIDX iSeg    = NIL_RTDBGSEGIDX;
    RTUINTPTR   offSeg  = 0;
    RTUINTPTR   MapAddr = 0;
    RTDBGMOD    hMod    = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, &MapAddr);
    if (hMod == NIL_RTDBGMOD)
    {
        if (phMod)
            *phMod = NIL_RTDBGMOD;
        return VERR_NOT_FOUND;
    }

    /*
     * Forward the call.
     */
    int rc = RTDbgModSymbolByAddrA(hMod, iSeg, offSeg, fFlags, poffDisp, ppSymInfo);
    if (RT_SUCCESS(rc))
        rtDbgAsAdjustSymbolValue(*ppSymInfo, hMod, MapAddr, iSeg);
    if (phMod)
        *phMod = hMod;
    else
        RTDbgModRelease(hMod);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsSymbolByAddrA);


/**
 * Attempts to find a mapping of the specified symbol/module and
 * adjust it's Value field accordingly.
 *
 * @returns true / false success indicator.
 * @param   pDbgAs          The address space.
 * @param   hDbgMod         The module handle.
 * @param   pSymbol         The symbol info.
 */
static bool rtDbgAsFindMappingAndAdjustSymbolValue(PRTDBGASINT pDbgAs, RTDBGMOD hDbgMod, PRTDBGSYMBOL pSymbol)
{
    /*
     * Absolute segments needs no fixing.
     */
    RTDBGSEGIDX const iSeg = pSymbol->iSeg;
    if (iSeg == RTDBGSEGIDX_ABS)
        return true;

    RTDBGAS_LOCK_READ(pDbgAs);

    /*
     * Lookup up the module by it's handle and iterate the mappings looking for one
     * that either encompasses the entire module or the segment in question.
     */
    PRTDBGASMOD pMod = (PRTDBGASMOD)RTAvlPVGet(&pDbgAs->ModTree, hDbgMod);
    if (pMod)
    {
        for (PRTDBGASMAP pMap = pMod->pMapHead; pMap; pMap = pMap->pNext)
        {
            /* Exact segment match or full-mapping. */
            if (    iSeg == pMap->iSeg
                ||  pMap->iSeg == NIL_RTDBGSEGIDX)
            {
                RTUINTPTR   MapAddr = pMap->Core.Key;
                RTDBGSEGIDX iMapSeg = pMap->iSeg;

                RTDBGAS_UNLOCK_READ(pDbgAs);
                rtDbgAsAdjustSymbolValue(pSymbol, hDbgMod, MapAddr, iMapSeg);
                return true;
            }

            /* Symbol uses RVA and the mapping doesn't, see if it's in the mapped segment. */
            if (iSeg == RTDBGSEGIDX_RVA)
            {
                Assert(pMap->iSeg != NIL_RTDBGSEGIDX);
                RTUINTPTR SegRva = RTDbgModSegmentRva(hDbgMod, pMap->iSeg);
                Assert(SegRva != RTUINTPTR_MAX);
                RTUINTPTR cbSeg = RTDbgModSegmentSize(hDbgMod, pMap->iSeg);
                if (SegRva - pSymbol->Value < cbSeg)
                {
                    RTUINTPTR   MapAddr = pMap->Core.Key;
                    RTDBGSEGIDX iMapSeg = pMap->iSeg;

                    RTDBGAS_UNLOCK_READ(pDbgAs);
                    rtDbgAsAdjustSymbolValue(pSymbol, hDbgMod, MapAddr, iMapSeg);
                    return true;
                }
            }
        }
    }
    /* else: Unmapped while we were searching. */

    RTDBGAS_UNLOCK_READ(pDbgAs);
    return false;
}


/**
 * Query a symbol by name.
 *
 * @returns IPRT status code.
 * @retval  VERR_SYMBOL_NOT_FOUND if not found.
 *
 * @param   hDbgAs          The address space handle.
 * @param   pszSymbol       The symbol name. It is possible to limit the scope
 *                          of the search by prefixing the symbol with a module
 *                          name pattern followed by a bang (!) character.
 *                          RTStrSimplePatternNMatch is used for the matching.
 * @param   pSymbol         Where to return the symbol info.
 * @param   phMod           Where to return the module handle. Optional.
 */
RTDECL(int) RTDbgAsSymbolByName(RTDBGAS hDbgAs, const char *pszSymbol, PRTDBGSYMBOL pSymbol, PRTDBGMOD phMod)
{
    /*
     * Validate input.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    AssertPtrReturn(pszSymbol, VERR_INVALID_POINTER);
    AssertPtrReturn(pSymbol, VERR_INVALID_POINTER);

    /*
     * Look for module pattern.
     */
    const char *pachModPat = NULL;
    size_t      cchModPat  = 0;
    const char *pszBang    = strchr(pszSymbol, '!');
    if (pszBang)
    {
        pachModPat = pszSymbol;
        cchModPat = pszBang - pszSymbol;
        pszSymbol = pszBang + 1;
        if (!*pszSymbol)
            return VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE;
        /* Note! Zero length module -> no pattern -> escape for symbol with '!'. */
    }

    /*
     * Iterate the modules, looking for the symbol.
     */
    uint32_t cModules;
    PRTDBGMOD pahModules = rtDbgAsSnapshotModuleTable(pDbgAs, &cModules);
    if (!pahModules)
        return VERR_NO_TMP_MEMORY;

    for (uint32_t i = 0; i < cModules; i++)
    {
        if (    cchModPat == 0
            ||  RTStrSimplePatternNMatch(pachModPat, cchModPat, RTDbgModName(pahModules[i]), RTSTR_MAX))
        {
            int rc = RTDbgModSymbolByName(pahModules[i], pszSymbol, pSymbol);
            if (RT_SUCCESS(rc))
            {
                if (rtDbgAsFindMappingAndAdjustSymbolValue(pDbgAs, pahModules[i], pSymbol))
                {
                    if (phMod)
                        RTDbgModRetain(*phMod = pahModules[i]);
                    for (; i < cModules; i++)
                        RTDbgModRelease(pahModules[i]);
                    RTMemTmpFree(pahModules);
                    return rc;
                }
            }
        }
        RTDbgModRelease(pahModules[i]);
    }

    RTMemTmpFree(pahModules);
    return VERR_SYMBOL_NOT_FOUND;
}
RT_EXPORT_SYMBOL(RTDbgAsSymbolByName);


/**
 * Query a symbol by name, allocating the returned symbol structure.
 *
 * @returns IPRT status code.
 * @retval  VERR_SYMBOL_NOT_FOUND if not found.
 *
 * @param   hDbgAs          The address space handle.
 * @param   pszSymbol       The symbol name. See RTDbgAsSymbolByName for more.
 * @param   ppSymbol        Where to return the pointer to the allocated
 *                          symbol info. Always set. Free with RTDbgSymbolFree.
 * @param   phMod           Where to return the module handle. Optional.
 */
RTDECL(int) RTDbgAsSymbolByNameA(RTDBGAS hDbgAs, const char *pszSymbol, PRTDBGSYMBOL *ppSymbol, PRTDBGMOD phMod)
{
    /*
     * Validate input.
     */
    AssertPtrReturn(ppSymbol, VERR_INVALID_POINTER);
    *ppSymbol = NULL;
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);
    AssertPtrReturn(pszSymbol, VERR_INVALID_POINTER);

    /*
     * Look for module pattern.
     */
    const char *pachModPat = NULL;
    size_t      cchModPat  = 0;
    const char *pszBang    = strchr(pszSymbol, '!');
    if (pszBang)
    {
        pachModPat = pszSymbol;
        cchModPat = pszBang - pszSymbol;
        pszSymbol = pszBang + 1;
        if (!*pszSymbol)
            return VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE;
        /* Note! Zero length module -> no pattern -> escape for symbol with '!'. */
    }

    /*
     * Iterate the modules, looking for the symbol.
     */
    uint32_t cModules;
    PRTDBGMOD pahModules = rtDbgAsSnapshotModuleTable(pDbgAs, &cModules);
    if (!pahModules)
        return VERR_NO_TMP_MEMORY;

    for (uint32_t i = 0; i < cModules; i++)
    {
        if (    cchModPat == 0
            ||  RTStrSimplePatternNMatch(pachModPat, cchModPat, RTDbgModName(pahModules[i]), RTSTR_MAX))
        {
            int rc = RTDbgModSymbolByNameA(pahModules[i], pszSymbol, ppSymbol);
            if (RT_SUCCESS(rc))
            {
                if (rtDbgAsFindMappingAndAdjustSymbolValue(pDbgAs, pahModules[i], *ppSymbol))
                {
                    if (phMod)
                        RTDbgModRetain(*phMod = pahModules[i]);
                    for (; i < cModules; i++)
                        RTDbgModRelease(pahModules[i]);
                    RTMemTmpFree(pahModules);
                    return rc;
                }
            }
        }
        RTDbgModRelease(pahModules[i]);
    }

    RTMemTmpFree(pahModules);
    return VERR_SYMBOL_NOT_FOUND;
}
RT_EXPORT_SYMBOL(RTDbgAsSymbolByNameA);


/**
 * Adds a line number to a module in the address space.
 *
 * @returns IPRT status code. See RTDbgModSymbolAdd for more specific ones.
 * @retval  VERR_INVALID_HANDLE if hDbgAs is invalid.
 * @retval  VERR_NOT_FOUND if no module was found at the specified address.
 * @retval  VERR_NOT_SUPPORTED if the module interpret doesn't support adding
 *          custom symbols.
 *
 * @param   hDbgAs          The address space handle.
 * @param   pszFile         The file name.
 * @param   uLineNo         The line number.
 * @param   Addr            The address of the symbol.
 * @param   piOrdinal       Where to return the line number ordinal on success.
 *                          If the interpreter doesn't do ordinals, this will be
 *                          set to UINT32_MAX. Optional.
 */
RTDECL(int) RTDbgAsLineAdd(RTDBGAS hDbgAs, const char *pszFile, uint32_t uLineNo, RTUINTPTR Addr, uint32_t *piOrdinal)
{
    /*
    * Validate input and resolve the address.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);

    RTDBGSEGIDX iSeg   = NIL_RTDBGSEGIDX; /* shut up gcc */
    RTUINTPTR   offSeg = 0;               /* ditto */
    RTDBGMOD    hMod   = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, NULL);
    if (hMod == NIL_RTDBGMOD)
        return VERR_NOT_FOUND;

    /*
     * Forward the call.
     */
    int rc = RTDbgModLineAdd(hMod, pszFile, uLineNo, iSeg, offSeg, piOrdinal);
    RTDbgModRelease(hMod);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsLineAdd);


RTDECL(int) RTDbgAsLineByAddr(RTDBGAS hDbgAs, RTUINTPTR Addr, PRTINTPTR poffDisp, PRTDBGLINE pLine, PRTDBGMOD phMod)
{
    /*
     * Validate input and resolve the address.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);

    RTDBGSEGIDX iSeg    = NIL_RTDBGSEGIDX; /* shut up gcc */
    RTUINTPTR   offSeg  = 0;
    RTUINTPTR   MapAddr = 0;
    RTDBGMOD    hMod    = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, &MapAddr);
    if (hMod == NIL_RTDBGMOD)
        return VERR_NOT_FOUND;

    /*
     * Forward the call.
     */
    int rc = RTDbgModLineByAddr(hMod, iSeg, offSeg, poffDisp, pLine);
    if (RT_SUCCESS(rc))
    {
        rtDbgAsAdjustLineAddress(pLine, hMod, MapAddr, iSeg);
        if (phMod)
            *phMod = hMod;
        else
            RTDbgModRelease(hMod);
    }
    else
        RTDbgModRelease(hMod);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsLineByAddr);


RTDECL(int) RTDbgAsLineByAddrA(RTDBGAS hDbgAs, RTUINTPTR Addr, PRTINTPTR poffDisp, PRTDBGLINE *ppLine, PRTDBGMOD phMod)
{
    /*
     * Validate input and resolve the address.
     */
    PRTDBGASINT pDbgAs = hDbgAs;
    RTDBGAS_VALID_RETURN_RC(pDbgAs, VERR_INVALID_HANDLE);

    RTDBGSEGIDX iSeg    = NIL_RTDBGSEGIDX; /* shut up gcc */
    RTUINTPTR   offSeg  = 0;
    RTUINTPTR   MapAddr = 0;
    RTDBGMOD    hMod    = rtDbgAsModuleByAddr(pDbgAs, Addr, &iSeg, &offSeg, &MapAddr);
    if (hMod == NIL_RTDBGMOD)
        return VERR_NOT_FOUND;

    /*
     * Forward the call.
     */
    int rc = RTDbgModLineByAddrA(hMod, iSeg, offSeg, poffDisp, ppLine);
    if (RT_SUCCESS(rc))
    {
        rtDbgAsAdjustLineAddress(*ppLine, hMod, MapAddr, iSeg);
        if (phMod)
            *phMod = hMod;
        else
            RTDbgModRelease(hMod);
    }
    else
        RTDbgModRelease(hMod);
    return rc;
}
RT_EXPORT_SYMBOL(RTDbgAsLineByAddrA);

