diff options
Diffstat (limited to 'src/VBox/Runtime/r0drv/darwin/dbgkrnlinfo-r0drv-darwin.cpp')
-rw-r--r-- | src/VBox/Runtime/r0drv/darwin/dbgkrnlinfo-r0drv-darwin.cpp | 1551 |
1 files changed, 1551 insertions, 0 deletions
diff --git a/src/VBox/Runtime/r0drv/darwin/dbgkrnlinfo-r0drv-darwin.cpp b/src/VBox/Runtime/r0drv/darwin/dbgkrnlinfo-r0drv-darwin.cpp new file mode 100644 index 00000000..56226b96 --- /dev/null +++ b/src/VBox/Runtime/r0drv/darwin/dbgkrnlinfo-r0drv-darwin.cpp @@ -0,0 +1,1551 @@ +/* $Id: dbgkrnlinfo-r0drv-darwin.cpp $ */ +/** @file + * IPRT - Kernel Debug Information, R0 Driver, Darwin. + */ + +/* + * Copyright (C) 2011-2020 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>*/ +#else +# include <stdio.h> /* for printf */ +#endif + +#if !defined(IN_RING0) && !defined(DOXYGEN_RUNNING) /* A linking tweak for the testcase: */ +# include <iprt/cdefs.h> +# undef RTR0DECL +# define RTR0DECL(type) DECLHIDDEN(type) RTCALL +#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 +#if defined(DEBUG_bird) && !defined(IN_RING3) +# define LOG_MISMATCH(...) kprintf(__VA_ARGS__) +# define LOG_NOT_PRESENT(...) kprintf(__VA_ARGS__) +# define LOG_BAD_SYM(...) kprintf(__VA_ARGS__) +# define LOG_SUCCESS(...) kprintf(__VA_ARGS__) +#else +# define LOG_MISMATCH(...) Log((__VA_ARGS__)) +# define LOG_NOT_PRESENT(...) Log((__VA_ARGS__)) +# define LOG_BAD_SYM(...) printf(__VA_ARGS__) +# define LOG_SUCCESS(...) printf(__VA_ARGS__) +#endif +/** @} */ + +#define VERR_LDR_UNEXPECTED (-641) + +#ifndef RT_OS_DARWIN +# define MAC_OS_X_VERSION_MIN_REQUIRED 1050 +#endif + + +/********************************************************************************************************************************* +* 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; + + /** Set if this is an in-memory rather than on-disk instance. */ + bool fIsInMem; + bool afAlignment[7]; + + /** @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; + /** The link address of the string table. */ + uintptr_t uStrTabLinkAddr; + /** 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; + /** The link address of the symbol table. */ + uintptr_t uSymTabLinkAddr; + /** The link address of the text segment. */ + uintptr_t uTextSegLinkAddr; + /** Size of the text segment. */ + uintptr_t cbTextSeg; + /** Offset between link address and actual load address of the text segment. */ + uintptr_t offLoad; + /** The minimum OS version (A.B.C; A is 16 bits, B & C each 8 bits). */ + uint32_t uMinOsVer; + /** The SDK version (A.B.C; A is 16 bits, B & C each 8 bits). */ + uint32_t uSdkVer; + /** The source version (A.B.C.D.E; A is 24 bits, the rest 10 each). */ + uint64_t uSrcVer; + /** @} */ + + /** @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; + /** The number of segments. */ + uint32_t cSegments; + /** The number of sections. */ + uint32_t cSections; + /** Section pointer table (points into the load commands). */ + MY_SEGMENT_COMMAND const *apSegments[MACHO_MAX_SECT / 2]; + /** Load displacement table for each segment. */ + uintptr_t aoffLoadSegments[MACHO_MAX_SECT / 2]; + /** Section pointer table (points into the load commands). */ + MY_SECTION const *apSections[MACHO_MAX_SECT]; + /** Mapping table to quickly get to a segment from MY_NLIST::n_sect. */ + uint8_t auSections2Segment[MACHO_MAX_SECT]; + /** @} */ + + /** Buffer space. */ + char abBuf[_4K]; +} RTDBGKRNLINFOINT; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#ifdef DEBUG +static bool g_fBreakpointOnError = false; +#endif + + +/** + * Close and free up resources we no longer needs. + * + * @param pThis The internal scratch data. + */ +static void rtR0DbgKrnlDarwinLoadDone(RTDBGKRNLINFOINT *pThis) +{ + if (!pThis->fIsInMem) + RTFileClose(pThis->hFile); + pThis->hFile = NIL_RTFILE; + + if (!pThis->fIsInMem) + RTMemFree(pThis->pLoadCmds); + pThis->pLoadCmds = NULL; + RT_ZERO(pThis->apSections); + RT_ZERO(pThis->apSegments); +} + + +/** + * Looks up a kernel symbol record. + * + * @returns Pointer to the symbol record or NULL if not found. + * @param pThis The internal scratch data. + * @param pszSymbol The symbol to resolve. Automatically prefixed + * with an underscore. + */ +static MY_NLIST const *rtR0DbgKrnlDarwinLookupSym(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; + } +#else + /** @todo binary search. */ +#endif + + return NULL; +} + + +/** + * 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) +{ + MY_NLIST const *pSym = rtR0DbgKrnlDarwinLookupSym(pThis, pszSymbol); + if (pSym) + { + uint8_t idxSeg = pThis->auSections2Segment[pSym->n_sect]; + if (pThis->aoffLoadSegments[idxSeg] != UINTPTR_MAX) + return pSym->n_value + pThis->aoffLoadSegments[idxSeg]; + } + + 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); + + +/* + * Determine the load displacement (10.8 kernels are PIE). + * + * Starting with 11.0 (BigSur) all segments can have different load displacements + * so determine the displacements from known symbols. + * + * @returns IPRT status code + * @param pThis The internal scratch data. + */ +static int rtR0DbgKrnlDarwinInitLoadDisplacements(RTDBGKRNLINFOINT *pThis) +{ + static struct + { + const char *pszName; + uintptr_t uAddr; + } const s_aStandardSyms[] = + { +#ifdef IN_RING0 +# define KNOWN_ENTRY(a_Sym) { #a_Sym, (uintptr_t)&a_Sym } +#else +# define KNOWN_ENTRY(a_Sym) { #a_Sym, 0 } +#endif + KNOWN_ENTRY(vm_map_unwire), /* __TEXT */ + KNOWN_ENTRY(kernel_map), /* __HIB */ + KNOWN_ENTRY(gIOServicePlane), /* __DATA (__HIB on ElCapitan) */ + KNOWN_ENTRY(page_mask) /* __DATA on ElCapitan */ +#undef KNOWN_ENTRY + }; + + for (unsigned i = 0; i < RT_ELEMENTS(s_aStandardSyms); i++) + { + MY_NLIST const *pSym = rtR0DbgKrnlDarwinLookupSym(pThis, s_aStandardSyms[i].pszName); + if (RT_UNLIKELY(!pSym)) + return VERR_INTERNAL_ERROR_2; + + uint8_t idxSeg = pThis->auSections2Segment[pSym->n_sect]; +#ifdef IN_RING0 + /* + * The segment should either not have the load displacement determined or it should + * be the same for all symbols in the same segment. + */ + if ( pThis->aoffLoadSegments[idxSeg] != UINTPTR_MAX + && pThis->aoffLoadSegments[idxSeg] != s_aStandardSyms[i].uAddr - pSym->n_value) + return VERR_INTERNAL_ERROR_2; + + pThis->aoffLoadSegments[idxSeg] = s_aStandardSyms[i].uAddr - pSym->n_value; +#elif defined(IN_RING3) + pThis->aoffLoadSegments[idxSeg] = 0; +#else +# error "Either IN_RING0 or IN_RING3 msut be defined" +#endif + } + + return VINF_SUCCESS; +} + + +/** + * 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. + * @param pszKernelFile The name of the kernel file. + */ +static int rtR0DbgKrnlDarwinCheckStandardSymbols(RTDBGKRNLINFOINT *pThis, const char *pszKernelFile) +{ + 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), - we get kernel_sysctlbyname from the 10.10+ kernels. + 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_unwire), /* vm_map_wire has an alternative symbol, vm_map_wire_external, in 10.11 */ + KNOWN_ENTRY(PE_kputc), + KNOWN_ENTRY(kernel_map), + KNOWN_ENTRY(kernel_pmap), +#undef KNOWN_ENTRY + }; + + 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 + { +#if defined(IN_RING0) && defined(DEBUG_bird) + kprintf("RTR0DbgKrnlInfoOpen: error: %s (%p != %p) in %s\n", + s_aStandardCandles[i].pszName, (void *)uAddr, (void *)s_aStandardCandles[i].uAddr, pszKernelFile); +#endif + printf("RTR0DbgKrnlInfoOpen: error: %s (%p != %p) in %s\n", + s_aStandardCandles[i].pszName, (void *)uAddr, (void *)s_aStandardCandles[i].uAddr, pszKernelFile); + 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. + * @param pszKernelFile The name of the kernel file. + */ +static int rtR0DbgKrnlDarwinParseSymTab(RTDBGKRNLINFOINT *pThis, const char *pszKernelFile) +{ + /* + * 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) + { + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Symbol #%u has a bad string table index: %#x vs cbStrTab=%#x\n", + pszKernelFile, iSym, 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) + { + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Symbol #%u '%s' problem: n_sect = MACHO_NO_SECT\n", + pszKernelFile, iSym, pszSym); + RETURN_VERR_BAD_EXE_FORMAT; + } + if (pSym->n_sect > pThis->cSections) + { + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Symbol #%u '%s' problem: n_sect (%u) is higher than cSections (%u)\n", + pszKernelFile, iSym, pszSym, pSym->n_sect, pThis->cSections); + RETURN_VERR_BAD_EXE_FORMAT; + } + if (pSym->n_desc & ~(REFERENCED_DYNAMICALLY | N_WEAK_DEF)) + { + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Symbol #%u '%s' problem: Unexpected value n_desc=%#x\n", + pszKernelFile, iSym, pszSym, pSym->n_desc); + 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?). */ + { + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Symbol #%u '%s' problem: n_value (%#llx) < section addr (%#llx)\n", + pszKernelFile, iSym, pszSym, pSym->n_value, pThis->apSections[pSym->n_sect - 1]->addr); + 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. */ + { + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Symbol #%u '%s' problem: n_value (%#llx) >= end of section (%#llx + %#llx)\n", + pszKernelFile, iSym, pszSym, pSym->n_value, pThis->apSections[pSym->n_sect - 1]->addr, + pThis->apSections[pSym->n_sect - 1]->size); + 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) ) + { + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Abs symbol #%u '%s' problem: n_sect (%u) is not MACHO_NO_SECT (cSections is %u)\n", + pszKernelFile, iSym, pszSym, pSym->n_sect, pThis->cSections); + RETURN_VERR_BAD_EXE_FORMAT; + } + if (pSym->n_desc & ~(REFERENCED_DYNAMICALLY | N_WEAK_DEF)) + { + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Abs symbol #%u '%s' problem: Unexpected value n_desc=%#x\n", + pszKernelFile, iSym, pszSym, pSym->n_desc); + RETURN_VERR_BAD_EXE_FORMAT; + } + break; + + case MACHO_N_UNDF: + /* No undefined or common symbols in the kernel. */ + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Unexpected undefined symbol #%u '%s'\n", pszKernelFile, iSym, pszSym); + RETURN_VERR_BAD_EXE_FORMAT; + + case MACHO_N_INDR: + /* No indirect symbols in the kernel. */ + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Unexpected indirect symbol #%u '%s'\n", pszKernelFile, iSym, pszSym); + RETURN_VERR_BAD_EXE_FORMAT; + + case MACHO_N_PBUD: + /* No prebound symbols in the kernel. */ + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Unexpected prebound symbol #%u '%s'\n", pszKernelFile, iSym, pszSym); + RETURN_VERR_BAD_EXE_FORMAT; + + default: + LOG_BAD_SYM("RTR0DbgKrnlInfoOpen: %s: Unexpected symbol n_type %#x for symbol #%u '%s'\n", + pszKernelFile, pSym->n_type, iSym, pszSym); + RETURN_VERR_BAD_EXE_FORMAT; + } + } + /* else: Ignore debug symbols. */ + } + + return VINF_SUCCESS; +} + + +/** + * Uses the segment table to translate a file offset into a virtual memory + * address. + * + * @returns The virtual memory address on success, 0 if not found. + * @param pThis The instance. + * @param offFile The file offset to translate. + */ +static uintptr_t rtR0DbgKrnlDarwinFileOffToVirtAddr(RTDBGKRNLINFOINT *pThis, uint64_t offFile) +{ + uint32_t iSeg = pThis->cSegments; + while (iSeg-- > 0) + { + uint64_t offSeg = offFile - pThis->apSegments[iSeg]->fileoff; + if (offSeg < pThis->apSegments[iSeg]->vmsize) + return pThis->apSegments[iSeg]->vmaddr + (uintptr_t)offSeg; + } + return 0; +} + + +/** + * Parses and validates the load commands. + * + * @returns IPRT status code. + * @param pThis The internal scratch data. + */ +static int rtR0DbgKrnlDarwinParseCommands(RTDBGKRNLINFOINT *pThis) +{ + Assert(pThis->pLoadCmds); + + /* + * Reset the state. + */ + pThis->offStrTab = 0; + pThis->cbStrTab = 0; + pThis->offSyms = 0; + pThis->cSyms = 0; + pThis->cSections = 0; + pThis->uTextSegLinkAddr = 0; + pThis->cbTextSeg = 0; + pThis->uMinOsVer = 0; + pThis->uSdkVer = 0; + pThis->uSrcVer = 0; + + /* + * 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; + 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 >= RT_ELEMENTS(pThis->apSections)) + RETURN_VERR_BAD_EXE_FORMAT; + pThis->auSections2Segment[pThis->cSections] = pThis->cSegments; + 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; + + /* + * Add to the segment table. + */ + if (pThis->cSegments >= RT_ELEMENTS(pThis->apSegments)) + RETURN_VERR_BAD_EXE_FORMAT; + pThis->apSegments[pThis->cSegments++] = pSeg; + + /* + * Take down the text segment size and link address (for in-mem variant): + */ + if (!strcmp(pSeg->segname, "__TEXT")) + { + if (pThis->cbTextSeg != 0) + RETURN_VERR_BAD_EXE_FORMAT; + pThis->uTextSegLinkAddr = pSeg->vmaddr; + pThis->cbTextSeg = pSeg->vmsize; + } + 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_ENCRYPTION_INFO_64: + case LC_LINKER_OPTION: + case LC_LINKER_OPTIMIZATION_HINT: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: + case LC_NOTE: + case LC_SEGMENT_SPLIT_INFO: + break; + + case LC_BUILD_VERSION: + if (pCmd->cmdsize >= RT_UOFFSETOF(build_version_command_t, aTools)) + { + build_version_command_t *pBldVerCmd = (build_version_command_t *)pCmd; + pThis->uMinOsVer = pBldVerCmd->minos; + pThis->uSdkVer = pBldVerCmd->sdk; + } + break; + + case LC_SOURCE_VERSION: + if (pCmd->cmdsize == sizeof(source_version_command_t)) + { + source_version_command_t *pSrcVerCmd = (source_version_command_t *)pCmd; + pThis->uSrcVer = pSrcVerCmd->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_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); + } + + /* + * Try figure out the virtual addresses for the symbol and string tables. + */ + if (pThis->cbStrTab > 0) + pThis->uStrTabLinkAddr = rtR0DbgKrnlDarwinFileOffToVirtAddr(pThis, pThis->offStrTab); + if (pThis->cSyms > 0) + pThis->uSymTabLinkAddr = rtR0DbgKrnlDarwinFileOffToVirtAddr(pThis, pThis->offSyms); + + return VINF_SUCCESS; +} + + +/** + * Loads and validates the symbol and string tables. + * + * @returns IPRT status code. + * @param pThis The internal scratch data. + * @param pszKernelFile The name of the kernel file. + */ +static int rtR0DbgKrnlDarwinLoadSymTab(RTDBGKRNLINFOINT *pThis, const char *pszKernelFile) +{ + /* + * Load the tables. + */ + int rc; + pThis->paSyms = (MY_NLIST *)RTMemAllocZ(pThis->cSyms * sizeof(MY_NLIST)); + if (pThis->paSyms) + { + rc = RTFileReadAt(pThis->hFile, pThis->offArch + pThis->offSyms, pThis->paSyms, pThis->cSyms * sizeof(MY_NLIST), NULL); + if (RT_SUCCESS(rc)) + { + pThis->pachStrTab = (char *)RTMemAllocZ(pThis->cbStrTab + 1); + if (pThis->pachStrTab) + { + rc = RTFileReadAt(pThis->hFile, pThis->offArch + pThis->offStrTab, pThis->pachStrTab, pThis->cbStrTab, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Join paths with the in-memory code path. + */ + rc = rtR0DbgKrnlDarwinParseSymTab(pThis, pszKernelFile); + } + } + else + rc = VERR_NO_MEMORY; + } + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * Loads the load commands and validates them. + * + * @returns IPRT status code. + * @param pThis The internal scratch data. + */ +static int rtR0DbgKrnlDarwinLoadCommands(RTDBGKRNLINFOINT *pThis) +{ + int rc; + pThis->pLoadCmds = (load_command_t *)RTMemAlloc(pThis->cbLoadCmds); + if (pThis->pLoadCmds) + { + rc = RTFileReadAt(pThis->hFile, pThis->offArch + sizeof(MY_MACHO_HEADER), pThis->pLoadCmds, pThis->cbLoadCmds, NULL); + if (RT_SUCCESS(rc)) + rc = rtR0DbgKrnlDarwinParseCommands(pThis); + } + else + rc = VERR_NO_MEMORY; + return rc; +} + + +/** + * 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; + + if (!pThis->fIsInMem) + RTMemFree(pThis->pachStrTab); + pThis->pachStrTab = NULL; + + if (!pThis->fIsInMem) + RTMemFree(pThis->paSyms); + pThis->paSyms = NULL; + + RTMemFree(pThis); +} + + +/** + * Completes a handle, logging details. + * + * @returns VINF_SUCCESS + * @param phKrnlInfo Where to return the handle. + * @param pThis The instance to complete. + * @param pszKernelFile What kernel file it's based on. + */ +static int rtR0DbgKrnlDarwinSuccess(PRTDBGKRNLINFO phKrnlInfo, RTDBGKRNLINFOINT *pThis, const char *pszKernelFile) +{ + pThis->u32Magic = RTDBGKRNLINFO_MAGIC; + pThis->cRefs = 1; + +#if defined(DEBUG) || defined(IN_RING3) + LOG_SUCCESS("RTR0DbgKrnlInfoOpen: Found: %#zx + %#zx - %s\n", pThis->uTextSegLinkAddr, pThis->offLoad, pszKernelFile); +#else + LOG_SUCCESS("RTR0DbgKrnlInfoOpen: Found: %s\n", pszKernelFile); +#endif + LOG_SUCCESS("RTR0DbgKrnlInfoOpen: SDK version: %u.%u.%u MinOS version: %u.%u.%u Source version: %u.%u.%u.%u.%u\n", + pThis->uSdkVer >> 16, (pThis->uSdkVer >> 8) & 0xff, pThis->uSdkVer & 0xff, + pThis->uMinOsVer >> 16, (pThis->uMinOsVer >> 8) & 0xff, pThis->uMinOsVer & 0xff, + (uint32_t)(pThis->uSrcVer >> 40), + (uint32_t)(pThis->uSrcVer >> 30) & 0x3ff, + (uint32_t)(pThis->uSrcVer >> 20) & 0x3ff, + (uint32_t)(pThis->uSrcVer >> 10) & 0x3ff, + (uint32_t)(pThis->uSrcVer) & 0x3ff); + + *phKrnlInfo = pThis; + return VINF_SUCCESS; +} + + +static int rtR0DbgKrnlDarwinOpen(PRTDBGKRNLINFO phKrnlInfo, const char *pszKernelFile) +{ + RTDBGKRNLINFOINT *pThis = (RTDBGKRNLINFOINT *)RTMemAllocZ(sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + pThis->hFile = NIL_RTFILE; + + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aoffLoadSegments); i++) + pThis->aoffLoadSegments[i] = UINTPTR_MAX; + + int rc = RTFileOpen(&pThis->hFile, pszKernelFile, 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, pszKernelFile); + if (RT_SUCCESS(rc)) + { + rc = rtR0DbgKrnlDarwinInitLoadDisplacements(pThis); + if (RT_SUCCESS(rc)) + rc = rtR0DbgKrnlDarwinCheckStandardSymbols(pThis, pszKernelFile); + } + + rtR0DbgKrnlDarwinLoadDone(pThis); + if (RT_SUCCESS(rc)) + rtR0DbgKrnlDarwinSuccess(phKrnlInfo, pThis, pszKernelFile); + else + rtR0DbgKrnlDarwinDtor(pThis); + return rc; +} + + +#ifdef IN_RING0 + +/** + * Checks if a page is present. + * @returns true if it is, false if it isn't. + * @param uPageAddr The address of/in the page to check. + */ +static bool rtR0DbgKrnlDarwinIsPagePresent(uintptr_t uPageAddr) +{ + /** @todo the dtrace code subjects the result to pmap_is_valid, but that + * isn't exported, so we'll have to make to with != 0 here. */ + return pmap_find_phys(kernel_pmap, uPageAddr) != 0; +} + + +/** + * Used to check whether a memory range is present or not. + * + * This is applied to the to the load commands and selected portions of the link + * edit segment. + * + * @returns true if all present, false if not. + * @param uAddress The start address. + * @param cb Number of bytes to check. + * @param pszWhat What we're checking, for logging. + * @param pHdr The header address (for logging). + */ +static bool rtR0DbgKrnlDarwinIsRangePresent(uintptr_t uAddress, size_t cb, + const char *pszWhat, MY_MACHO_HEADER const volatile *pHdr) +{ + uintptr_t const uStartAddress = uAddress; + intptr_t cPages = RT_ALIGN_Z(cb + (uAddress & PAGE_OFFSET_MASK), PAGE_SIZE); + RT_NOREF(uStartAddress, pszWhat, pHdr); + for (;;) + { + if (!rtR0DbgKrnlDarwinIsPagePresent(uAddress)) + { + LOG_NOT_PRESENT("RTR0DbgInfo: %p: Page in %s is not present: %#zx - rva %#zx; in structure %#zx (%#zx LB %#zx)\n", + pHdr, pszWhat, uAddress, uAddress - (uintptr_t)pHdr, uAddress - uStartAddress, uStartAddress, cb); + return false; + } + + cPages -= 1; + if (cPages <= 0) + uAddress += PAGE_SIZE; + else + return true; + } +} + + +/** + * Try "open" the in-memory kernel image + * + * @returns IPRT stauts code + * @param phKrnlInfo Where to return the info instance on success. + */ +static int rtR0DbgKrnlDarwinOpenInMemory(PRTDBGKRNLINFO phKrnlInfo) +{ + RTDBGKRNLINFOINT *pThis = (RTDBGKRNLINFOINT *)RTMemAllocZ(sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + pThis->hFile = NIL_RTFILE; + pThis->fIsInMem = true; + + for (uint32_t i = 0; i < RT_ELEMENTS(pThis->aoffLoadSegments); i++) + pThis->aoffLoadSegments[i] = UINTPTR_MAX; + + /* + * Figure the search range based on a symbol that is supposed to be in + * kernel text segment, using it as the upper boundrary. The lower boundary + * is determined by subtracting a max kernel size of 64MB (the largest kernel + * file, kernel.kasan, is around 45MB, but the end of __TEXT is about 27 MB, + * which means we should still have plenty of room for future growth with 64MB). + */ + uintptr_t const uSomeKernelAddr = (uintptr_t)&absolutetime_to_nanoseconds; + uintptr_t const uLowestKernelAddr = uSomeKernelAddr - _64M; + + /* + * The kernel is probably aligned at some boundrary larger than a page size, + * so to speed things up we start by assuming the alignment is page directory + * sized. In case we're wrong and it's smaller, we decrease the alignment till + * we've reach the page size. + */ + uintptr_t fPrevAlignMask = ~(uintptr_t)0; + uintptr_t uCurAlign = _2M; /* ASSUMES the kernel is typically 2MB aligned. */ + while (uCurAlign >= PAGE_SIZE) + { + /* + * Search down from the symbol address looking for a mach-O header that + * looks like it might belong to the kernel. + */ + for (uintptr_t uCur = uSomeKernelAddr & ~(uCurAlign - 1); uCur >= uLowestKernelAddr; uCur -= uCurAlign) + { + /* Skip pages we've checked in previous iterations and pages that aren't present: */ + /** @todo This is a little bogus in case the header is paged out. */ + if ( (uCur & fPrevAlignMask) + && rtR0DbgKrnlDarwinIsPagePresent(uCur)) + { + /* + * Look for valid mach-o header (we skip cpusubtype on purpose here). + */ + MY_MACHO_HEADER const volatile *pHdr = (MY_MACHO_HEADER const volatile *)uCur; + if ( pHdr->magic == MY_MACHO_MAGIC + && pHdr->filetype == MH_EXECUTE + && pHdr->cputype == MY_CPU_TYPE) + { + /* More header validation: */ + pThis->cLoadCmds = pHdr->ncmds; + pThis->cbLoadCmds = pHdr->sizeofcmds; + if (pHdr->ncmds < 4) + LOG_MISMATCH("RTR0DbgInfo: %p: ncmds=%u is too small\n", pHdr, pThis->cLoadCmds); + else if (pThis->cLoadCmds > 256) + LOG_MISMATCH("RTR0DbgInfo: %p: ncmds=%u is too big\n", pHdr, pThis->cLoadCmds); + else if (pThis->cbLoadCmds <= pThis->cLoadCmds * sizeof(load_command_t)) + LOG_MISMATCH("RTR0DbgInfo: %p: sizeofcmds=%u is too small for ncmds=%u\n", + pHdr, pThis->cbLoadCmds, pThis->cLoadCmds); + else if (pThis->cbLoadCmds >= _1M) + LOG_MISMATCH("RTR0DbgInfo: %p: sizeofcmds=%u is too big\n", pHdr, pThis->cbLoadCmds); + else if (pHdr->flags & ~MH_VALID_FLAGS) + LOG_MISMATCH("RTR0DbgInfo: %p: invalid flags=%#x\n", pHdr, pHdr->flags); + /* + * Check that we can safely read the load commands, then parse & validate them. + */ + else if (rtR0DbgKrnlDarwinIsRangePresent((uintptr_t)(pHdr + 1), pThis->cbLoadCmds, "load commands", pHdr)) + { + pThis->pLoadCmds = (load_command_t *)(pHdr + 1); + int rc = rtR0DbgKrnlDarwinParseCommands(pThis); + if (RT_SUCCESS(rc)) + { + /* Calculate the slide value. This is typically zero as the + load commands has been relocated (the case with 10.14.0 at least). */ + /** @todo ASSUMES that the __TEXT segment comes first and includes the + * mach-o header and load commands and all that. */ + pThis->offLoad = uCur - pThis->uTextSegLinkAddr; + + /* Check that the kernel symbol is in the text segment: */ + uintptr_t const offSomeKernAddr = uSomeKernelAddr - uCur; + if (offSomeKernAddr >= pThis->cbTextSeg) + LOG_MISMATCH("RTR0DbgInfo: %p: Our symbol at %zx (off %zx) isn't within the text segment (size %#zx)\n", + pHdr, uSomeKernelAddr, offSomeKernAddr, pThis->cbTextSeg); + /* + * Parse the symbol+string tables. + */ + else if (pThis->uSymTabLinkAddr == 0) + LOG_MISMATCH("RTR0DbgInfo: %p: No symbol table VA (off %#x L %#x)\n", + pHdr, pThis->offSyms, pThis->cSyms); + else if (pThis->uStrTabLinkAddr == 0) + LOG_MISMATCH("RTR0DbgInfo: %p: No string table VA (off %#x LB %#x)\n", + pHdr, pThis->offSyms, pThis->cbStrTab); + else if ( rtR0DbgKrnlDarwinIsRangePresent(pThis->uStrTabLinkAddr + pThis->offLoad, + pThis->cbStrTab, "string table", pHdr) + && rtR0DbgKrnlDarwinIsRangePresent(pThis->uSymTabLinkAddr + pThis->offLoad, + pThis->cSyms * sizeof(pThis->paSyms), + "symbol table", pHdr)) + { + pThis->pachStrTab = (char *)pThis->uStrTabLinkAddr + pThis->offLoad; + pThis->paSyms = (MY_NLIST *)pThis->uSymTabLinkAddr + pThis->offLoad; + rc = rtR0DbgKrnlDarwinParseSymTab(pThis, "in-memory"); + if (RT_SUCCESS(rc)) + { + rc = rtR0DbgKrnlDarwinInitLoadDisplacements(pThis); + if (RT_SUCCESS(rc)) + { + /* + * Finally check the standard candles. + */ + rc = rtR0DbgKrnlDarwinCheckStandardSymbols(pThis, "in-memory"); + rtR0DbgKrnlDarwinLoadDone(pThis); + if (RT_SUCCESS(rc)) + return rtR0DbgKrnlDarwinSuccess(phKrnlInfo, pThis, "in-memory"); + } + } + } + } + + RT_ZERO(pThis->apSections); + RT_ZERO(pThis->apSegments); + pThis->pLoadCmds = NULL; + } + } + } + } + + fPrevAlignMask = uCurAlign - 1; + uCurAlign >>= 1; + } + + RTMemFree(pThis); + return VERR_GENERAL_FAILURE; +} + +#endif /* IN_RING0 */ + +RTR0DECL(int) RTR0DbgKrnlInfoOpen(PRTDBGKRNLINFO phKrnlInfo, uint32_t fFlags) +{ + AssertPtrReturn(phKrnlInfo, VERR_INVALID_POINTER); + *phKrnlInfo = NIL_RTDBGKRNLINFO; + AssertReturn(!fFlags, VERR_INVALID_PARAMETER); + +#ifdef IN_RING0 + /* + * Try see if we can use the kernel memory directly. This depends on not + * having the __LINKEDIT segment jettisoned or swapped out. For older + * kernels this is typically the case, unless kallsyms=1 is in boot-args. + */ + int rc = rtR0DbgKrnlDarwinOpenInMemory(phKrnlInfo); + if (RT_SUCCESS(rc)) + { + Log(("RTR0DbgKrnlInfoOpen: Using in-memory kernel.\n")); + return rc; + } +#else + int rc = VERR_WRONG_ORDER; /* shut up stupid MSC */ +#endif + + /* + * Go thru likely kernel locations + * + * Note! Check the OS X version and reorder the list? + * Note! We should try fish kcsuffix out of bootargs or somewhere one day. + */ + static bool s_fFirstCall = true; +#ifdef IN_RING3 + extern const char *g_pszTestKernel; +#endif + struct + { + const char *pszLocation; + int rc; + } aKernels[] = + { +#ifdef IN_RING3 + { g_pszTestKernel, VERR_WRONG_ORDER }, +#endif + { "/System/Library/Kernels/kernel", VERR_WRONG_ORDER }, + { "/System/Library/Kernels/kernel.development", VERR_WRONG_ORDER }, + { "/System/Library/Kernels/kernel.debug", VERR_WRONG_ORDER }, + { "/mach_kernel", VERR_WRONG_ORDER }, + }; + for (uint32_t i = 0; i < RT_ELEMENTS(aKernels); i++) + { + aKernels[i].rc = rc = rtR0DbgKrnlDarwinOpen(phKrnlInfo, aKernels[i].pszLocation); + if (RT_SUCCESS(rc)) + { + if (s_fFirstCall) + { + printf("RTR0DbgKrnlInfoOpen: Using kernel file '%s'\n", aKernels[i].pszLocation); + s_fFirstCall = false; + } + return rc; + } + } + + /* + * Failed. + */ + /* Pick the best error code. */ + for (uint32_t i = 0; rc == VERR_FILE_NOT_FOUND && i < RT_ELEMENTS(aKernels); i++) + if (aKernels[i].rc != VERR_FILE_NOT_FOUND) + rc = aKernels[i].rc; + + /* Bitch about it. */ + printf("RTR0DbgKrnlInfoOpen: failed to find matching kernel file! rc=%d\n", rc); + if (s_fFirstCall) + { + for (uint32_t i = 0; i < RT_ELEMENTS(aKernels); i++) + printf("RTR0DbgKrnlInfoOpen: '%s' -> %d\n", aKernels[i].pszLocation, aKernels[i].rc); + s_fFirstCall = false; + } + + 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 *pszModule, 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_POINTER); + AssertPtrReturn(pszModule, VERR_INVALID_POINTER); + AssertPtrReturn(pszStructure, VERR_INVALID_POINTER); + AssertPtrReturn(poffMember, VERR_INVALID_POINTER); + 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; +} + |