diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Debugger/DBGPlugInFreeBsd.cpp | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream.tar.xz virtualbox-upstream.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Debugger/DBGPlugInFreeBsd.cpp')
-rw-r--r-- | src/VBox/Debugger/DBGPlugInFreeBsd.cpp | 961 |
1 files changed, 961 insertions, 0 deletions
diff --git a/src/VBox/Debugger/DBGPlugInFreeBsd.cpp b/src/VBox/Debugger/DBGPlugInFreeBsd.cpp new file mode 100644 index 00000000..1d373c34 --- /dev/null +++ b/src/VBox/Debugger/DBGPlugInFreeBsd.cpp @@ -0,0 +1,961 @@ +/* $Id: DBGPlugInFreeBsd.cpp $ */ +/** @file + * DBGPlugInFreeBsd - Debugger and Guest OS Digger Plugin For FreeBSD. + */ + +/* + * Copyright (C) 2016-2022 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 <https://www.gnu.org/licenses>. + * + * 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 <VBox/vmm/vmmr3vtable.h> +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/stream.h> +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* 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 +}; + |