/* $Id: dbgkrnlinfo-r0drv-darwin.cpp $ */
/** @file
 * IPRT - Kernel Debug Information, R0 Driver, Darwin.
 */

/*
 * Copyright (C) 2011-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                                                               *
*******************************************************************************/
#ifdef IN_RING0
# include "the-darwin-kernel.h"
# include <sys/kauth.h>
RT_C_DECLS_BEGIN /* Buggy 10.4 headers, fixed in 10.5. */
# include <sys/kpi_mbuf.h>
# include <net/kpi_interfacefilter.h>
# include <sys/kpi_socket.h>
# include <sys/kpi_socketfilter.h>
RT_C_DECLS_END
# include <sys/buf.h>
# include <sys/vm.h>
# include <sys/vnode_if.h>
/*# include <sys/sysctl.h>*/
# include <sys/systm.h>
# include <vfs/vfs_support.h>
/*# include <miscfs/specfs/specdev.h>*/
#endif

#include "internal/iprt.h"
#include <iprt/dbg.h>

#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/err.h>
#include <iprt/assert.h>
#include <iprt/file.h>
#include <iprt/log.h>
#include <iprt/mem.h>
#include <iprt/string.h>
#include <iprt/formats/mach-o.h>
#include "internal/magics.h"

/** @def MY_CPU_TYPE
 * The CPU type targeted by the compiler. */
/** @def MY_CPU_TYPE
 * The "ALL" CPU subtype targeted by the compiler. */
/** @def MY_MACHO_HEADER
 * The Mach-O header targeted by the compiler.  */
/** @def MY_MACHO_MAGIC
 * The Mach-O header magic we're targeting.  */
/** @def MY_SEGMENT_COMMAND
 * The segment command targeted by the compiler.  */
/** @def MY_SECTION
 * The section struture targeted by the compiler.  */
/** @def MY_NLIST
 * The symbol table entry targeted by the compiler.  */
#ifdef RT_ARCH_X86
# define MY_CPU_TYPE            CPU_TYPE_I386
# define MY_CPU_SUBTYPE_ALL     CPU_SUBTYPE_I386_ALL
# define MY_MACHO_HEADER        mach_header_32_t
# define MY_MACHO_MAGIC         IMAGE_MACHO32_SIGNATURE
# define MY_SEGMENT_COMMAND     segment_command_32_t
# define MY_SECTION             section_32_t
# define MY_NLIST               macho_nlist_32_t

#elif defined(RT_ARCH_AMD64)
# define MY_CPU_TYPE            CPU_TYPE_X86_64
# define MY_CPU_SUBTYPE_ALL     CPU_SUBTYPE_X86_64_ALL
# define MY_MACHO_HEADER        mach_header_64_t
# define MY_MACHO_MAGIC         IMAGE_MACHO64_SIGNATURE
# define MY_SEGMENT_COMMAND     segment_command_64_t
# define MY_SECTION             section_64_t
# define MY_NLIST               macho_nlist_64_t

#else
# error "Port me!"
#endif

/** @name Return macros for make it simpler to track down too paranoid code.
 * @{
 */
#ifdef DEBUG
# define RETURN_VERR_BAD_EXE_FORMAT \
    do { Assert(!g_fBreakpointOnError);         return VERR_BAD_EXE_FORMAT; } while (0)
# define RETURN_VERR_LDR_UNEXPECTED \
    do { Assert(!g_fBreakpointOnError);         return VERR_LDR_UNEXPECTED; } while (0)
# define RETURN_VERR_LDR_ARCH_MISMATCH \
    do { Assert(!g_fBreakpointOnError);         return VERR_LDR_ARCH_MISMATCH; } while (0)
#else
# define RETURN_VERR_BAD_EXE_FORMAT     do {    return VERR_BAD_EXE_FORMAT; } while (0)
# define RETURN_VERR_LDR_UNEXPECTED     do {    return VERR_LDR_UNEXPECTED; } while (0)
# define RETURN_VERR_LDR_ARCH_MISMATCH  do {    return VERR_LDR_ARCH_MISMATCH; } while (0)
#endif
/** @} */

#define VERR_LDR_UNEXPECTED     (-641)


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
/**
 * Our internal representation of the mach_kernel after loading it's symbols
 * and successfully resolving their addresses.
 */
typedef struct RTDBGKRNLINFOINT
{
    /** Magic value (RTDBGKRNLINFO_MAGIC). */
    uint32_t            u32Magic;
    /** Reference counter.  */
    uint32_t volatile   cRefs;

    /** @name Result.
     * @{ */
    /** Pointer to the string table. */
    char               *pachStrTab;
    /** The size of the string table. */
    uint32_t            cbStrTab;
    /** The file offset of the string table. */
    uint32_t            offStrTab;
    /** Pointer to the symbol table. */
    MY_NLIST           *paSyms;
    /** The size of the symbol table. */
    uint32_t            cSyms;
    /** The file offset of the symbol table. */
    uint32_t            offSyms;
    /** Offset between link address and actual load address. */
    uintptr_t           offLoad;
    /** @} */

    /** @name Used during loading.
     * @{ */
    /** The file handle.  */
    RTFILE              hFile;
    /** The architecture image offset (fat_arch_t::offset). */
    uint64_t            offArch;
    /** The architecture image size (fat_arch_t::size). */
    uint32_t            cbArch;
    /** The number of load commands (mach_header_XX_t::ncmds). */
    uint32_t            cLoadCmds;
    /** The size of the load commands. */
    uint32_t            cbLoadCmds;
    /** The load commands. */
    load_command_t     *pLoadCmds;
    /** Section pointer table (points into the load commands). */
    MY_SECTION const   *apSections[MACHO_MAX_SECT];
    /** The number of sections. */
    uint32_t            cSections;
    /** @} */

    /** Buffer space. */
    char                abBuf[_4K];
} RTDBGKRNLINFOINT;


/*******************************************************************************
*   Structures and Typedefs                                                    *
*******************************************************************************/
#ifdef DEBUG
static bool g_fBreakpointOnError = false;
#endif


#ifdef IN_RING0

/** Default file permissions for newly created files. */
#if defined(S_IRUSR) && defined(S_IWUSR)
# define RT_FILE_PERMISSION  (S_IRUSR | S_IWUSR)
#else
# define RT_FILE_PERMISSION  (00600)
#endif

/**
 * Darwin kernel file handle data.
 */
typedef struct RTFILEINT
{
    /** Magic value (RTFILE_MAGIC). */
    uint32_t        u32Magic;
    /** The open mode flags passed to the kernel API. */
    int             fOpenMode;
    /** The open flags passed to RTFileOpen. */
    uint64_t        fOpen;
    /** The VFS context in which the file was opened. */
    vfs_context_t   hVfsCtx;
    /** The vnode returned by vnode_open. */
    vnode_t         hVnode;
} RTFILEINT;
/** Magic number for RTFILEINT::u32Magic (To Be Determined). */
#define RTFILE_MAGIC                    UINT32_C(0x01020304)


RTDECL(int) RTFileOpen(PRTFILE phFile, const char *pszFilename, uint64_t fOpen)
{
    RTFILEINT *pThis = (RTFILEINT *)RTMemAllocZ(sizeof(*pThis));
    if (!pThis)
        return VERR_NO_MEMORY;

    errno_t rc;
    pThis->u32Magic = RTFILE_MAGIC;
    pThis->fOpen    = fOpen;
    pThis->hVfsCtx  = vfs_context_current();
    if (pThis->hVfsCtx != NULL)
    {
        int             fCMode    = (fOpen & RTFILE_O_CREATE_MODE_MASK)
                                  ? (fOpen & RTFILE_O_CREATE_MODE_MASK) >> RTFILE_O_CREATE_MODE_SHIFT
                                  : RT_FILE_PERMISSION;
        int             fVnFlags  = 0; /* VNODE_LOOKUP_XXX */
        int             fOpenMode = 0;
        if (fOpen & RTFILE_O_NON_BLOCK)
            fOpenMode |= O_NONBLOCK;
        if (fOpen & RTFILE_O_WRITE_THROUGH)
            fOpenMode |= O_SYNC;

        /* create/truncate file */
        switch (fOpen & RTFILE_O_ACTION_MASK)
        {
            case RTFILE_O_OPEN:             break;
            case RTFILE_O_OPEN_CREATE:      fOpenMode |= O_CREAT; break;
            case RTFILE_O_CREATE:           fOpenMode |= O_CREAT | O_EXCL; break;
            case RTFILE_O_CREATE_REPLACE:   fOpenMode |= O_CREAT | O_TRUNC; break; /** @todo replacing needs fixing, this is *not* a 1:1 mapping! */
        }
        if (fOpen & RTFILE_O_TRUNCATE)
            fOpenMode |= O_TRUNC;

        switch (fOpen & RTFILE_O_ACCESS_MASK)
        {
            case RTFILE_O_READ:
                fOpenMode |= FREAD;
                break;
            case RTFILE_O_WRITE:
                fOpenMode |= fOpen & RTFILE_O_APPEND ? O_APPEND | FWRITE : FWRITE;
                break;
            case RTFILE_O_READWRITE:
                fOpenMode |= fOpen & RTFILE_O_APPEND ? O_APPEND | FWRITE | FREAD : FWRITE | FREAD;
                break;
            default:
                AssertMsgFailed(("RTFileOpen received an invalid RW value, fOpen=%#x\n", fOpen));
                return VERR_INVALID_PARAMETER;
        }

        pThis->fOpenMode = fOpenMode;
        rc = vnode_open(pszFilename, fOpenMode, fCMode, fVnFlags, &pThis->hVnode, pThis->hVfsCtx);
        if (rc == 0)
        {
            *phFile = pThis;
            return VINF_SUCCESS;
        }

        rc = RTErrConvertFromErrno(rc);
    }
    else
        rc = VERR_INTERNAL_ERROR_5;
    RTMemFree(pThis);

    return rc;
}


RTDECL(int) RTFileClose(RTFILE hFile)
{
    if (hFile == NIL_RTFILE)
        return VINF_SUCCESS;

    RTFILEINT *pThis = hFile;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTFILE_MAGIC, VERR_INVALID_HANDLE);
    pThis->u32Magic = ~RTFILE_MAGIC;

    errno_t rc = vnode_close(pThis->hVnode, pThis->fOpenMode & (FREAD | FWRITE), pThis->hVfsCtx);

    RTMemFree(pThis);
    return RTErrConvertFromErrno(rc);
}


RTDECL(int) RTFileReadAt(RTFILE hFile, RTFOFF off, void *pvBuf, size_t cbToRead, size_t *pcbRead)
{
    RTFILEINT *pThis = hFile;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertReturn(pThis->u32Magic == RTFILE_MAGIC, VERR_INVALID_HANDLE);

    off_t offNative = (off_t)off;
    AssertReturn((RTFOFF)offNative == off, VERR_OUT_OF_RANGE);

#if 0 /* Added in 10.6, grr. */
    errno_t rc;
    if (!pcbRead)
        rc = vn_rdwr(UIO_READ, pThis->hVnode, (char *)pvBuf, cbToRead, offNative, UIO_SYSSPACE, 0 /*ioflg*/,
                     vfs_context_ucred(pThis->hVfsCtx), NULL, vfs_context_proc(pThis->hVfsCtx));
    else
    {
        int cbLeft = 0;
        rc = vn_rdwr(UIO_READ, pThis->hVnode, (char *)pvBuf, cbToRead, offNative, UIO_SYSSPACE, 0 /*ioflg*/,
                     vfs_context_ucred(pThis->hVfsCtx), &cbLeft, vfs_context_proc(pThis->hVfsCtx));
        *pcbRead = cbToRead - cbLeft;
    }
    return !rc ? VINF_SUCCESS : RTErrConvertFromErrno(rc);

#else
    uio_t hUio = uio_create(1, offNative, UIO_SYSSPACE, UIO_READ);
    if (!hUio)
        return VERR_NO_MEMORY;
    errno_t rc;
    if (uio_addiov(hUio, (user_addr_t)(uintptr_t)pvBuf, cbToRead) == 0)
    {
        rc = VNOP_READ(pThis->hVnode, hUio, 0 /*ioflg*/, pThis->hVfsCtx);
        if (pcbRead)
            *pcbRead = cbToRead - uio_resid(hUio);
        else if (!rc && uio_resid(hUio))
            rc = VERR_FILE_IO_ERROR;
    }
    else
        rc = VERR_INTERNAL_ERROR_3;
    uio_free(hUio);
    return rc;

#endif
}

#endif


/**
 * Close and free up resources we no longer needs.
 *
 * @param   pThis               The internal scratch data.
 */
static void rtR0DbgKrnlDarwinLoadDone(RTDBGKRNLINFOINT *pThis)
{
    RTFileClose(pThis->hFile);
    pThis->hFile = NIL_RTFILE;

    RTMemFree(pThis->pLoadCmds);
    pThis->pLoadCmds = NULL;
    memset((void *)&pThis->apSections[0], 0, sizeof(pThis->apSections[0]) * MACHO_MAX_SECT);
}


/**
 * Looks up a kernel symbol.
 *
 * @returns The symbol address on success, 0 on failure.
 * @param   pThis               The internal scratch data.
 * @param   pszSymbol           The symbol to resolve.  Automatically prefixed
 *                              with an underscore.
 */
static uintptr_t rtR0DbgKrnlDarwinLookup(RTDBGKRNLINFOINT *pThis, const char *pszSymbol)
{
    uint32_t const  cSyms = pThis->cSyms;
    MY_NLIST const *pSym = pThis->paSyms;

#if 1
    /* linear search. */
    for (uint32_t iSym = 0; iSym < cSyms; iSym++, pSym++)
    {
        if (pSym->n_type & MACHO_N_STAB)
            continue;

        const char *pszTabName= &pThis->pachStrTab[(uint32_t)pSym->n_un.n_strx];
        if (   *pszTabName == '_'
            && strcmp(pszTabName + 1, pszSymbol) == 0)
            return pSym->n_value + pThis->offLoad;
    }
#else
    /** @todo binary search. */

#endif
    return 0;
}


/* Rainy day: Find the right headers for these symbols ... if there are any. */
extern "C" void ev_try_lock(void);
extern "C" void OSMalloc(void);
extern "C" void OSlibkernInit(void);
extern "C" void kdp_set_interface(void);


/**
 * Check the symbol table against symbols we known symbols.
 *
 * This is done to detect whether the on disk image and the in
 * memory images matches.  Mismatches could stem from user
 * replacing the default kernel image on disk.
 *
 * @returns IPRT status code.
 * @param   pThis               The internal scratch data.
 */
static int rtR0DbgKrnlDarwinCheckStandardSymbols(RTDBGKRNLINFOINT *pThis)
{
    static struct
    {
        const char *pszName;
        uintptr_t   uAddr;
    } const s_aStandardCandles[] =
    {
#ifdef IN_RING0
# define KNOWN_ENTRY(a_Sym)  { #a_Sym, (uintptr_t)&a_Sym }
#else
# define KNOWN_ENTRY(a_Sym)  { #a_Sym, 0 }
#endif
        /* IOKit: */
        KNOWN_ENTRY(IOMalloc),
        KNOWN_ENTRY(IOFree),
        KNOWN_ENTRY(IOSleep),
        KNOWN_ENTRY(IORWLockAlloc),
        KNOWN_ENTRY(IORecursiveLockLock),
        KNOWN_ENTRY(IOSimpleLockAlloc),
        KNOWN_ENTRY(PE_cpu_halt),
        KNOWN_ENTRY(gIOKitDebug),
        KNOWN_ENTRY(gIOServicePlane),
        KNOWN_ENTRY(ev_try_lock),

        /* Libkern: */
        KNOWN_ENTRY(OSAddAtomic),
        KNOWN_ENTRY(OSBitAndAtomic),
        KNOWN_ENTRY(OSBitOrAtomic),
        KNOWN_ENTRY(OSBitXorAtomic),
        KNOWN_ENTRY(OSCompareAndSwap),
        KNOWN_ENTRY(OSMalloc),
        KNOWN_ENTRY(OSlibkernInit),
        KNOWN_ENTRY(bcmp),
        KNOWN_ENTRY(copyout),
        KNOWN_ENTRY(copyin),
        KNOWN_ENTRY(kprintf),
        KNOWN_ENTRY(printf),
        KNOWN_ENTRY(lck_grp_alloc_init),
        KNOWN_ENTRY(lck_mtx_alloc_init),
        KNOWN_ENTRY(lck_rw_alloc_init),
        KNOWN_ENTRY(lck_spin_alloc_init),
        KNOWN_ENTRY(osrelease),
        KNOWN_ENTRY(ostype),
        KNOWN_ENTRY(panic),
        KNOWN_ENTRY(strprefix),
        KNOWN_ENTRY(sysctlbyname),
        KNOWN_ENTRY(vsscanf),
        KNOWN_ENTRY(page_mask),

        /* Mach: */
        KNOWN_ENTRY(absolutetime_to_nanoseconds),
        KNOWN_ENTRY(assert_wait),
        KNOWN_ENTRY(clock_delay_until),
        KNOWN_ENTRY(clock_get_uptime),
        KNOWN_ENTRY(current_task),
        KNOWN_ENTRY(current_thread),
        KNOWN_ENTRY(kernel_task),
        KNOWN_ENTRY(lck_mtx_sleep),
        KNOWN_ENTRY(lck_rw_sleep),
        KNOWN_ENTRY(lck_spin_sleep),
        KNOWN_ENTRY(mach_absolute_time),
        KNOWN_ENTRY(semaphore_create),
        KNOWN_ENTRY(task_reference),
        KNOWN_ENTRY(thread_block),
        KNOWN_ENTRY(thread_reference),
        KNOWN_ENTRY(thread_terminate),
        KNOWN_ENTRY(thread_wakeup_prim),

        /* BSDKernel: */
        KNOWN_ENTRY(buf_size),
        KNOWN_ENTRY(copystr),
        KNOWN_ENTRY(current_proc),
        KNOWN_ENTRY(ifnet_hdrlen),
        KNOWN_ENTRY(ifnet_set_promiscuous),
        KNOWN_ENTRY(kauth_getuid),
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1050
        KNOWN_ENTRY(kauth_cred_unref),
#else
        KNOWN_ENTRY(kauth_cred_rele),
#endif
        KNOWN_ENTRY(mbuf_data),
        KNOWN_ENTRY(msleep),
        KNOWN_ENTRY(nanotime),
        KNOWN_ENTRY(nop_close),
        KNOWN_ENTRY(proc_pid),
        KNOWN_ENTRY(sock_accept),
        KNOWN_ENTRY(sockopt_name),
        //KNOWN_ENTRY(spec_write),
        KNOWN_ENTRY(suword),
        //KNOWN_ENTRY(sysctl_int),
        KNOWN_ENTRY(uio_rw),
        KNOWN_ENTRY(vfs_flags),
        KNOWN_ENTRY(vfs_name),
        KNOWN_ENTRY(vfs_statfs),
        KNOWN_ENTRY(VNOP_READ),
        KNOWN_ENTRY(uio_create),
        KNOWN_ENTRY(uio_addiov),
        KNOWN_ENTRY(uio_free),
        KNOWN_ENTRY(vnode_get),
        KNOWN_ENTRY(vnode_open),
        KNOWN_ENTRY(vnode_ref),
        KNOWN_ENTRY(vnode_rele),
        KNOWN_ENTRY(vnop_close_desc),
        KNOWN_ENTRY(wakeup),
        KNOWN_ENTRY(wakeup_one),

        /* Unsupported: */
        KNOWN_ENTRY(kdp_set_interface),
        KNOWN_ENTRY(pmap_find_phys),
        KNOWN_ENTRY(vm_map),
        KNOWN_ENTRY(vm_protect),
        KNOWN_ENTRY(vm_region),
        KNOWN_ENTRY(vm_map_wire),
        KNOWN_ENTRY(PE_kputc),
        KNOWN_ENTRY(kernel_map),
        KNOWN_ENTRY(kernel_pmap),
    };

    for (unsigned i = 0; i < RT_ELEMENTS(s_aStandardCandles); i++)
    {
        uintptr_t uAddr = rtR0DbgKrnlDarwinLookup(pThis, s_aStandardCandles[i].pszName);
#ifdef IN_RING0
        if (uAddr != s_aStandardCandles[i].uAddr)
#else
        if (uAddr == 0)
#endif
        {
            AssertLogRelMsgFailed(("%s (%p != %p)\n", s_aStandardCandles[i].pszName, uAddr, s_aStandardCandles[i].uAddr));
            return VERR_INTERNAL_ERROR_2;
        }
    }
    return VINF_SUCCESS;
}


/**
 * Loads and validates the symbol and string tables.
 *
 * @returns IPRT status code.
 * @param   pThis               The internal scratch data.
 */
static int rtR0DbgKrnlDarwinLoadSymTab(RTDBGKRNLINFOINT *pThis)
{
    /*
     * Load the tables.
     */
    pThis->paSyms = (MY_NLIST *)RTMemAllocZ(pThis->cSyms * sizeof(MY_NLIST));
    if (!pThis->paSyms)
        return VERR_NO_MEMORY;

    int rc = RTFileReadAt(pThis->hFile, pThis->offArch + pThis->offSyms,
                          pThis->paSyms, pThis->cSyms * sizeof(MY_NLIST), NULL);
    if (RT_FAILURE(rc))
        return rc;

    pThis->pachStrTab = (char *)RTMemAllocZ(pThis->cbStrTab + 1);
    if (!pThis->pachStrTab)
        return VERR_NO_MEMORY;

    rc = RTFileReadAt(pThis->hFile, pThis->offArch + pThis->offStrTab,
                      pThis->pachStrTab, pThis->cbStrTab, NULL);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * The first string table symbol must be a zero length name.
     */
    if (pThis->pachStrTab[0] != '\0')
        RETURN_VERR_BAD_EXE_FORMAT;

    /*
     * Validate the symbol table.
     */
    const char     *pszPrev = "";
    uint32_t const  cSyms   = pThis->cSyms;
    MY_NLIST const  *pSym   = pThis->paSyms;
    for (uint32_t iSym = 0; iSym < cSyms; iSym++, pSym++)
    {
        if ((uint32_t)pSym->n_un.n_strx >= pThis->cbStrTab)
            RETURN_VERR_BAD_EXE_FORMAT;
        const char *pszSym = &pThis->pachStrTab[(uint32_t)pSym->n_un.n_strx];
#ifdef IN_RING3
        RTAssertMsg2("%05i: %02x:%08llx %02x %04x %s\n", iSym, pSym->n_sect, (uint64_t)pSym->n_value, pSym->n_type, pSym->n_desc, pszSym);
#endif

        if (strcmp(pszSym, pszPrev) < 0)
            RETURN_VERR_BAD_EXE_FORMAT; /* not sorted */

        if (!(pSym->n_type & MACHO_N_STAB))
        {
            switch (pSym->n_type & MACHO_N_TYPE)
            {
                case MACHO_N_SECT:
                    if (pSym->n_sect == MACHO_NO_SECT)
                        RETURN_VERR_BAD_EXE_FORMAT;
                    if (pSym->n_sect > pThis->cSections)
                        RETURN_VERR_BAD_EXE_FORMAT;
                    if (pSym->n_desc & ~(REFERENCED_DYNAMICALLY))
                        RETURN_VERR_BAD_EXE_FORMAT;
                    if (   pSym->n_value < pThis->apSections[pSym->n_sect - 1]->addr
                        && strcmp(pszSym, "__mh_execute_header"))    /* in 10.8 it's no longer absolute (PIE?). */
                        RETURN_VERR_BAD_EXE_FORMAT;
                    if (      pSym->n_value - pThis->apSections[pSym->n_sect - 1]->addr
                           > pThis->apSections[pSym->n_sect - 1]->size
                        && strcmp(pszSym, "__mh_execute_header"))    /* see above. */
                        RETURN_VERR_BAD_EXE_FORMAT;
                    break;

                case MACHO_N_ABS:
                    if (   pSym->n_sect != MACHO_NO_SECT
                        && (   strcmp(pszSym, "__mh_execute_header") /* n_sect=1 in 10.7/amd64 */
                            || pSym->n_sect > pThis->cSections) )
                        RETURN_VERR_BAD_EXE_FORMAT;
                    if (pSym->n_desc & ~(REFERENCED_DYNAMICALLY))
                        RETURN_VERR_BAD_EXE_FORMAT;
                    break;

                case MACHO_N_UNDF:
                    /* No undefined or common symbols in the kernel. */
                    RETURN_VERR_BAD_EXE_FORMAT;

                case MACHO_N_INDR:
                    /* No indirect symbols in the kernel. */
                    RETURN_VERR_BAD_EXE_FORMAT;

                case MACHO_N_PBUD:
                    /* No prebound symbols in the kernel. */
                    RETURN_VERR_BAD_EXE_FORMAT;

                default:
                    RETURN_VERR_BAD_EXE_FORMAT;
            }
        }
        /* else: Ignore debug symbols. */
    }

    return VINF_SUCCESS;
}


/**
 * Loads the load commands and validates them.
 *
 * @returns IPRT status code.
 * @param   pThis               The internal scratch data.
 */
static int rtR0DbgKrnlDarwinLoadCommands(RTDBGKRNLINFOINT *pThis)
{
    pThis->offStrTab = 0;
    pThis->cbStrTab  = 0;
    pThis->offSyms   = 0;
    pThis->cSyms     = 0;
    pThis->cSections = 0;

    pThis->pLoadCmds = (load_command_t *)RTMemAlloc(pThis->cbLoadCmds);
    if (!pThis->pLoadCmds)
        return VERR_NO_MEMORY;

    int rc = RTFileReadAt(pThis->hFile, pThis->offArch + sizeof(MY_MACHO_HEADER),
                          pThis->pLoadCmds, pThis->cbLoadCmds, NULL);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Validate the relevant commands, picking up sections and the symbol
     * table location.
     */
    load_command_t const   *pCmd = pThis->pLoadCmds;
    for (uint32_t iCmd = 0; ; iCmd++)
    {
        /* cmd index & offset. */
        uintptr_t offCmd = (uintptr_t)pCmd - (uintptr_t)pThis->pLoadCmds;
        if (offCmd == pThis->cbLoadCmds && iCmd == pThis->cLoadCmds)
            break;
        if (offCmd + sizeof(*pCmd) > pThis->cbLoadCmds)
            RETURN_VERR_BAD_EXE_FORMAT;
        if (iCmd >= pThis->cLoadCmds)
            RETURN_VERR_BAD_EXE_FORMAT;

        /* cmdsize */
        if (pCmd->cmdsize < sizeof(*pCmd))
            RETURN_VERR_BAD_EXE_FORMAT;
        if (pCmd->cmdsize > pThis->cbLoadCmds)
            RETURN_VERR_BAD_EXE_FORMAT;
        if (RT_ALIGN_32(pCmd->cmdsize, 4) != pCmd->cmdsize)
            RETURN_VERR_BAD_EXE_FORMAT;

        /* cmd */
        switch (pCmd->cmd & ~LC_REQ_DYLD)
        {
            /* Validate and store the symbol table details. */
            case LC_SYMTAB:
            {
                struct symtab_command const *pSymTab = (struct symtab_command const *)pCmd;
                if (pSymTab->cmdsize != sizeof(*pSymTab))
                    RETURN_VERR_BAD_EXE_FORMAT;
                if (pSymTab->nsyms > _1M)
                    RETURN_VERR_BAD_EXE_FORMAT;
                if (pSymTab->strsize > _2M)
                    RETURN_VERR_BAD_EXE_FORMAT;

                pThis->offStrTab = pSymTab->stroff;
                pThis->cbStrTab  = pSymTab->strsize;
                pThis->offSyms   = pSymTab->symoff;
                pThis->cSyms     = pSymTab->nsyms;
                break;
            }

            /* Validate the segment. */
#if ARCH_BITS == 32
            case LC_SEGMENT_32:
#elif ARCH_BITS == 64
            case LC_SEGMENT_64:
#else
# error ARCH_BITS
#endif
            {
                MY_SEGMENT_COMMAND const *pSeg = (MY_SEGMENT_COMMAND const *)pCmd;
                if (pSeg->cmdsize < sizeof(*pSeg))
                    RETURN_VERR_BAD_EXE_FORMAT;

                if (pSeg->segname[0] == '\0')
                    RETURN_VERR_BAD_EXE_FORMAT;

                if (pSeg->nsects > MACHO_MAX_SECT)
                    RETURN_VERR_BAD_EXE_FORMAT;
                if (pSeg->nsects * sizeof(MY_SECTION) + sizeof(*pSeg) != pSeg->cmdsize)
                    RETURN_VERR_BAD_EXE_FORMAT;

                if (pSeg->flags & ~(SG_HIGHVM | SG_FVMLIB | SG_NORELOC | SG_PROTECTED_VERSION_1))
                    RETURN_VERR_BAD_EXE_FORMAT;

                if (   pSeg->vmaddr != 0
                    || !strcmp(pSeg->segname, "__PAGEZERO"))
                {
                    if (pSeg->vmaddr + RT_ALIGN_Z(pSeg->vmsize, RT_BIT_32(12)) < pSeg->vmaddr)
                        RETURN_VERR_BAD_EXE_FORMAT;
                }
                else if (pSeg->vmsize)
                    RETURN_VERR_BAD_EXE_FORMAT;

                if (pSeg->maxprot & ~VM_PROT_ALL)
                    RETURN_VERR_BAD_EXE_FORMAT;
                if (pSeg->initprot & ~VM_PROT_ALL)
                    RETURN_VERR_BAD_EXE_FORMAT;

                /* Validate the sections. */
                uint32_t            uAlignment = 0;
                uintptr_t           uAddr      = pSeg->vmaddr;
                MY_SECTION const   *paSects    = (MY_SECTION const *)(pSeg + 1);
                for (uint32_t i = 0; i < pSeg->nsects; i++)
                {
                    if (paSects[i].sectname[0] == '\0')
                        RETURN_VERR_BAD_EXE_FORMAT;
                    if (memcmp(paSects[i].segname, pSeg->segname, sizeof(pSeg->segname)))
                        RETURN_VERR_BAD_EXE_FORMAT;

                    switch (paSects[i].flags & SECTION_TYPE)
                    {
                        case S_REGULAR:
                        case S_CSTRING_LITERALS:
                        case S_NON_LAZY_SYMBOL_POINTERS:
                        case S_MOD_INIT_FUNC_POINTERS:
                        case S_MOD_TERM_FUNC_POINTERS:
                        case S_COALESCED:
                        case S_4BYTE_LITERALS:
                            if (  pSeg->filesize != 0
                                ? paSects[i].offset - pSeg->fileoff >= pSeg->filesize
                                : paSects[i].offset - pSeg->fileoff != pSeg->filesize)
                                RETURN_VERR_BAD_EXE_FORMAT;
                            if (   paSects[i].addr != 0
                                && paSects[i].offset - pSeg->fileoff != paSects[i].addr - pSeg->vmaddr)
                                RETURN_VERR_BAD_EXE_FORMAT;
                            break;

                        case S_ZEROFILL:
                            if (paSects[i].offset != 0)
                                RETURN_VERR_BAD_EXE_FORMAT;
                            break;

                        /* not observed */
                        case S_SYMBOL_STUBS:
                        case S_INTERPOSING:
                        case S_8BYTE_LITERALS:
                        case S_16BYTE_LITERALS:
                        case S_DTRACE_DOF:
                        case S_LAZY_SYMBOL_POINTERS:
                        case S_LAZY_DYLIB_SYMBOL_POINTERS:
                            RETURN_VERR_LDR_UNEXPECTED;
                        case S_GB_ZEROFILL:
                            RETURN_VERR_LDR_UNEXPECTED;
                        default:
                            RETURN_VERR_BAD_EXE_FORMAT;
                    }

                    if (paSects[i].align > 12)
                        RETURN_VERR_BAD_EXE_FORMAT;
                    if (paSects[i].align > uAlignment)
                        uAlignment = paSects[i].align;

                    /* Add to the section table. */
                    if (pThis->cSections == MACHO_MAX_SECT)
                        RETURN_VERR_BAD_EXE_FORMAT;
                    pThis->apSections[pThis->cSections++] = &paSects[i];
                }

                if (RT_ALIGN_Z(pSeg->vmaddr, RT_BIT_32(uAlignment)) != pSeg->vmaddr)
                    RETURN_VERR_BAD_EXE_FORMAT;
                if (   pSeg->filesize > RT_ALIGN_Z(pSeg->vmsize, RT_BIT_32(uAlignment))
                    && pSeg->vmsize != 0)
                    RETURN_VERR_BAD_EXE_FORMAT;
                break;
            }

            case LC_UUID:
                if (pCmd->cmdsize != sizeof(uuid_command))
                    RETURN_VERR_BAD_EXE_FORMAT;
                break;

            case LC_DYSYMTAB:
            case LC_UNIXTHREAD:
            case LC_CODE_SIGNATURE:
            case LC_VERSION_MIN_MACOSX:
            case LC_FUNCTION_STARTS:
            case LC_MAIN:
            case LC_DATA_IN_CODE:
            case LC_SOURCE_VERSION:
                break;

            /* not observed */
            case LC_SYMSEG:
#if ARCH_BITS == 32
            case LC_SEGMENT_64:
#elif ARCH_BITS == 64
            case LC_SEGMENT_32:
#endif
            case LC_ROUTINES_64:
            case LC_ROUTINES:
            case LC_THREAD:
            case LC_LOADFVMLIB:
            case LC_IDFVMLIB:
            case LC_IDENT:
            case LC_FVMFILE:
            case LC_PREPAGE:
            case LC_TWOLEVEL_HINTS:
            case LC_PREBIND_CKSUM:
            case LC_SEGMENT_SPLIT_INFO:
            case LC_ENCRYPTION_INFO:
                RETURN_VERR_LDR_UNEXPECTED;

            /* no phones here yet */
            case LC_VERSION_MIN_IPHONEOS:
                RETURN_VERR_LDR_UNEXPECTED;

            /* dylib */
            case LC_LOAD_DYLIB:
            case LC_ID_DYLIB:
            case LC_LOAD_DYLINKER:
            case LC_ID_DYLINKER:
            case LC_PREBOUND_DYLIB:
            case LC_LOAD_WEAK_DYLIB & ~LC_REQ_DYLD:
            case LC_SUB_FRAMEWORK:
            case LC_SUB_UMBRELLA:
            case LC_SUB_CLIENT:
            case LC_SUB_LIBRARY:
            case LC_RPATH:
            case LC_REEXPORT_DYLIB:
            case LC_LAZY_LOAD_DYLIB:
            case LC_DYLD_INFO:
            case LC_DYLD_INFO_ONLY:
            case LC_LOAD_UPWARD_DYLIB:
            case LC_DYLD_ENVIRONMENT:
            case LC_DYLIB_CODE_SIGN_DRS:
                RETURN_VERR_LDR_UNEXPECTED;

            default:
                RETURN_VERR_BAD_EXE_FORMAT;
        }

        /* next */
        pCmd = (load_command_t *)((uintptr_t)pCmd + pCmd->cmdsize);
    }

    return VINF_SUCCESS;
}


/**
 * Loads the FAT and MACHO headers, noting down the relevant info.
 *
 * @returns IPRT status code.
 * @param   pThis               The internal scratch data.
 */
static int rtR0DbgKrnlDarwinLoadFileHeaders(RTDBGKRNLINFOINT *pThis)
{
    uint32_t i;

    pThis->offArch = 0;
    pThis->cbArch  = 0;

    /*
     * Read the first bit of the file, parse the FAT if found there.
     */
    int rc = RTFileReadAt(pThis->hFile, 0, pThis->abBuf, sizeof(fat_header_t) + sizeof(fat_arch_t) * 16, NULL);
    if (RT_FAILURE(rc))
        return rc;

    fat_header_t   *pFat        = (fat_header *)pThis->abBuf;
    fat_arch_t     *paFatArches = (fat_arch_t *)(pFat + 1);

    /* Correct FAT endian first. */
    if (pFat->magic == IMAGE_FAT_SIGNATURE_OE)
    {
        pFat->magic     = RT_BSWAP_U32(pFat->magic);
        pFat->nfat_arch = RT_BSWAP_U32(pFat->nfat_arch);
        i = RT_MIN(pFat->nfat_arch, 16);
        while (i-- > 0)
        {
            paFatArches[i].cputype    = RT_BSWAP_U32(paFatArches[i].cputype);
            paFatArches[i].cpusubtype = RT_BSWAP_U32(paFatArches[i].cpusubtype);
            paFatArches[i].offset     = RT_BSWAP_U32(paFatArches[i].offset);
            paFatArches[i].size       = RT_BSWAP_U32(paFatArches[i].size);
            paFatArches[i].align      = RT_BSWAP_U32(paFatArches[i].align);
        }
    }

    /* Lookup our architecture in the FAT. */
    if (pFat->magic == IMAGE_FAT_SIGNATURE)
    {
        if (pFat->nfat_arch > 16)
            RETURN_VERR_BAD_EXE_FORMAT;

        for (i = 0; i < pFat->nfat_arch; i++)
        {
            if (   paFatArches[i].cputype == MY_CPU_TYPE
                && paFatArches[i].cpusubtype == MY_CPU_SUBTYPE_ALL)
            {
                pThis->offArch = paFatArches[i].offset;
                pThis->cbArch  = paFatArches[i].size;
                if (!pThis->cbArch)
                    RETURN_VERR_BAD_EXE_FORMAT;
                if (pThis->offArch < sizeof(fat_header_t) + sizeof(fat_arch_t) * pFat->nfat_arch)
                    RETURN_VERR_BAD_EXE_FORMAT;
                if (pThis->offArch + pThis->cbArch <= pThis->offArch)
                    RETURN_VERR_LDR_ARCH_MISMATCH;
                break;
            }
        }
        if (i >= pFat->nfat_arch)
            RETURN_VERR_LDR_ARCH_MISMATCH;
    }

    /*
     * Read the Mach-O header and validate it.
     */
    rc = RTFileReadAt(pThis->hFile, pThis->offArch, pThis->abBuf, sizeof(MY_MACHO_HEADER), NULL);
    if (RT_FAILURE(rc))
        return rc;
    MY_MACHO_HEADER const *pHdr = (MY_MACHO_HEADER const *)pThis->abBuf;
    if (pHdr->magic != MY_MACHO_MAGIC)
    {
        if (   pHdr->magic == IMAGE_MACHO32_SIGNATURE
            || pHdr->magic == IMAGE_MACHO32_SIGNATURE_OE
            || pHdr->magic == IMAGE_MACHO64_SIGNATURE
            || pHdr->magic == IMAGE_MACHO64_SIGNATURE_OE)
            RETURN_VERR_LDR_ARCH_MISMATCH;
        RETURN_VERR_BAD_EXE_FORMAT;
    }

    if (pHdr->cputype    != MY_CPU_TYPE)
        RETURN_VERR_LDR_ARCH_MISMATCH;
    if (pHdr->cpusubtype != MY_CPU_SUBTYPE_ALL)
        RETURN_VERR_LDR_ARCH_MISMATCH;
    if (pHdr->filetype   != MH_EXECUTE)
        RETURN_VERR_LDR_UNEXPECTED;
    if (pHdr->ncmds      < 4)
        RETURN_VERR_LDR_UNEXPECTED;
    if (pHdr->ncmds      > 256)
        RETURN_VERR_LDR_UNEXPECTED;
    if (pHdr->sizeofcmds <= pHdr->ncmds * sizeof(load_command_t))
        RETURN_VERR_LDR_UNEXPECTED;
    if (pHdr->sizeofcmds >= _1M)
        RETURN_VERR_LDR_UNEXPECTED;
    if (pHdr->flags & ~MH_VALID_FLAGS)
        RETURN_VERR_LDR_UNEXPECTED;

    pThis->cLoadCmds  = pHdr->ncmds;
    pThis->cbLoadCmds = pHdr->sizeofcmds;
    return VINF_SUCCESS;
}


/**
 * Destructor.
 *
 * @param   pThis               The instance to destroy.
 */
static void rtR0DbgKrnlDarwinDtor(RTDBGKRNLINFOINT *pThis)
{
    pThis->u32Magic = ~RTDBGKRNLINFO_MAGIC;

    RTMemFree(pThis->pachStrTab);
    pThis->pachStrTab = NULL;

    RTMemFree(pThis->paSyms);
    pThis->paSyms = NULL;

    RTMemFree(pThis);
}


RTR0DECL(int) RTR0DbgKrnlInfoOpen(PRTDBGKRNLINFO phKrnlInfo, uint32_t fFlags)
{
    AssertPtrReturn(phKrnlInfo, VERR_INVALID_POINTER);
    *phKrnlInfo = NIL_RTDBGKRNLINFO;
    AssertReturn(!fFlags, VERR_INVALID_PARAMETER);

    RTDBGKRNLINFOINT *pThis = (RTDBGKRNLINFOINT *)RTMemAllocZ(sizeof(*pThis));
    if (!pThis)
        return VERR_NO_MEMORY;
    pThis->hFile = NIL_RTFILE;

    int rc = RTFileOpen(&pThis->hFile, "/mach_kernel", RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE);
    if (RT_SUCCESS(rc))
        rc = rtR0DbgKrnlDarwinLoadFileHeaders(pThis);
    if (RT_SUCCESS(rc))
        rc = rtR0DbgKrnlDarwinLoadCommands(pThis);
    if (RT_SUCCESS(rc))
        rc = rtR0DbgKrnlDarwinLoadSymTab(pThis);
    if (RT_SUCCESS(rc))
    {
#ifdef IN_RING0
        /*
         * Determine the load displacement (10.8 kernels are PIE).
         */
        uintptr_t uLinkAddr = rtR0DbgKrnlDarwinLookup(pThis, "kernel_map");
        if (uLinkAddr != 0)
            pThis->offLoad = (uintptr_t)&kernel_map - uLinkAddr;
#endif
        rc = rtR0DbgKrnlDarwinCheckStandardSymbols(pThis);
    }

    rtR0DbgKrnlDarwinLoadDone(pThis);
    if (RT_SUCCESS(rc))
    {
        pThis->u32Magic = RTDBGKRNLINFO_MAGIC;
        pThis->cRefs    = 1;
        *phKrnlInfo = pThis;
    }
    else
        rtR0DbgKrnlDarwinDtor(pThis);
    return rc;
}


RTR0DECL(uint32_t) RTR0DbgKrnlInfoRetain(RTDBGKRNLINFO hKrnlInfo)
{
    RTDBGKRNLINFOINT *pThis = hKrnlInfo;
    AssertPtrReturn(pThis, UINT32_MAX);
    AssertMsgReturn(pThis->u32Magic == RTDBGKRNLINFO_MAGIC, ("%p: u32Magic=%RX32\n", pThis, pThis->u32Magic), UINT32_MAX);

    uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
    Assert(cRefs && cRefs < 100000);
    return cRefs;
}


RTR0DECL(uint32_t) RTR0DbgKrnlInfoRelease(RTDBGKRNLINFO hKrnlInfo)
{
    RTDBGKRNLINFOINT *pThis = hKrnlInfo;
    if (pThis == NIL_RTDBGKRNLINFO)
        return 0;
    AssertPtrReturn(pThis, UINT32_MAX);
    AssertMsgReturn(pThis->u32Magic == RTDBGKRNLINFO_MAGIC, ("%p: u32Magic=%RX32\n", pThis, pThis->u32Magic), UINT32_MAX);

    uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
    if (cRefs == 0)
        rtR0DbgKrnlDarwinDtor(pThis);
    return cRefs;
}


RTR0DECL(int) RTR0DbgKrnlInfoQueryMember(RTDBGKRNLINFO hKrnlInfo, const char *pszStructure,
                                         const char *pszMember, size_t *poffMember)
{
    RTDBGKRNLINFOINT *pThis = hKrnlInfo;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertMsgReturn(pThis->u32Magic == RTDBGKRNLINFO_MAGIC, ("%p: u32Magic=%RX32\n", pThis, pThis->u32Magic), VERR_INVALID_HANDLE);
    AssertPtrReturn(pszMember, VERR_INVALID_PARAMETER);
    AssertPtrReturn(pszStructure, VERR_INVALID_PARAMETER);
    AssertPtrReturn(poffMember, VERR_INVALID_PARAMETER);
    return VERR_NOT_FOUND;
}


RTR0DECL(int) RTR0DbgKrnlInfoQuerySymbol(RTDBGKRNLINFO hKrnlInfo, const char *pszModule,
                                         const char *pszSymbol, void **ppvSymbol)
{
    RTDBGKRNLINFOINT *pThis = hKrnlInfo;
    AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
    AssertMsgReturn(pThis->u32Magic == RTDBGKRNLINFO_MAGIC, ("%p: u32Magic=%RX32\n", pThis, pThis->u32Magic), VERR_INVALID_HANDLE);
    AssertPtrReturn(pszSymbol, VERR_INVALID_PARAMETER);
    AssertPtrNullReturn(ppvSymbol, VERR_INVALID_PARAMETER);
    AssertReturn(!pszModule, VERR_MODULE_NOT_FOUND);

    uintptr_t uValue = rtR0DbgKrnlDarwinLookup(pThis, pszSymbol);
    if (ppvSymbol)
        *ppvSymbol = (void *)uValue;
    if (uValue)
        return VINF_SUCCESS;
    return VERR_SYMBOL_NOT_FOUND;
}

