/* $Id: DBGPlugInFreeBsd.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */ /** @file * DBGPlugInFreeBsd - Debugger and Guest OS Digger Plugin For FreeBSD. */ /* * Copyright (C) 2016-2024 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, in version 3 of the * License. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DBGF /// @todo add new log group. #include "DBGPlugIns.h" #include "DBGPlugInCommonELF.h" #include #include #include #include #include #include #include /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** FreeBSD on little endian ASCII systems. */ #define DIG_FBSD_MOD_TAG UINT64_C(0x0044534265657246) /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * FreeBSD .dynstr and .dynsym location probing state. */ typedef enum FBSDPROBESTATE { /** Invalid state. */ FBSDPROBESTATE_INVALID = 0, /** Searching for the end of the .dynstr section (terminator). */ FBSDPROBESTATE_DYNSTR_END, /** Last symbol was a symbol terminator character. */ FBSDPROBESTATE_DYNSTR_SYM_TERMINATOR, /** Last symbol was a symbol character. */ FBSDPROBESTATE_DYNSTR_SYM_CHAR } FBSDPROBESTATE; /** * ELF headers union. */ typedef union ELFEHDRS { /** 32bit version of the ELF header. */ Elf32_Ehdr Hdr32; /** 64bit version of the ELF header. */ Elf64_Ehdr Hdr64; } ELFEHDRS; /** Pointer to a ELF header union. */ typedef ELFEHDRS *PELFEHDRS; /** Pointer to const ELF header union. */ typedef ELFEHDRS const *PCELFEHDRS; /** * ELF symbol entry union. */ typedef union ELFSYMS { /** 32bit version of the ELF section header. */ Elf32_Sym Hdr32; /** 64bit version of the ELF section header. */ Elf64_Sym Hdr64; } ELFSYMS; /** Pointer to a ELF symbol entry union. */ typedef ELFSYMS *PELFSYMS; /** Pointer to const ELF symbol entry union. */ typedef ELFSYMS const *PCELFSYMS; /** * Message buffer structure. */ typedef union FBSDMSGBUF { /** 32bit version. */ struct { /** Message buffer pointer. */ uint32_t msg_ptr; /** Magic value to identify the structure. */ uint32_t msg_magic; /** Size of the buffer area. */ uint32_t msg_size; /** Write sequence number. */ uint32_t msg_wseq; /** Read sequence number. */ uint32_t msg_rseq; /** @todo More fields which are not required atm. */ } Hdr32; /** 64bit version. */ struct { /** Message buffer pointer. */ uint64_t msg_ptr; /** Magic value to identify the structure. */ uint32_t msg_magic; /** Size of the buffer area. */ uint32_t msg_size; /** Write sequence number. */ uint32_t msg_wseq; /** Read sequence number. */ uint32_t msg_rseq; /** @todo More fields which are not required atm. */ } Hdr64; } FBSDMSGBUF; /** Pointer to a message buffer structure. */ typedef FBSDMSGBUF *PFBSDMSGBUF; /** Pointer to a const message buffer structure. */ typedef FBSDMSGBUF const *PCFBSDMSGBUF; /** Magic value to identify the message buffer structure. */ #define FBSD_MSGBUF_MAGIC UINT32_C(0x063062) /** * FreeBSD guest OS digger instance data. */ typedef struct DBGDIGGERFBSD { /** Whether the information is valid or not. * (For fending off illegal interface method calls.) */ bool fValid; /** 64-bit/32-bit indicator. */ bool f64Bit; /** Address of the start of the kernel ELF image, * set during probing. */ DBGFADDRESS AddrKernelElfStart; /** Address of the interpreter content aka "/red/herring". */ DBGFADDRESS AddrKernelInterp; /** Address of the start of the text section. */ DBGFADDRESS AddrKernelText; /** The kernel message log interface. */ DBGFOSIDMESG IDmesg; } DBGDIGGERFBSD; /** Pointer to the FreeBSD guest OS digger instance data. */ typedef DBGDIGGERFBSD *PDBGDIGGERFBSD; /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** Min kernel address (32bit). */ #define FBSD32_MIN_KRNL_ADDR UINT32_C(0x80000000) /** Max kernel address (32bit). */ #define FBSD32_MAX_KRNL_ADDR UINT32_C(0xfffff000) /** Min kernel address (64bit). */ #define FBSD64_MIN_KRNL_ADDR UINT64_C(0xFFFFF80000000000) /** Max kernel address (64bit). */ #define FBSD64_MAX_KRNL_ADDR UINT64_C(0xFFFFFFFFFFF00000) /** Validates a 32-bit FreeBSD kernel address */ #define FBSD32_VALID_ADDRESS(Addr) ( (Addr) > FBSD32_MIN_KRNL_ADDR \ && (Addr) < FBSD32_MAX_KRNL_ADDR) /** Validates a 64-bit FreeBSD kernel address */ #define FBSD64_VALID_ADDRESS(Addr) ( (Addr) > FBSD64_MIN_KRNL_ADDR \ && (Addr) < FBSD64_MAX_KRNL_ADDR) /** Validates a FreeBSD kernel address. */ #define FBSD_VALID_ADDRESS(a_pThis, a_Addr) ((a_pThis)->f64Bit ? FBSD64_VALID_ADDRESS(a_Addr) : FBSD32_VALID_ADDRESS(a_Addr)) /** Maximum offset from the start of the ELF image we look for the /red/herring .interp section content. */ #define FBSD_MAX_INTERP_OFFSET _16K /** The max kernel size. */ #define FBSD_MAX_KERNEL_SIZE UINT32_C(0x0f000000) /** Versioned and bitness wrapper. */ #define FBSD_UNION(a_pThis, a_pUnion, a_Member) ((a_pThis)->f64Bit ? (a_pUnion)->Hdr64. a_Member : (a_pUnion)->Hdr32. a_Member ) /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static DECLCALLBACK(int) dbgDiggerFreeBsdInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData); /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** Table of common FreeBSD kernel addresses. */ static uint64_t g_au64FreeBsdKernelAddresses[] = { UINT64_C(0xc0100000), UINT64_C(0xffffffff80100000) }; /** Magic string which resides in the .interp section of the image. */ static const uint8_t g_abNeedleInterp[] = "/red/herring"; /** * Load the symbols from the .dynsym and .dynstr sections given * by their address in guest memory. * * @returns VBox status code. * @param pThis The instance data. * @param pUVM The user mode VM handle. * @param pVMM The VMM function table. * @param pszName The image name. * @param uKernelStart The kernel start address. * @param cbKernel Size of the kernel image. * @param pAddrDynsym Start address of the .dynsym section. * @param cSymbols Number of symbols in the .dynsym section. * @param pAddrDynstr Start address of the .dynstr section containing the symbol names. * @param cbDynstr Size of the .dynstr section. */ static int dbgDiggerFreeBsdLoadSymbols(PDBGDIGGERFBSD pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszName, RTGCUINTPTR uKernelStart, size_t cbKernel, PDBGFADDRESS pAddrDynsym, uint32_t cSymbols, PDBGFADDRESS pAddrDynstr, size_t cbDynstr) { LogFlowFunc(("pThis=%#p pszName=%s uKernelStart=%RGv cbKernel=%zu pAddrDynsym=%#p{%RGv} cSymbols=%u pAddrDynstr=%#p{%RGv} cbDynstr=%zu\n", pThis, pszName, uKernelStart, cbKernel, pAddrDynsym, pAddrDynsym->FlatPtr, cSymbols, pAddrDynstr, pAddrDynstr->FlatPtr, cbDynstr)); char *pbDynstr = (char *)RTMemAllocZ(cbDynstr + 1); /* Extra terminator. */ int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pAddrDynstr, pbDynstr, cbDynstr); if (RT_SUCCESS(rc)) { uint32_t cbDynsymEnt = pThis->f64Bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym); uint8_t *pbDynsym = (uint8_t *)RTMemAllocZ(cSymbols * cbDynsymEnt); rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pAddrDynsym, pbDynsym, cSymbols * cbDynsymEnt); if (RT_SUCCESS(rc)) { /* * Create a module for the kernel. */ RTDBGMOD hMod; rc = RTDbgModCreate(&hMod, pszName, cbKernel, 0 /*fFlags*/); if (RT_SUCCESS(rc)) { rc = RTDbgModSetTag(hMod, DIG_FBSD_MOD_TAG); AssertRC(rc); rc = VINF_SUCCESS; /* * Enumerate the symbols. */ uint32_t cLeft = cSymbols; while (cLeft-- > 0 && RT_SUCCESS(rc)) { PCELFSYMS pSym = (PCELFSYMS)&pbDynsym[cLeft * cbDynsymEnt]; uint32_t idxSymStr = FBSD_UNION(pThis, pSym, st_name); uint8_t uType = FBSD_UNION(pThis, pSym, st_info); RTGCUINTPTR AddrVal = FBSD_UNION(pThis, pSym, st_value); size_t cbSymVal = FBSD_UNION(pThis, pSym, st_size); /* Add it without the type char. */ RT_NOREF(uType); if ( AddrVal <= uKernelStart + cbKernel && idxSymStr < cbDynstr) { rc = RTDbgModSymbolAdd(hMod, &pbDynstr[idxSymStr], RTDBGSEGIDX_RVA, AddrVal - uKernelStart, cbSymVal, 0 /*fFlags*/, NULL); if (RT_FAILURE(rc)) { if ( rc == VERR_DBG_SYMBOL_NAME_OUT_OF_RANGE || rc == VERR_DBG_INVALID_RVA || rc == VERR_DBG_ADDRESS_CONFLICT || rc == VERR_DBG_DUPLICATE_SYMBOL) { Log2(("dbgDiggerFreeBsdLoadSymbols: RTDbgModSymbolAdd(,%s,) failed %Rrc (ignored)\n", &pbDynstr[idxSymStr], rc)); rc = VINF_SUCCESS; } else Log(("dbgDiggerFreeBsdLoadSymbols: RTDbgModSymbolAdd(,%s,) failed %Rrc\n", &pbDynstr[idxSymStr], rc)); } } } /* * Link the module into the address space. */ if (RT_SUCCESS(rc)) { RTDBGAS hAs = pVMM->pfnDBGFR3AsResolveAndRetain(pUVM, DBGF_AS_KERNEL); if (hAs != NIL_RTDBGAS) rc = RTDbgAsModuleLink(hAs, hMod, uKernelStart, RTDBGASLINK_FLAGS_REPLACE); else rc = VERR_INTERNAL_ERROR; RTDbgAsRelease(hAs); } else Log(("dbgDiggerFreeBsdLoadSymbols: Failed: %Rrc\n", rc)); RTDbgModRelease(hMod); } else Log(("dbgDiggerFreeBsdLoadSymbols: RTDbgModCreate failed: %Rrc\n", rc)); } else Log(("dbgDiggerFreeBsdLoadSymbols: Reading symbol table at %RGv failed: %Rrc\n", pAddrDynsym->FlatPtr, rc)); RTMemFree(pbDynsym); } else Log(("dbgDiggerFreeBsdLoadSymbols: Reading symbol string table at %RGv failed: %Rrc\n", pAddrDynstr->FlatPtr, rc)); RTMemFree(pbDynstr); LogFlowFunc(("returns %Rrc\n", rc)); return rc; } /** * Process the kernel image. * * @param pThis The instance data. * @param pUVM The user mode VM handle. * @param pVMM The VMM function table. * @param pszName The image name. */ static void dbgDiggerFreeBsdProcessKernelImage(PDBGDIGGERFBSD pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, const char *pszName) { /* * FreeBSD has parts of the kernel ELF image in guest memory, starting with the * ELF header and the content of the sections which are indicated to be loaded * into memory (text, rodata, etc.) of course. Whats missing are the section headers * which is understandable but unfortunate because it would make our life easier. * * All checked FreeBSD kernels so far have the following layout in the kernel: * [.interp] - contains the /red/herring string we used for probing earlier * [.hash] - contains the hashes of the symbol names, 8 byte alignment on 64bit, 4 byte on 32bit * [.gnu.hash] - GNU hash section. (introduced somewhere between 10.0 and 12.0 @todo Find out when exactly) * [.dynsym] - contains the ELF symbol descriptors, 8 byte alignment, 4 byte on 32bit * [.dynstr] - contains the symbol names as a string table, 1 byte alignmnt * [.text] - contains the executable code, 16 byte alignment. * * To find the start of the .dynsym and .dynstr sections we scan backwards from the start of the .text section * and check for all characters allowed for symbol names and count the amount of symbols found. When the start of the * .dynstr section is reached the number of entries in .dynsym is known and we can deduce the start address. * * This applied to the old code before the FreeBSD kernel introduced the .gnu.hash section * (keeping it here for informational pruposes): * The sections are always adjacent (sans alignment) so we just parse the .hash section right after * .interp, ELF states that it can contain 32bit or 64bit words but all observed kernels * always use 32bit words. It contains two counters at the beginning which we can use to * deduct the .hash section size and the beginning of .dynsym. * .dynsym contains an array of symbol descriptors which have a fixed size depending on the * guest bitness. * Finding the end of .dynsym is not easily doable as there is no counter available (it lives * in the section headers) at this point so we just have to check whether the record is valid * and if not check if it contains an ASCII string which marks the start of the .dynstr section. */ #if 0 DBGFADDRESS AddrInterpEnd = pThis->AddrKernelInterp; DBGFR3AddrAdd(&AddrInterpEnd, sizeof(g_abNeedleInterp)); DBGFADDRESS AddrCur = pThis->AddrKernelText; int rc = VINF_SUCCESS; uint32_t cSymbols = 0; size_t cbKernel = 512 * _1M; RTGCUINTPTR uKernelStart = pThis->AddrKernelElfStart.FlatPtr; FBSDPROBESTATE enmState = FBSDPROBESTATE_DYNSTR_END; /* Start searching for the end of the .dynstr section. */ while (AddrCur.FlatPtr > AddrInterpEnd.FlatPtr) { char achBuf[_16K]; size_t cbToRead = RT_MIN(sizeof(achBuf), AddrCur.FlatPtr - AddrInterpEnd.FlatPtr); rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrSub(&AddrCur, cbToRead), &achBuf[0], cbToRead); if (RT_FAILURE(rc)) break; for (unsigned i = cbToRead; i > 0; i--) { char ch = achBuf[i - 1]; switch (enmState) { case FBSDPROBESTATE_DYNSTR_END: { if (ch != '\0') enmState = FBSDPROBESTATE_DYNSTR_SYM_CHAR; break; } case FBSDPROBESTATE_DYNSTR_SYM_TERMINATOR: { if ( RT_C_IS_ALNUM(ch) || ch == '_' || ch == '.') enmState = FBSDPROBESTATE_DYNSTR_SYM_CHAR; else { /* Two consecutive terminator symbols mean end of .dynstr section. */ pVMM->pfnDBGFR3AddrAdd(&AddrCur, i); DBGFADDRESS AddrDynstrStart = AddrCur; DBGFADDRESS AddrDynsymStart = AddrCur; pVMM->pfnDBGFR3AddrSub(&AddrDynsymStart, cSymbols * (pThis->f64Bit ? sizeof(Elf64_Sym) : sizeof(Elf64_Sym))); LogFlowFunc(("Found all required section start addresses (.dynsym=%RGv cSymbols=%u, .dynstr=%RGv cb=%u)\n", AddrDynsymStart.FlatPtr, cSymbols, AddrDynstrStart.FlatPtr, pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr)); dbgDiggerFreeBsdLoadSymbols(pThis, pUVM, pVMM, pszName, uKernelStart, cbKernel, &AddrDynsymStart, cSymbols, &AddrDynstrStart, pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr); return; } break; } case FBSDPROBESTATE_DYNSTR_SYM_CHAR: { if ( !RT_C_IS_ALNUM(ch) && ch != '_' && ch != '.') { /* Non symbol character. */ if (ch == '\0') { enmState = FBSDPROBESTATE_DYNSTR_SYM_TERMINATOR; cSymbols++; } else { /* Indicates the end of the .dynstr section. */ pVMM->pfnDBGFR3AddrAdd(&AddrCur, i); DBGFADDRESS AddrDynstrStart = AddrCur; DBGFADDRESS AddrDynsymStart = AddrCur; pVMM->pfnDBGFR3AddrSub(&AddrDynsymStart, cSymbols * (pThis->f64Bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym))); LogFlowFunc(("Found all required section start addresses (.dynsym=%RGv cSymbols=%u, .dynstr=%RGv cb=%u)\n", AddrDynsymStart.FlatPtr, cSymbols, AddrDynstrStart.FlatPtr, pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr)); dbgDiggerFreeBsdLoadSymbols(pThis, pUVM, pVMM, pszName, uKernelStart, cbKernel, &AddrDynsymStart, cSymbols, &AddrDynstrStart, pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr); return; } } break; } default: AssertFailedBreak(); } } } LogFlow(("Failed to find valid .dynsym and .dynstr sections (%Rrc), can't load kernel symbols\n", rc)); #else /* Calculate the start of the .hash section. */ DBGFADDRESS AddrHashStart = pThis->AddrKernelInterp; pVMM->pfnDBGFR3AddrAdd(&AddrHashStart, sizeof(g_abNeedleInterp)); AddrHashStart.FlatPtr = RT_ALIGN_GCPT(AddrHashStart.FlatPtr, pThis->f64Bit ? 8 : 4, RTGCUINTPTR); uint32_t au32Counters[2]; int rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrHashStart, &au32Counters[0], sizeof(au32Counters)); if (RT_SUCCESS(rc)) { size_t cbHash = (au32Counters[0] + au32Counters[1] + 2) * sizeof(uint32_t); if (AddrHashStart.FlatPtr + cbHash < pThis->AddrKernelText.FlatPtr) /* Should be much smaller */ { DBGFADDRESS AddrDynsymStart = AddrHashStart; uint32_t cSymbols = 0; size_t cbKernel = 0; RTGCUINTPTR uKernelStart = pThis->AddrKernelElfStart.FlatPtr; pVMM->pfnDBGFR3AddrAdd(&AddrDynsymStart, cbHash); AddrDynsymStart.FlatPtr = RT_ALIGN_GCPT(AddrDynsymStart.FlatPtr, pThis->f64Bit ? 8 : 4, RTGCUINTPTR); DBGFADDRESS AddrDynstrStart = AddrDynsymStart; while (AddrDynstrStart.FlatPtr < pThis->AddrKernelText.FlatPtr) { size_t cbDynSymEnt = pThis->f64Bit ? sizeof(Elf64_Sym) : sizeof(Elf32_Sym); uint8_t abBuf[_16K]; size_t cbToRead = RT_MIN(sizeof(abBuf), pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr); rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrDynstrStart, &abBuf[0], cbToRead); if (RT_FAILURE(rc)) break; for (unsigned i = 0; i < cbToRead / cbDynSymEnt; i++) { PCELFSYMS pSym = (PCELFSYMS)&abBuf[i * cbDynSymEnt]; uint32_t idxSymStr = FBSD_UNION(pThis, pSym, st_name); uint8_t uType = FBSD_UNION(pThis, pSym, st_info); RTGCUINTPTR AddrVal = FBSD_UNION(pThis, pSym, st_value); size_t cbSymVal = FBSD_UNION(pThis, pSym, st_size); /* * If the entry doesn't look valid check whether it contains an ASCII string, * we then found the start of the .dynstr section. */ RT_NOREF(uType); if ( ELF32_ST_TYPE(uType) != STT_NOTYPE && ( !FBSD_VALID_ADDRESS(pThis, AddrVal) || cbSymVal > FBSD_MAX_KERNEL_SIZE || idxSymStr > pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr)) { LogFlowFunc(("Invalid symbol table entry found at %RGv\n", AddrDynstrStart.FlatPtr + i * cbDynSymEnt)); uint8_t *pbBuf = &abBuf[i * cbDynSymEnt]; size_t cbLeft = cbToRead - i * cbDynSymEnt; /* * Check to the end of the buffer whether it contains only a certain set of * ASCII characters and 0 terminators. */ while ( cbLeft > 0 && ( RT_C_IS_ALNUM(*pbBuf) || *pbBuf == '_' || *pbBuf == '\0' || *pbBuf == '.')) { cbLeft--; pbBuf++; } if (!cbLeft) { pVMM->pfnDBGFR3AddrAdd(&AddrDynstrStart, i * cbDynSymEnt); LogFlowFunc(("Found all required section start addresses (.dynsym=%RGv cSymbols=%u, .dynstr=%RGv cb=%u)\n", AddrDynsymStart.FlatPtr, cSymbols, AddrDynstrStart.FlatPtr, pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr)); dbgDiggerFreeBsdLoadSymbols(pThis, pUVM, pVMM, pszName, uKernelStart, cbKernel, &AddrDynsymStart, cSymbols, &AddrDynstrStart, pThis->AddrKernelText.FlatPtr - AddrDynstrStart.FlatPtr); return; } else LogFlowFunc(("Found invalid ASCII character in .dynstr section candidate: %#x\n", *pbBuf)); } else { cSymbols++; if ( ELF32_ST_TYPE(uType) != STT_NOTYPE && FBSD_VALID_ADDRESS(pThis, AddrVal)) { uKernelStart = RT_MIN(uKernelStart, AddrVal); cbKernel = RT_MAX(cbKernel, AddrVal + cbSymVal - uKernelStart); } } } /* Don't account incomplete entries. */ pVMM->pfnDBGFR3AddrAdd(&AddrDynstrStart, (cbToRead / cbDynSymEnt) * cbDynSymEnt); } } else LogFlowFunc((".hash section overlaps with .text section: %zu (expected much less than %u)\n", cbHash, pThis->AddrKernelText.FlatPtr - AddrHashStart.FlatPtr)); } #endif } /** * @interface_method_impl{DBGFOSIDMESG,pfnQueryKernelLog} */ static DECLCALLBACK(int) dbgDiggerFreeBsdIDmsg_QueryKernelLog(PDBGFOSIDMESG pThis, PUVM pUVM, PCVMMR3VTABLE pVMM, uint32_t fFlags, uint32_t cMessages, char *pszBuf, size_t cbBuf, size_t *pcbActual) { PDBGDIGGERFBSD pData = RT_FROM_MEMBER(pThis, DBGDIGGERFBSD, IDmesg); RT_NOREF(fFlags); if (cMessages < 1) return VERR_INVALID_PARAMETER; /* Resolve the message buffer address from the msgbufp symbol. */ RTDBGSYMBOL SymInfo; int rc = pVMM->pfnDBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "kernel!msgbufp", &SymInfo, NULL); if (RT_SUCCESS(rc)) { DBGFADDRESS AddrMsgBuf; /* Read the message buffer pointer. */ RTGCPTR GCPtrMsgBufP = 0; rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrMsgBuf, SymInfo.Value), &GCPtrMsgBufP, pData->f64Bit ? sizeof(uint64_t) : sizeof(uint32_t)); if (RT_FAILURE(rc)) { Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: failed to read msgbufp at %RGv: %Rrc\n", AddrMsgBuf.FlatPtr, rc)); return VERR_NOT_FOUND; } if (!FBSD_VALID_ADDRESS(pData, GCPtrMsgBufP)) { Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: Invalid address for msgbufp: %RGv\n", GCPtrMsgBufP)); return VERR_NOT_FOUND; } /* Read the structure. */ FBSDMSGBUF MsgBuf; rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrMsgBuf, GCPtrMsgBufP), &MsgBuf, sizeof(MsgBuf)); if (RT_SUCCESS(rc)) { RTGCUINTPTR AddrBuf = FBSD_UNION(pData, &MsgBuf, msg_ptr); uint32_t cbMsgBuf = FBSD_UNION(pData, &MsgBuf, msg_size); uint32_t uMsgBufSeqR = FBSD_UNION(pData, &MsgBuf, msg_rseq); uint32_t uMsgBufSeqW = FBSD_UNION(pData, &MsgBuf, msg_wseq); /* * Validate the structure. */ if ( FBSD_UNION(pData, &MsgBuf, msg_magic) != FBSD_MSGBUF_MAGIC || cbMsgBuf < UINT32_C(4096) || cbMsgBuf > 16*_1M || FBSD_UNION(pData, &MsgBuf, msg_rseq) > cbMsgBuf || FBSD_UNION(pData, &MsgBuf, msg_wseq) > cbMsgBuf || !FBSD_VALID_ADDRESS(pData, AddrBuf) ) { Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: Invalid MsgBuf data: msg_magic=%#x msg_size=%#x msg_rseq=%#x msg_wseq=%#x msg_ptr=%RGv\n", FBSD_UNION(pData, &MsgBuf, msg_magic), cbMsgBuf, uMsgBufSeqR, uMsgBufSeqW, AddrBuf)); return VERR_INVALID_STATE; } /* * Read the buffer. */ char *pchMsgBuf = (char *)RTMemAlloc(cbMsgBuf); if (!pchMsgBuf) { Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: Failed to allocate %#x bytes of memory for the log buffer\n", cbMsgBuf)); return VERR_INVALID_STATE; } rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrMsgBuf, AddrBuf), pchMsgBuf, cbMsgBuf); if (RT_SUCCESS(rc)) { /* * Copy it out raw. */ uint32_t offDst = 0; if (uMsgBufSeqR < uMsgBufSeqW) { /* Single chunk between the read and write offsets. */ uint32_t cbToCopy = uMsgBufSeqW - uMsgBufSeqR; if (cbToCopy < cbBuf) { memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbToCopy); pszBuf[cbToCopy] = '\0'; rc = VINF_SUCCESS; } else { if (cbBuf) { memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbBuf - 1); pszBuf[cbBuf - 1] = '\0'; } rc = VERR_BUFFER_OVERFLOW; } offDst = cbToCopy + 1; } else { /* Two chunks, read offset to end, start to write offset. */ uint32_t cbFirst = cbMsgBuf - uMsgBufSeqR; uint32_t cbSecond = uMsgBufSeqW; if (cbFirst + cbSecond < cbBuf) { memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbFirst); memcpy(&pszBuf[cbFirst], pchMsgBuf, cbSecond); offDst = cbFirst + cbSecond; pszBuf[offDst++] = '\0'; rc = VINF_SUCCESS; } else { offDst = cbFirst + cbSecond + 1; if (cbFirst < cbBuf) { memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbFirst); memcpy(&pszBuf[cbFirst], pchMsgBuf, cbBuf - cbFirst); pszBuf[cbBuf - 1] = '\0'; } else if (cbBuf) { memcpy(pszBuf, &pchMsgBuf[uMsgBufSeqR], cbBuf - 1); pszBuf[cbBuf - 1] = '\0'; } rc = VERR_BUFFER_OVERFLOW; } } if (pcbActual) *pcbActual = offDst; } else Log(("dbgDiggerFreeBsdIDmsg_QueryKernelLog: Error reading %#x bytes at %RGv: %Rrc\n", cbBuf, AddrBuf, rc)); RTMemFree(pchMsgBuf); } else LogFlowFunc(("Failed to read message buffer header: %Rrc\n", rc)); } return rc; } /** * @copydoc DBGFOSREG::pfnStackUnwindAssist */ static DECLCALLBACK(int) dbgDiggerFreeBsdStackUnwindAssist(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, VMCPUID idCpu, PDBGFSTACKFRAME pFrame, PRTDBGUNWINDSTATE pState, PCCPUMCTX pInitialCtx, RTDBGAS hAs, uint64_t *puScratch) { RT_NOREF(pUVM, pVMM, pvData, idCpu, pFrame, pState, pInitialCtx, hAs, puScratch); return VINF_SUCCESS; } /** * @copydoc DBGFOSREG::pfnQueryInterface */ static DECLCALLBACK(void *) dbgDiggerFreeBsdQueryInterface(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, DBGFOSINTERFACE enmIf) { PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; RT_NOREF(pUVM, pVMM); switch (enmIf) { case DBGFOSINTERFACE_DMESG: return &pThis->IDmesg; default: return NULL; } } /** * @copydoc DBGFOSREG::pfnQueryVersion */ static DECLCALLBACK(int) dbgDiggerFreeBsdQueryVersion(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData, char *pszVersion, size_t cchVersion) { PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; Assert(pThis->fValid); RT_NOREF(pThis); RTDBGSYMBOL SymInfo; int rc = pVMM->pfnDBGFR3AsSymbolByName(pUVM, DBGF_AS_KERNEL, "kernel!version", &SymInfo, NULL); if (RT_SUCCESS(rc)) { DBGFADDRESS AddrVersion; pVMM->pfnDBGFR3AddrFromFlat(pUVM, &AddrVersion, SymInfo.Value); rc = pVMM->pfnDBGFR3MemReadString(pUVM, 0, &AddrVersion, pszVersion, cchVersion); if (RT_SUCCESS(rc)) { char *pszEnd = RTStrEnd(pszVersion, cchVersion); AssertReturn(pszEnd, VERR_BUFFER_OVERFLOW); while ( pszEnd > pszVersion && RT_C_IS_SPACE(pszEnd[-1])) pszEnd--; *pszEnd = '\0'; } else RTStrPrintf(pszVersion, cchVersion, "DBGFR3MemReadString -> %Rrc", rc); } return rc; } /** * @copydoc DBGFOSREG::pfnTerm */ static DECLCALLBACK(void) dbgDiggerFreeBsdTerm(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData) { PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; Assert(pThis->fValid); RT_NOREF(pUVM, pVMM); pThis->fValid = false; } /** * @copydoc DBGFOSREG::pfnRefresh */ static DECLCALLBACK(int) dbgDiggerFreeBsdRefresh(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData) { PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; NOREF(pThis); Assert(pThis->fValid); dbgDiggerFreeBsdTerm(pUVM, pVMM, pvData); return dbgDiggerFreeBsdInit(pUVM, pVMM, pvData); } /** * @copydoc DBGFOSREG::pfnInit */ static DECLCALLBACK(int) dbgDiggerFreeBsdInit(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData) { PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; Assert(!pThis->fValid); RT_NOREF1(pUVM); dbgDiggerFreeBsdProcessKernelImage(pThis, pUVM, pVMM, "kernel"); pThis->fValid = true; return VINF_SUCCESS; } /** * @copydoc DBGFOSREG::pfnProbe */ static DECLCALLBACK(bool) dbgDiggerFreeBsdProbe(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData) { PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; /* * Look for the magic ELF header near the known start addresses. * If one is found look for the magic "/red/herring" string which is in the * "interp" section not far away and then validate the start of the ELF header * to be sure. */ for (unsigned i = 0; i < RT_ELEMENTS(g_au64FreeBsdKernelAddresses); i++) { static const uint8_t s_abNeedle[] = ELFMAG; DBGFADDRESS KernelAddr; pVMM->pfnDBGFR3AddrFromFlat(pUVM, &KernelAddr, g_au64FreeBsdKernelAddresses[i]); DBGFADDRESS HitAddr; uint32_t cbLeft = FBSD_MAX_KERNEL_SIZE; while (cbLeft > X86_PAGE_4K_SIZE) { int rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &KernelAddr, cbLeft, 1, s_abNeedle, sizeof(s_abNeedle) - 1, &HitAddr); if (RT_FAILURE(rc)) break; /* * Look for the magic "/red/herring" near the header and verify the basic * ELF header. */ DBGFADDRESS HitAddrInterp; rc = pVMM->pfnDBGFR3MemScan(pUVM, 0 /*idCpu*/, &HitAddr, FBSD_MAX_INTERP_OFFSET, 1, g_abNeedleInterp, sizeof(g_abNeedleInterp), &HitAddrInterp); if (RT_SUCCESS(rc)) { union { uint8_t ab[2 * X86_PAGE_4K_SIZE]; Elf32_Ehdr Hdr32; Elf64_Ehdr Hdr64; } ElfHdr; AssertCompileMembersSameSizeAndOffset(Elf64_Ehdr, e_ident, Elf32_Ehdr, e_ident); AssertCompileMembersSameSizeAndOffset(Elf64_Ehdr, e_type, Elf32_Ehdr, e_type); AssertCompileMembersSameSizeAndOffset(Elf64_Ehdr, e_machine, Elf32_Ehdr, e_machine); AssertCompileMembersSameSizeAndOffset(Elf64_Ehdr, e_version, Elf32_Ehdr, e_version); rc = pVMM->pfnDBGFR3MemRead(pUVM, 0 /*idCpu*/, &HitAddr, &ElfHdr.ab[0], X86_PAGE_4K_SIZE); if (RT_SUCCESS(rc)) { /* We verified the magic above already by scanning for it. */ if ( ( ElfHdr.Hdr32.e_ident[EI_CLASS] == ELFCLASS32 || ElfHdr.Hdr32.e_ident[EI_CLASS] == ELFCLASS64) && ElfHdr.Hdr32.e_ident[EI_DATA] == ELFDATA2LSB && ElfHdr.Hdr32.e_ident[EI_VERSION] == EV_CURRENT && ElfHdr.Hdr32.e_ident[EI_OSABI] == ELFOSABI_FREEBSD && ElfHdr.Hdr32.e_type == ET_EXEC && ( ElfHdr.Hdr32.e_machine == EM_386 || ElfHdr.Hdr32.e_machine == EM_X86_64) && ElfHdr.Hdr32.e_version == EV_CURRENT) { pThis->f64Bit = ElfHdr.Hdr32.e_ident[EI_CLASS] == ELFCLASS64; pThis->AddrKernelElfStart = HitAddr; pThis->AddrKernelInterp = HitAddrInterp; pVMM->pfnDBGFR3AddrFromFlat(pUVM, &pThis->AddrKernelText, FBSD_UNION(pThis, &ElfHdr, e_entry)); LogFunc(("Found %s FreeBSD kernel at %RGv (.interp section at %RGv, .text section at %RGv)\n", pThis->f64Bit ? "amd64" : "i386", pThis->AddrKernelElfStart.FlatPtr, pThis->AddrKernelInterp.FlatPtr, pThis->AddrKernelText.FlatPtr)); return true; } } } /* * Advance. */ RTGCUINTPTR cbDistance = HitAddr.FlatPtr - KernelAddr.FlatPtr + sizeof(s_abNeedle) - 1; if (RT_UNLIKELY(cbDistance >= cbLeft)) break; cbLeft -= cbDistance; pVMM->pfnDBGFR3AddrAdd(&KernelAddr, cbDistance); } } return false; } /** * @copydoc DBGFOSREG::pfnDestruct */ static DECLCALLBACK(void) dbgDiggerFreeBsdDestruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData) { RT_NOREF(pUVM, pVMM, pvData); } /** * @copydoc DBGFOSREG::pfnConstruct */ static DECLCALLBACK(int) dbgDiggerFreeBsdConstruct(PUVM pUVM, PCVMMR3VTABLE pVMM, void *pvData) { PDBGDIGGERFBSD pThis = (PDBGDIGGERFBSD)pvData; RT_NOREF(pUVM, pVMM); pThis->fValid = false; pThis->f64Bit = false; pThis->IDmesg.u32Magic = DBGFOSIDMESG_MAGIC; pThis->IDmesg.pfnQueryKernelLog = dbgDiggerFreeBsdIDmsg_QueryKernelLog; pThis->IDmesg.u32EndMagic = DBGFOSIDMESG_MAGIC; return VINF_SUCCESS; } const DBGFOSREG g_DBGDiggerFreeBsd = { /* .u32Magic = */ DBGFOSREG_MAGIC, /* .fFlags = */ 0, /* .cbData = */ sizeof(DBGDIGGERFBSD), /* .szName = */ "FreeBSD", /* .pfnConstruct = */ dbgDiggerFreeBsdConstruct, /* .pfnDestruct = */ dbgDiggerFreeBsdDestruct, /* .pfnProbe = */ dbgDiggerFreeBsdProbe, /* .pfnInit = */ dbgDiggerFreeBsdInit, /* .pfnRefresh = */ dbgDiggerFreeBsdRefresh, /* .pfnTerm = */ dbgDiggerFreeBsdTerm, /* .pfnQueryVersion = */ dbgDiggerFreeBsdQueryVersion, /* .pfnQueryInterface = */ dbgDiggerFreeBsdQueryInterface, /* .pfnStackUnwindAssist = */ dbgDiggerFreeBsdStackUnwindAssist, /* .u32EndMagic = */ DBGFOSREG_MAGIC };