From 3fa2d384c245bcee3a9ecfa11f298b76ea4c9d57 Mon Sep 17 00:00:00 2001 From: Viktor Prutyanov Date: Wed, 29 Aug 2018 15:41:25 +0300 Subject: [PATCH] contrib: add elf2dmp tool elf2dmp is a converter from ELF dump (produced by 'dump-guest-memory') to Windows MEMORY.DMP format (also know as 'Complete Memory Dump') which can be opened in WinDbg. This tool can help if VMCoreInfo device/driver is absent in Windows VM and 'dump-guest-memory -w' is not available but dump can be created in ELF format. The tool works as follows: 1. Determine the system paging root looking at GS_BASE or KERNEL_GS_BASE to locate the PRCB structure and finds the kernel CR3 nearby if QEMU CPU state CR3 is not suitable. 2. Find an address within the kernel image by dereferencing the first IDT entry and scans virtual memory upwards until the start of the kernel. 3. Download a PDB matching the kernel from the Microsoft symbol store, and figure out the layout of certain relevant structures necessary for the dump. 4. Populate the corresponding structures in the memory image and create the appropriate dump header. Signed-off-by: Viktor Prutyanov Message-Id: <1535546488-30208-3-git-send-email-viktor.prutyanov@virtuozzo.com> Signed-off-by: Paolo Bonzini --- Makefile | 5 + Makefile.objs | 1 + configure | 3 + contrib/elf2dmp/Makefile.objs | 1 + contrib/elf2dmp/addrspace.c | 233 ++++++++++++++ contrib/elf2dmp/addrspace.h | 44 +++ contrib/elf2dmp/download.c | 47 +++ contrib/elf2dmp/download.h | 13 + contrib/elf2dmp/err.h | 13 + contrib/elf2dmp/kdbg.h | 194 +++++++++++ contrib/elf2dmp/main.c | 589 ++++++++++++++++++++++++++++++++++ contrib/elf2dmp/pdb.c | 322 +++++++++++++++++++ contrib/elf2dmp/pdb.h | 241 ++++++++++++++ contrib/elf2dmp/pe.h | 121 +++++++ contrib/elf2dmp/qemu_elf.c | 164 ++++++++++ contrib/elf2dmp/qemu_elf.h | 51 +++ 16 files changed, 2042 insertions(+) create mode 100644 contrib/elf2dmp/Makefile.objs create mode 100644 contrib/elf2dmp/addrspace.c create mode 100644 contrib/elf2dmp/addrspace.h create mode 100644 contrib/elf2dmp/download.c create mode 100644 contrib/elf2dmp/download.h create mode 100644 contrib/elf2dmp/err.h create mode 100644 contrib/elf2dmp/kdbg.h create mode 100644 contrib/elf2dmp/main.c create mode 100644 contrib/elf2dmp/pdb.c create mode 100644 contrib/elf2dmp/pdb.h create mode 100644 contrib/elf2dmp/pe.h create mode 100644 contrib/elf2dmp/qemu_elf.c create mode 100644 contrib/elf2dmp/qemu_elf.h diff --git a/Makefile b/Makefile index 3730092817..1144d6e3ba 100644 --- a/Makefile +++ b/Makefile @@ -415,6 +415,7 @@ dummy := $(call unnest-vars,, \ chardev-obj-y \ util-obj-y \ qga-obj-y \ + elf2dmp-obj-y \ ivshmem-client-obj-y \ ivshmem-server-obj-y \ libvhost-user-obj-y \ @@ -710,6 +711,10 @@ ifneq ($(EXESUF),) qemu-ga: qemu-ga$(EXESUF) $(QGA_VSS_PROVIDER) $(QEMU_GA_MSI) endif +elf2dmp: LIBS = $(CURL_LIBS) +elf2dmp: $(elf2dmp-obj-y) + $(call LINK, $^) + ifdef CONFIG_IVSHMEM ivshmem-client$(EXESUF): $(ivshmem-client-obj-y) $(COMMON_LDADDS) $(call LINK, $^) diff --git a/Makefile.objs b/Makefile.objs index ce9c79235e..1e1ff387d7 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -186,6 +186,7 @@ qga-vss-dll-obj-y = qga/ ###################################################################### # contrib +elf2dmp-obj-y = contrib/elf2dmp/ ivshmem-client-obj-$(CONFIG_IVSHMEM) = contrib/ivshmem-client/ ivshmem-server-obj-$(CONFIG_IVSHMEM) = contrib/ivshmem-server/ libvhost-user-obj-y = contrib/libvhost-user/ diff --git a/configure b/configure index 4fc1feaa6f..f3d4b799a5 100755 --- a/configure +++ b/configure @@ -5721,6 +5721,9 @@ if test "$want_tools" = "yes" ; then if [ "$ivshmem" = "yes" ]; then tools="ivshmem-client\$(EXESUF) ivshmem-server\$(EXESUF) $tools" fi + if [ "$posix" = "yes" ] && [ "$curl" = "yes" ]; then + tools="elf2dmp $tools" + fi fi if test "$softmmu" = yes ; then if test "$linux" = yes; then diff --git a/contrib/elf2dmp/Makefile.objs b/contrib/elf2dmp/Makefile.objs new file mode 100644 index 0000000000..e3140f58cf --- /dev/null +++ b/contrib/elf2dmp/Makefile.objs @@ -0,0 +1 @@ +elf2dmp-obj-y = main.o addrspace.o download.o pdb.o qemu_elf.o diff --git a/contrib/elf2dmp/addrspace.c b/contrib/elf2dmp/addrspace.c new file mode 100644 index 0000000000..8a76069cb5 --- /dev/null +++ b/contrib/elf2dmp/addrspace.c @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#include "qemu/osdep.h" +#include "addrspace.h" + +static struct pa_block *pa_space_find_block(struct pa_space *ps, uint64_t pa) +{ + size_t i; + for (i = 0; i < ps->block_nr; i++) { + if (ps->block[i].paddr <= pa && + pa <= ps->block[i].paddr + ps->block[i].size) { + return ps->block + i; + } + } + + return NULL; +} + +static uint8_t *pa_space_resolve(struct pa_space *ps, uint64_t pa) +{ + struct pa_block *block = pa_space_find_block(ps, pa); + + if (!block) { + return NULL; + } + + return block->addr + (pa - block->paddr); +} + +int pa_space_create(struct pa_space *ps, QEMU_Elf *qemu_elf) +{ + Elf64_Half phdr_nr = elf_getphdrnum(qemu_elf->map); + Elf64_Phdr *phdr = elf64_getphdr(qemu_elf->map); + size_t block_i = 0; + size_t i; + + ps->block_nr = 0; + + for (i = 0; i < phdr_nr; i++) { + if (phdr[i].p_type == PT_LOAD) { + ps->block_nr++; + } + } + + ps->block = malloc(sizeof(*ps->block) * ps->block_nr); + if (!ps->block) { + return 1; + } + + for (i = 0; i < phdr_nr; i++) { + if (phdr[i].p_type == PT_LOAD) { + ps->block[block_i] = (struct pa_block) { + .addr = (uint8_t *)qemu_elf->map + phdr[i].p_offset, + .paddr = phdr[i].p_paddr, + .size = phdr[i].p_filesz, + }; + block_i++; + } + } + + return 0; +} + +void pa_space_destroy(struct pa_space *ps) +{ + ps->block_nr = 0; + free(ps->block); +} + +void va_space_set_dtb(struct va_space *vs, uint64_t dtb) +{ + vs->dtb = dtb & 0x00ffffffffff000; +} + +void va_space_create(struct va_space *vs, struct pa_space *ps, uint64_t dtb) +{ + vs->ps = ps; + va_space_set_dtb(vs, dtb); +} + +static uint64_t get_pml4e(struct va_space *vs, uint64_t va) +{ + uint64_t pa = (vs->dtb & 0xffffffffff000) | ((va & 0xff8000000000) >> 36); + + return *(uint64_t *)pa_space_resolve(vs->ps, pa); +} + +static uint64_t get_pdpi(struct va_space *vs, uint64_t va, uint64_t pml4e) +{ + uint64_t pdpte_paddr = (pml4e & 0xffffffffff000) | + ((va & 0x7FC0000000) >> 27); + + return *(uint64_t *)pa_space_resolve(vs->ps, pdpte_paddr); +} + +static uint64_t pde_index(uint64_t va) +{ + return (va >> 21) & 0x1FF; +} + +static uint64_t pdba_base(uint64_t pdpe) +{ + return pdpe & 0xFFFFFFFFFF000; +} + +static uint64_t get_pgd(struct va_space *vs, uint64_t va, uint64_t pdpe) +{ + uint64_t pgd_entry = pdba_base(pdpe) + pde_index(va) * 8; + + return *(uint64_t *)pa_space_resolve(vs->ps, pgd_entry); +} + +static uint64_t pte_index(uint64_t va) +{ + return (va >> 12) & 0x1FF; +} + +static uint64_t ptba_base(uint64_t pde) +{ + return pde & 0xFFFFFFFFFF000; +} + +static uint64_t get_pte(struct va_space *vs, uint64_t va, uint64_t pgd) +{ + uint64_t pgd_val = ptba_base(pgd) + pte_index(va) * 8; + + return *(uint64_t *)pa_space_resolve(vs->ps, pgd_val); +} + +static uint64_t get_paddr(uint64_t va, uint64_t pte) +{ + return (pte & 0xFFFFFFFFFF000) | (va & 0xFFF); +} + +static bool is_present(uint64_t entry) +{ + return entry & 0x1; +} + +static bool page_size_flag(uint64_t entry) +{ + return entry & (1 << 7); +} + +static uint64_t get_1GB_paddr(uint64_t va, uint64_t pdpte) +{ + return (pdpte & 0xfffffc0000000) | (va & 0x3fffffff); +} + +static uint64_t get_2MB_paddr(uint64_t va, uint64_t pgd_entry) +{ + return (pgd_entry & 0xfffffffe00000) | (va & 0x00000001fffff); +} + +static uint64_t va_space_va2pa(struct va_space *vs, uint64_t va) +{ + uint64_t pml4e, pdpe, pgd, pte; + + pml4e = get_pml4e(vs, va); + if (!is_present(pml4e)) { + return INVALID_PA; + } + + pdpe = get_pdpi(vs, va, pml4e); + if (!is_present(pdpe)) { + return INVALID_PA; + } + + if (page_size_flag(pdpe)) { + return get_1GB_paddr(va, pdpe); + } + + pgd = get_pgd(vs, va, pdpe); + if (!is_present(pgd)) { + return INVALID_PA; + } + + if (page_size_flag(pgd)) { + return get_2MB_paddr(va, pgd); + } + + pte = get_pte(vs, va, pgd); + if (!is_present(pte)) { + return INVALID_PA; + } + + return get_paddr(va, pte); +} + +void *va_space_resolve(struct va_space *vs, uint64_t va) +{ + uint64_t pa = va_space_va2pa(vs, va); + + if (pa == INVALID_PA) { + return NULL; + } + + return pa_space_resolve(vs->ps, pa); +} + +int va_space_rw(struct va_space *vs, uint64_t addr, + void *buf, size_t size, int is_write) +{ + while (size) { + uint64_t page = addr & PFN_MASK; + size_t s = (page + PAGE_SIZE) - addr; + void *ptr; + + s = (s > size) ? size : s; + + ptr = va_space_resolve(vs, addr); + if (!ptr) { + return 1; + } + + if (is_write) { + memcpy(ptr, buf, s); + } else { + memcpy(buf, ptr, s); + } + + size -= s; + buf = (uint8_t *)buf + s; + addr += s; + } + + return 0; +} diff --git a/contrib/elf2dmp/addrspace.h b/contrib/elf2dmp/addrspace.h new file mode 100644 index 0000000000..d87f6a18c6 --- /dev/null +++ b/contrib/elf2dmp/addrspace.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#ifndef ADDRSPACE_H +#define ADDRSPACE_H + +#include "qemu_elf.h" + +#define PAGE_BITS 12 +#define PAGE_SIZE (1ULL << PAGE_BITS) +#define PFN_MASK (~(PAGE_SIZE - 1)) + +#define INVALID_PA UINT64_MAX + +struct pa_block { + uint8_t *addr; + uint64_t paddr; + uint64_t size; +}; + +struct pa_space { + size_t block_nr; + struct pa_block *block; +}; + +struct va_space { + uint64_t dtb; + struct pa_space *ps; +}; + +int pa_space_create(struct pa_space *ps, QEMU_Elf *qemu_elf); +void pa_space_destroy(struct pa_space *ps); + +void va_space_create(struct va_space *vs, struct pa_space *ps, uint64_t dtb); +void va_space_set_dtb(struct va_space *vs, uint64_t dtb); +void *va_space_resolve(struct va_space *vs, uint64_t va); +int va_space_rw(struct va_space *vs, uint64_t addr, + void *buf, size_t size, int is_write); + +#endif /* ADDRSPACE_H */ diff --git a/contrib/elf2dmp/download.c b/contrib/elf2dmp/download.c new file mode 100644 index 0000000000..d09e607431 --- /dev/null +++ b/contrib/elf2dmp/download.c @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#include "qemu/osdep.h" +#include +#include "download.h" + +int download_url(const char *name, const char *url) +{ + int err = 0; + FILE *file; + CURL *curl = curl_easy_init(); + + if (!curl) { + return 1; + } + + file = fopen(name, "wb"); + if (!file) { + err = 1; + goto out_curl; + } + + curl_easy_setopt(curl, CURLOPT_URL, url); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0); + + if (curl_easy_perform(curl) != CURLE_OK) { + err = 1; + fclose(file); + unlink(name); + goto out_curl; + } + + err = fclose(file); + +out_curl: + curl_easy_cleanup(curl); + + return err; +} diff --git a/contrib/elf2dmp/download.h b/contrib/elf2dmp/download.h new file mode 100644 index 0000000000..5c274925f7 --- /dev/null +++ b/contrib/elf2dmp/download.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#ifndef DOWNLOAD_H +#define DOWNLOAD_H + +int download_url(const char *name, const char *url); + +#endif /* DOWNLOAD_H */ diff --git a/contrib/elf2dmp/err.h b/contrib/elf2dmp/err.h new file mode 100644 index 0000000000..5456bd5a30 --- /dev/null +++ b/contrib/elf2dmp/err.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#ifndef ERR_H +#define ERR_H + +#define eprintf(...) fprintf(stderr, __VA_ARGS__) + +#endif /* ERR_H */ diff --git a/contrib/elf2dmp/kdbg.h b/contrib/elf2dmp/kdbg.h new file mode 100644 index 0000000000..851b57c321 --- /dev/null +++ b/contrib/elf2dmp/kdbg.h @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#ifndef KDBG_H +#define KDBG_H + +typedef struct DBGKD_GET_VERSION64 { + uint16_t MajorVersion; + uint16_t MinorVersion; + uint8_t ProtocolVersion; + uint8_t KdSecondaryVersion; + uint16_t Flags; + uint16_t MachineType; + uint8_t MaxPacketType; + uint8_t MaxStateChange; + uint8_t MaxManipulate; + uint8_t Simulation; + uint16_t Unused[1]; + uint64_t KernBase; + uint64_t PsLoadedModuleList; + uint64_t DebuggerDataList; +} DBGKD_GET_VERSION64; + +typedef struct DBGKD_DEBUG_DATA_HEADER64 { + struct LIST_ENTRY64 { + struct LIST_ENTRY64 *Flink; + struct LIST_ENTRY64 *Blink; + } List; + uint32_t OwnerTag; + uint32_t Size; +} DBGKD_DEBUG_DATA_HEADER64; + +typedef struct KDDEBUGGER_DATA64 { + DBGKD_DEBUG_DATA_HEADER64 Header; + + uint64_t KernBase; + uint64_t BreakpointWithStatus; + uint64_t SavedContext; + uint16_t ThCallbackStack; + uint16_t NextCallback; + uint16_t FramePointer; + uint16_t PaeEnabled:1; + uint64_t KiCallUserMode; + uint64_t KeUserCallbackDispatcher; + uint64_t PsLoadedModuleList; + uint64_t PsActiveProcessHead; + uint64_t PspCidTable; + uint64_t ExpSystemResourcesList; + uint64_t ExpPagedPoolDescriptor; + uint64_t ExpNumberOfPagedPools; + uint64_t KeTimeIncrement; + uint64_t KeBugCheckCallbackListHead; + uint64_t KiBugcheckData; + uint64_t IopErrorLogListHead; + uint64_t ObpRootDirectoryObject; + uint64_t ObpTypeObjectType; + uint64_t MmSystemCacheStart; + uint64_t MmSystemCacheEnd; + uint64_t MmSystemCacheWs; + uint64_t MmPfnDatabase; + uint64_t MmSystemPtesStart; + uint64_t MmSystemPtesEnd; + uint64_t MmSubsectionBase; + uint64_t MmNumberOfPagingFiles; + uint64_t MmLowestPhysicalPage; + uint64_t MmHighestPhysicalPage; + uint64_t MmNumberOfPhysicalPages; + uint64_t MmMaximumNonPagedPoolInBytes; + uint64_t MmNonPagedSystemStart; + uint64_t MmNonPagedPoolStart; + uint64_t MmNonPagedPoolEnd; + uint64_t MmPagedPoolStart; + uint64_t MmPagedPoolEnd; + uint64_t MmPagedPoolInformation; + uint64_t MmPageSize; + uint64_t MmSizeOfPagedPoolInBytes; + uint64_t MmTotalCommitLimit; + uint64_t MmTotalCommittedPages; + uint64_t MmSharedCommit; + uint64_t MmDriverCommit; + uint64_t MmProcessCommit; + uint64_t MmPagedPoolCommit; + uint64_t MmExtendedCommit; + uint64_t MmZeroedPageListHead; + uint64_t MmFreePageListHead; + uint64_t MmStandbyPageListHead; + uint64_t MmModifiedPageListHead; + uint64_t MmModifiedNoWritePageListHead; + uint64_t MmAvailablePages; + uint64_t MmResidentAvailablePages; + uint64_t PoolTrackTable; + uint64_t NonPagedPoolDescriptor; + uint64_t MmHighestUserAddress; + uint64_t MmSystemRangeStart; + uint64_t MmUserProbeAddress; + uint64_t KdPrintCircularBuffer; + uint64_t KdPrintCircularBufferEnd; + uint64_t KdPrintWritePointer; + uint64_t KdPrintRolloverCount; + uint64_t MmLoadedUserImageList; + + /* NT 5.1 Addition */ + + uint64_t NtBuildLab; + uint64_t KiNormalSystemCall; + + /* NT 5.0 hotfix addition */ + + uint64_t KiProcessorBlock; + uint64_t MmUnloadedDrivers; + uint64_t MmLastUnloadedDriver; + uint64_t MmTriageActionTaken; + uint64_t MmSpecialPoolTag; + uint64_t KernelVerifier; + uint64_t MmVerifierData; + uint64_t MmAllocatedNonPagedPool; + uint64_t MmPeakCommitment; + uint64_t MmTotalCommitLimitMaximum; + uint64_t CmNtCSDVersion; + + /* NT 5.1 Addition */ + + uint64_t MmPhysicalMemoryBlock; + uint64_t MmSessionBase; + uint64_t MmSessionSize; + uint64_t MmSystemParentTablePage; + + /* Server 2003 addition */ + + uint64_t MmVirtualTranslationBase; + uint16_t OffsetKThreadNextProcessor; + uint16_t OffsetKThreadTeb; + uint16_t OffsetKThreadKernelStack; + uint16_t OffsetKThreadInitialStack; + uint16_t OffsetKThreadApcProcess; + uint16_t OffsetKThreadState; + uint16_t OffsetKThreadBStore; + uint16_t OffsetKThreadBStoreLimit; + uint16_t SizeEProcess; + uint16_t OffsetEprocessPeb; + uint16_t OffsetEprocessParentCID; + uint16_t OffsetEprocessDirectoryTableBase; + uint16_t SizePrcb; + uint16_t OffsetPrcbDpcRoutine; + uint16_t OffsetPrcbCurrentThread; + uint16_t OffsetPrcbMhz; + uint16_t OffsetPrcbCpuType; + uint16_t OffsetPrcbVendorString; + uint16_t OffsetPrcbProcStateContext; + uint16_t OffsetPrcbNumber; + uint16_t SizeEThread; + uint64_t KdPrintCircularBufferPtr; + uint64_t KdPrintBufferSize; + uint64_t KeLoaderBlock; + uint16_t SizePcr; + uint16_t OffsetPcrSelfPcr; + uint16_t OffsetPcrCurrentPrcb; + uint16_t OffsetPcrContainedPrcb; + uint16_t OffsetPcrInitialBStore; + uint16_t OffsetPcrBStoreLimit; + uint16_t OffsetPcrInitialStack; + uint16_t OffsetPcrStackLimit; + uint16_t OffsetPrcbPcrPage; + uint16_t OffsetPrcbProcStateSpecialReg; + uint16_t GdtR0Code; + uint16_t GdtR0Data; + uint16_t GdtR0Pcr; + uint16_t GdtR3Code; + uint16_t GdtR3Data; + uint16_t GdtR3Teb; + uint16_t GdtLdt; + uint16_t GdtTss; + uint16_t Gdt64R3CmCode; + uint16_t Gdt64R3CmTeb; + uint64_t IopNumTriageDumpDataBlocks; + uint64_t IopTriageDumpDataBlocks; + + /* Longhorn addition */ + + uint64_t VfCrashDataBlock; + uint64_t MmBadPagesDetected; + uint64_t MmZeroedPageSingleBitErrorsDetected; + + /* Windows 7 addition */ + + uint64_t EtwpDebuggerData; + uint16_t OffsetPrcbContext; +} KDDEBUGGER_DATA64; + +#endif /* KDBG_H */ diff --git a/contrib/elf2dmp/main.c b/contrib/elf2dmp/main.c new file mode 100644 index 0000000000..9b93dab662 --- /dev/null +++ b/contrib/elf2dmp/main.c @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#include "qemu/osdep.h" +#include "err.h" +#include "addrspace.h" +#include "pe.h" +#include "pdb.h" +#include "kdbg.h" +#include "download.h" +#include "qemu/win_dump_defs.h" + +#define SYM_URL_BASE "https://msdl.microsoft.com/download/symbols/" +#define PDB_NAME "ntkrnlmp.pdb" + +#define INITIAL_MXCSR 0x1f80 + +typedef struct idt_desc { + uint16_t offset1; /* offset bits 0..15 */ + uint16_t selector; + uint8_t ist; + uint8_t type_attr; + uint16_t offset2; /* offset bits 16..31 */ + uint32_t offset3; /* offset bits 32..63 */ + uint32_t rsrvd; +} __attribute__ ((packed)) idt_desc_t; + +static uint64_t idt_desc_addr(idt_desc_t desc) +{ + return (uint64_t)desc.offset1 | ((uint64_t)desc.offset2 << 16) | + ((uint64_t)desc.offset3 << 32); +} + +static const uint64_t SharedUserData = 0xfffff78000000000; + +#define KUSD_OFFSET_SUITE_MASK 0x2d0 +#define KUSD_OFFSET_PRODUCT_TYPE 0x264 + +#define SYM_RESOLVE(base, r, s) ((s = pdb_resolve(base, r, #s)),\ + s ? printf(#s" = 0x%016lx\n", s) : eprintf("Failed to resolve "#s"\n"), s) + +static uint64_t rol(uint64_t x, uint64_t y) +{ + return (x << y) | (x >> (64 - y)); +} + +/* + * Decoding algorithm can be found in Volatility project + */ +static void kdbg_decode(uint64_t *dst, uint64_t *src, size_t size, + uint64_t kwn, uint64_t kwa, uint64_t kdbe) +{ + size_t i; + assert(size % sizeof(uint64_t) == 0); + for (i = 0; i < size / sizeof(uint64_t); i++) { + uint64_t block; + + block = src[i]; + block = rol(block ^ kwn, (uint8_t)kwn); + block = __builtin_bswap64(block ^ kdbe) ^ kwa; + dst[i] = block; + } +} + +static KDDEBUGGER_DATA64 *get_kdbg(uint64_t KernBase, struct pdb_reader *pdb, + struct va_space *vs, uint64_t KdDebuggerDataBlock) +{ + const char OwnerTag[4] = "KDBG"; + KDDEBUGGER_DATA64 *kdbg = NULL; + DBGKD_DEBUG_DATA_HEADER64 kdbg_hdr; + bool decode = false; + uint64_t kwn, kwa, KdpDataBlockEncoded; + + if (va_space_rw(vs, + KdDebuggerDataBlock + offsetof(KDDEBUGGER_DATA64, Header), + &kdbg_hdr, sizeof(kdbg_hdr), 0)) { + eprintf("Failed to extract KDBG header\n"); + return NULL; + } + + if (memcmp(&kdbg_hdr.OwnerTag, OwnerTag, sizeof(OwnerTag))) { + uint64_t KiWaitNever, KiWaitAlways; + + decode = true; + + if (!SYM_RESOLVE(KernBase, pdb, KiWaitNever) || + !SYM_RESOLVE(KernBase, pdb, KiWaitAlways) || + !SYM_RESOLVE(KernBase, pdb, KdpDataBlockEncoded)) { + return NULL; + } + + if (va_space_rw(vs, KiWaitNever, &kwn, sizeof(kwn), 0) || + va_space_rw(vs, KiWaitAlways, &kwa, sizeof(kwa), 0)) { + return NULL; + } + + printf("[KiWaitNever] = 0x%016lx\n", kwn); + printf("[KiWaitAlways] = 0x%016lx\n", kwa); + + /* + * If KDBG header can be decoded, KDBG size is available + * and entire KDBG can be decoded. + */ + printf("Decoding KDBG header...\n"); + kdbg_decode((uint64_t *)&kdbg_hdr, (uint64_t *)&kdbg_hdr, + sizeof(kdbg_hdr), kwn, kwa, KdpDataBlockEncoded); + + printf("Owner tag is \'%.4s\'\n", (char *)&kdbg_hdr.OwnerTag); + if (memcmp(&kdbg_hdr.OwnerTag, OwnerTag, sizeof(OwnerTag))) { + eprintf("Failed to decode KDBG header\n"); + return NULL; + } + } + + kdbg = malloc(kdbg_hdr.Size); + if (!kdbg) { + return NULL; + } + + if (va_space_rw(vs, KdDebuggerDataBlock, kdbg, kdbg_hdr.Size, 0)) { + eprintf("Failed to extract entire KDBG\n"); + return NULL; + } + + if (!decode) { + return kdbg; + } + + printf("Decoding KdDebuggerDataBlock...\n"); + kdbg_decode((uint64_t *)kdbg, (uint64_t *)kdbg, kdbg_hdr.Size, + kwn, kwa, KdpDataBlockEncoded); + + va_space_rw(vs, KdDebuggerDataBlock, kdbg, kdbg_hdr.Size, 1); + + return kdbg; +} + +static void win_context_init_from_qemu_cpu_state(WinContext *ctx, + QEMUCPUState *s) +{ + WinContext win_ctx = (WinContext){ + .ContextFlags = WIN_CTX_X64 | WIN_CTX_INT | WIN_CTX_SEG | WIN_CTX_CTL, + .MxCsr = INITIAL_MXCSR, + + .SegCs = s->cs.selector, + .SegSs = s->ss.selector, + .SegDs = s->ds.selector, + .SegEs = s->es.selector, + .SegFs = s->fs.selector, + .SegGs = s->gs.selector, + .EFlags = (uint32_t)s->rflags, + + .Rax = s->rax, + .Rbx = s->rbx, + .Rcx = s->rcx, + .Rdx = s->rdx, + .Rsp = s->rsp, + .Rbp = s->rbp, + .Rsi = s->rsi, + .Rdi = s->rdi, + .R8 = s->r8, + .R9 = s->r9, + .R10 = s->r10, + .R11 = s->r11, + .R12 = s->r12, + .R13 = s->r13, + .R14 = s->r14, + .R15 = s->r15, + + .Rip = s->rip, + .FltSave = { + .MxCsr = INITIAL_MXCSR, + }, + }; + + *ctx = win_ctx; +} + +/* + * Finds paging-structure hierarchy base, + * if previously set doesn't give access to kernel structures + */ +static int fix_dtb(struct va_space *vs, QEMU_Elf *qe) +{ + /* + * Firstly, test previously set DTB. + */ + if (va_space_resolve(vs, SharedUserData)) { + return 0; + } + + /* + * Secondly, find CPU which run system task. + */ + size_t i; + for (i = 0; i < qe->state_nr; i++) { + QEMUCPUState *s = qe->state[i]; + + if (is_system(s)) { + va_space_set_dtb(vs, s->cr[3]); + printf("DTB 0x%016lx has been found from CPU #%zu" + " as system task CR3\n", vs->dtb, i); + return !(va_space_resolve(vs, SharedUserData)); + } + } + + /* + * Thirdly, use KERNEL_GS_BASE from CPU #0 as PRCB address and + * CR3 as [Prcb+0x7000] + */ + if (qe->has_kernel_gs_base) { + QEMUCPUState *s = qe->state[0]; + uint64_t Prcb = s->kernel_gs_base; + uint64_t *cr3 = va_space_resolve(vs, Prcb + 0x7000); + + if (!cr3) { + return 1; + } + + va_space_set_dtb(vs, *cr3); + printf("DirectoryTableBase = 0x%016lx has been found from CPU #0" + " as interrupt handling CR3\n", vs->dtb); + return !(va_space_resolve(vs, SharedUserData)); + } + + return 1; +} + +static int fill_header(WinDumpHeader64 *hdr, struct pa_space *ps, + struct va_space *vs, uint64_t KdDebuggerDataBlock, + KDDEBUGGER_DATA64 *kdbg, uint64_t KdVersionBlock, int nr_cpus) +{ + uint32_t *suite_mask = va_space_resolve(vs, SharedUserData + + KUSD_OFFSET_SUITE_MASK); + int32_t *product_type = va_space_resolve(vs, SharedUserData + + KUSD_OFFSET_PRODUCT_TYPE); + DBGKD_GET_VERSION64 kvb; + WinDumpHeader64 h; + size_t i; + + QEMU_BUILD_BUG_ON(KUSD_OFFSET_SUITE_MASK >= PAGE_SIZE); + QEMU_BUILD_BUG_ON(KUSD_OFFSET_PRODUCT_TYPE >= PAGE_SIZE); + + if (!suite_mask || !product_type) { + return 1; + } + + if (va_space_rw(vs, KdVersionBlock, &kvb, sizeof(kvb), 0)) { + eprintf("Failed to extract KdVersionBlock\n"); + return 1; + } + + h = (WinDumpHeader64) { + .Signature = "PAGE", + .ValidDump = "DU64", + .MajorVersion = kvb.MajorVersion, + .MinorVersion = kvb.MinorVersion, + .DirectoryTableBase = vs->dtb, + .PfnDatabase = kdbg->MmPfnDatabase, + .PsLoadedModuleList = kdbg->PsLoadedModuleList, + .PsActiveProcessHead = kdbg->PsActiveProcessHead, + .MachineImageType = kvb.MachineType, + .NumberProcessors = nr_cpus, + .BugcheckCode = LIVE_SYSTEM_DUMP, + .KdDebuggerDataBlock = KdDebuggerDataBlock, + .DumpType = 1, + .Comment = "Hello from elf2dmp!", + .SuiteMask = *suite_mask, + .ProductType = *product_type, + .SecondaryDataState = kvb.KdSecondaryVersion, + .PhysicalMemoryBlock = (WinDumpPhyMemDesc64) { + .NumberOfRuns = ps->block_nr, + }, + .RequiredDumpSpace = sizeof(h), + }; + + for (i = 0; i < ps->block_nr; i++) { + h.PhysicalMemoryBlock.NumberOfPages += ps->block[i].size / PAGE_SIZE; + h.PhysicalMemoryBlock.Run[i] = (WinDumpPhyMemRun64) { + .BasePage = ps->block[i].paddr / PAGE_SIZE, + .PageCount = ps->block[i].size / PAGE_SIZE, + }; + } + + h.RequiredDumpSpace += h.PhysicalMemoryBlock.NumberOfPages << PAGE_BITS; + + *hdr = h; + + return 0; +} + +static int fill_context(KDDEBUGGER_DATA64 *kdbg, + struct va_space *vs, QEMU_Elf *qe) +{ + int i; + for (i = 0; i < qe->state_nr; i++) { + uint64_t Prcb; + uint64_t Context; + WinContext ctx; + QEMUCPUState *s = qe->state[i]; + + if (va_space_rw(vs, kdbg->KiProcessorBlock + sizeof(Prcb) * i, + &Prcb, sizeof(Prcb), 0)) { + eprintf("Failed to read CPU #%d PRCB location\n", i); + return 1; + } + + if (va_space_rw(vs, Prcb + kdbg->OffsetPrcbContext, + &Context, sizeof(Context), 0)) { + eprintf("Failed to read CPU #%d ContextFrame location\n", i); + return 1; + } + + printf("Filling context for CPU #%d...\n", i); + win_context_init_from_qemu_cpu_state(&ctx, s); + + if (va_space_rw(vs, Context, &ctx, sizeof(ctx), 1)) { + eprintf("Failed to fill CPU #%d context\n", i); + return 1; + } + } + + return 0; +} + +static int write_dump(struct pa_space *ps, + WinDumpHeader64 *hdr, const char *name) +{ + FILE *dmp_file = fopen(name, "wb"); + size_t i; + + if (!dmp_file) { + eprintf("Failed to open output file \'%s\'\n", name); + return 1; + } + + printf("Writing header to file...\n"); + + if (fwrite(hdr, sizeof(*hdr), 1, dmp_file) != 1) { + eprintf("Failed to write dump header\n"); + fclose(dmp_file); + return 1; + } + + for (i = 0; i < ps->block_nr; i++) { + struct pa_block *b = &ps->block[i]; + + printf("Writing block #%zu/%zu to file...\n", i, ps->block_nr); + if (fwrite(b->addr, b->size, 1, dmp_file) != 1) { + eprintf("Failed to write dump header\n"); + fclose(dmp_file); + return 1; + } + } + + return fclose(dmp_file); +} + +static int pe_get_pdb_symstore_hash(uint64_t base, void *start_addr, + char *hash, struct va_space *vs) +{ + const char e_magic[2] = "MZ"; + const char Signature[4] = "PE\0\0"; + const char sign_rsds[4] = "RSDS"; + IMAGE_DOS_HEADER *dos_hdr = start_addr; + IMAGE_NT_HEADERS64 nt_hdrs; + IMAGE_FILE_HEADER *file_hdr = &nt_hdrs.FileHeader; + IMAGE_OPTIONAL_HEADER64 *opt_hdr = &nt_hdrs.OptionalHeader; + IMAGE_DATA_DIRECTORY *data_dir = nt_hdrs.OptionalHeader.DataDirectory; + IMAGE_DEBUG_DIRECTORY debug_dir; + OMFSignatureRSDS rsds; + char *pdb_name; + size_t pdb_name_sz; + size_t i; + + QEMU_BUILD_BUG_ON(sizeof(*dos_hdr) >= PAGE_SIZE); + + if (memcmp(&dos_hdr->e_magic, e_magic, sizeof(e_magic))) { + return 1; + } + + if (va_space_rw(vs, base + dos_hdr->e_lfanew, + &nt_hdrs, sizeof(nt_hdrs), 0)) { + return 1; + } + + if (memcmp(&nt_hdrs.Signature, Signature, sizeof(Signature)) || + file_hdr->Machine != 0x8664 || opt_hdr->Magic != 0x020b) { + return 1; + } + + printf("Debug Directory RVA = 0x%016x\n", + data_dir[IMAGE_FILE_DEBUG_DIRECTORY].VirtualAddress); + + if (va_space_rw(vs, + base + data_dir[IMAGE_FILE_DEBUG_DIRECTORY].VirtualAddress, + &debug_dir, sizeof(debug_dir), 0)) { + return 1; + } + + if (debug_dir.Type != IMAGE_DEBUG_TYPE_CODEVIEW) { + return 1; + } + + if (va_space_rw(vs, + base + debug_dir.AddressOfRawData, + &rsds, sizeof(rsds), 0)) { + return 1; + } + + printf("CodeView signature is \'%.4s\'\n", rsds.Signature); + + if (memcmp(&rsds.Signature, sign_rsds, sizeof(sign_rsds))) { + return 1; + } + + pdb_name_sz = debug_dir.SizeOfData - sizeof(rsds); + pdb_name = malloc(pdb_name_sz); + if (!pdb_name) { + return 1; + } + + if (va_space_rw(vs, base + debug_dir.AddressOfRawData + + offsetof(OMFSignatureRSDS, name), pdb_name, pdb_name_sz, 0)) { + free(pdb_name); + return 1; + } + + printf("PDB name is \'%s\', \'%s\' expected\n", pdb_name, PDB_NAME); + + if (strcmp(pdb_name, PDB_NAME)) { + eprintf("Unexpected PDB name, it seems the kernel isn't found\n"); + free(pdb_name); + return 1; + } + + free(pdb_name); + + sprintf(hash, "%.08x%.04x%.04x%.02x%.02x", rsds.guid.a, rsds.guid.b, + rsds.guid.c, rsds.guid.d[0], rsds.guid.d[1]); + hash += 20; + for (i = 0; i < 6; i++, hash += 2) { + sprintf(hash, "%.02x", rsds.guid.e[i]); + } + + sprintf(hash, "%.01x", rsds.age); + + return 0; +} + +int main(int argc, char *argv[]) +{ + int err = 0; + QEMU_Elf qemu_elf; + struct pa_space ps; + struct va_space vs; + QEMUCPUState *state; + idt_desc_t first_idt_desc; + uint64_t KernBase; + void *nt_start_addr = NULL; + WinDumpHeader64 header; + char pdb_hash[34]; + char pdb_url[] = SYM_URL_BASE PDB_NAME + "/0123456789ABCDEF0123456789ABCDEFx/" PDB_NAME; + struct pdb_reader pdb; + uint64_t KdDebuggerDataBlock; + KDDEBUGGER_DATA64 *kdbg; + uint64_t KdVersionBlock; + + if (argc != 3) { + eprintf("usage:\n\t%s elf_file dmp_file\n", argv[0]); + return 1; + } + + if (QEMU_Elf_init(&qemu_elf, argv[1])) { + eprintf("Failed to initialize QEMU ELF dump\n"); + return 1; + } + + if (pa_space_create(&ps, &qemu_elf)) { + eprintf("Failed to initialize physical address space\n"); + err = 1; + goto out_elf; + } + + state = qemu_elf.state[0]; + printf("CPU #0 CR3 is 0x%016lx\n", state->cr[3]); + + va_space_create(&vs, &ps, state->cr[3]); + if (fix_dtb(&vs, &qemu_elf)) { + eprintf("Failed to find paging base\n"); + err = 1; + goto out_elf; + } + + printf("CPU #0 IDT is at 0x%016lx\n", state->idt.base); + + if (va_space_rw(&vs, state->idt.base, + &first_idt_desc, sizeof(first_idt_desc), 0)) { + eprintf("Failed to get CPU #0 IDT[0]\n"); + err = 1; + goto out_ps; + } + printf("CPU #0 IDT[0] -> 0x%016lx\n", idt_desc_addr(first_idt_desc)); + + KernBase = idt_desc_addr(first_idt_desc) & ~(PAGE_SIZE - 1); + printf("Searching kernel downwards from 0x%16lx...\n", KernBase); + + for (; KernBase >= 0xfffff78000000000; KernBase -= PAGE_SIZE) { + nt_start_addr = va_space_resolve(&vs, KernBase); + if (!nt_start_addr) { + continue; + } + + if (*(uint16_t *)nt_start_addr == 0x5a4d) { /* MZ */ + break; + } + } + + printf("KernBase = 0x%16lx, signature is \'%.2s\'\n", KernBase, + (char *)nt_start_addr); + + if (pe_get_pdb_symstore_hash(KernBase, nt_start_addr, pdb_hash, &vs)) { + eprintf("Failed to get PDB symbol store hash\n"); + err = 1; + goto out_ps; + } + + sprintf(pdb_url, "%s%s/%s/%s", SYM_URL_BASE, PDB_NAME, pdb_hash, PDB_NAME); + printf("PDB URL is %s\n", pdb_url); + + if (download_url(PDB_NAME, pdb_url)) { + eprintf("Failed to download PDB file\n"); + err = 1; + goto out_ps; + } + + if (pdb_init_from_file(PDB_NAME, &pdb)) { + eprintf("Failed to initialize PDB reader\n"); + err = 1; + goto out_pdb_file; + } + + if (!SYM_RESOLVE(KernBase, &pdb, KdDebuggerDataBlock) || + !SYM_RESOLVE(KernBase, &pdb, KdVersionBlock)) { + err = 1; + goto out_pdb; + } + + kdbg = get_kdbg(KernBase, &pdb, &vs, KdDebuggerDataBlock); + if (!kdbg) { + err = 1; + goto out_pdb; + } + + if (fill_header(&header, &ps, &vs, KdDebuggerDataBlock, kdbg, + KdVersionBlock, qemu_elf.state_nr)) { + err = 1; + goto out_pdb; + } + + if (fill_context(kdbg, &vs, &qemu_elf)) { + err = 1; + goto out_pdb; + } + + if (write_dump(&ps, &header, argv[2])) { + eprintf("Failed to save dump\n"); + err = 1; + goto out_kdbg; + } + +out_kdbg: + free(kdbg); +out_pdb: + pdb_exit(&pdb); +out_pdb_file: + unlink(PDB_NAME); +out_ps: + pa_space_destroy(&ps); +out_elf: + QEMU_Elf_exit(&qemu_elf); + + return err; +} diff --git a/contrib/elf2dmp/pdb.c b/contrib/elf2dmp/pdb.c new file mode 100644 index 0000000000..bcb01b414f --- /dev/null +++ b/contrib/elf2dmp/pdb.c @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * Based on source of Wine project + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "qemu/osdep.h" +#include "pdb.h" +#include "err.h" + +static uint32_t pdb_get_file_size(const struct pdb_reader *r, unsigned idx) +{ + return r->ds.toc->file_size[idx]; +} + +static pdb_seg *get_seg_by_num(struct pdb_reader *r, size_t n) +{ + size_t i = 0; + char *ptr; + + for (ptr = r->segs; (ptr < r->segs + r->segs_size); ) { + i++; + ptr += 8; + if (i == n) { + break; + } + ptr += sizeof(pdb_seg); + } + + return (pdb_seg *)ptr; +} + +uint64_t pdb_find_public_v3_symbol(struct pdb_reader *r, const char *name) +{ + size_t size = pdb_get_file_size(r, r->symbols->gsym_file); + int length; + const union codeview_symbol *sym; + const uint8_t *root = r->modimage; + size_t i; + + for (i = 0; i < size; i += length) { + sym = (const void *)(root + i); + length = sym->generic.len + 2; + + if (!sym->generic.id || length < 4) { + break; + } + + if (sym->generic.id == S_PUB_V3 && + !strcmp(name, sym->public_v3.name)) { + pdb_seg *segment = get_seg_by_num(r, sym->public_v3.segment); + uint32_t sect_rva = segment->dword[1]; + uint64_t rva = sect_rva + sym->public_v3.offset; + + printf("%s: 0x%016x(%d:\'%.8s\') + 0x%08x = 0x%09lx\n", name, + sect_rva, sym->public_v3.segment, + ((char *)segment - 8), sym->public_v3.offset, rva); + return rva; + } + } + + return 0; +} + +uint64_t pdb_resolve(uint64_t img_base, struct pdb_reader *r, const char *name) +{ + uint64_t rva = pdb_find_public_v3_symbol(r, name); + + if (!rva) { + return 0; + } + + return img_base + rva; +} + +static void pdb_reader_ds_exit(struct pdb_reader *r) +{ + free(r->ds.toc); +} + +static void pdb_exit_symbols(struct pdb_reader *r) +{ + free(r->modimage); + free(r->symbols); +} + +static void pdb_exit_segments(struct pdb_reader *r) +{ + free(r->segs); +} + +static void *pdb_ds_read(const PDB_DS_HEADER *header, + const uint32_t *block_list, int size) +{ + int i, nBlocks; + uint8_t *buffer; + + if (!size) { + return NULL; + } + + nBlocks = (size + header->block_size - 1) / header->block_size; + + buffer = malloc(nBlocks * header->block_size); + if (!buffer) { + return NULL; + } + + for (i = 0; i < nBlocks; i++) { + memcpy(buffer + i * header->block_size, (const char *)header + + block_list[i] * header->block_size, header->block_size); + } + + return buffer; +} + +static void *pdb_ds_read_file(struct pdb_reader* r, uint32_t file_number) +{ + const uint32_t *block_list; + uint32_t block_size; + const uint32_t *file_size; + size_t i; + + if (!r->ds.toc || file_number >= r->ds.toc->num_files) { + return NULL; + } + + file_size = r->ds.toc->file_size; + r->file_used[file_number / 32] |= 1 << (file_number % 32); + + if (file_size[file_number] == 0 || file_size[file_number] == 0xFFFFFFFF) { + return NULL; + } + + block_list = file_size + r->ds.toc->num_files; + block_size = r->ds.header->block_size; + + for (i = 0; i < file_number; i++) { + block_list += (file_size[i] + block_size - 1) / block_size; + } + + return pdb_ds_read(r->ds.header, block_list, file_size[file_number]); +} + +static int pdb_init_segments(struct pdb_reader *r) +{ + char *segs; + unsigned stream_idx = r->sidx.segments; + + segs = pdb_ds_read_file(r, stream_idx); + if (!segs) { + return 1; + } + + r->segs = segs; + r->segs_size = pdb_get_file_size(r, stream_idx); + + return 0; +} + +static int pdb_init_symbols(struct pdb_reader *r) +{ + int err = 0; + PDB_SYMBOLS *symbols; + PDB_STREAM_INDEXES *sidx = &r->sidx; + + memset(sidx, -1, sizeof(*sidx)); + + symbols = pdb_ds_read_file(r, 3); + if (!symbols) { + return 1; + } + + r->symbols = symbols; + + if (symbols->stream_index_size != sizeof(PDB_STREAM_INDEXES)) { + err = 1; + goto out_symbols; + } + + memcpy(sidx, (const char *)symbols + sizeof(PDB_SYMBOLS) + + symbols->module_size + symbols->offset_size + + symbols->hash_size + symbols->srcmodule_size + + symbols->pdbimport_size + symbols->unknown2_size, sizeof(*sidx)); + + /* Read global symbol table */ + r->modimage = pdb_ds_read_file(r, symbols->gsym_file); + if (!r->modimage) { + err = 1; + goto out_symbols; + } + + return 0; + +out_symbols: + free(symbols); + + return err; +} + +static int pdb_reader_ds_init(struct pdb_reader *r, PDB_DS_HEADER *hdr) +{ + memset(r->file_used, 0, sizeof(r->file_used)); + r->ds.header = hdr; + r->ds.toc = pdb_ds_read(hdr, (uint32_t *)((uint8_t *)hdr + + hdr->toc_page * hdr->block_size), hdr->toc_size); + + if (!r->ds.toc) { + return 1; + } + + return 0; +} + +static int pdb_reader_init(struct pdb_reader *r, void *data) +{ + int err = 0; + const char pdb7[] = "Microsoft C/C++ MSF 7.00"; + + if (memcmp(data, pdb7, sizeof(pdb7) - 1)) { + return 1; + } + + if (pdb_reader_ds_init(r, data)) { + return 1; + } + + r->ds.root = pdb_ds_read_file(r, 1); + if (!r->ds.root) { + err = 1; + goto out_ds; + } + + if (pdb_init_symbols(r)) { + err = 1; + goto out_root; + } + + if (pdb_init_segments(r)) { + err = 1; + goto out_sym; + } + + return 0; + +out_sym: + pdb_exit_symbols(r); +out_root: + free(r->ds.root); +out_ds: + pdb_reader_ds_exit(r); + + return err; +} + +static void pdb_reader_exit(struct pdb_reader *r) +{ + pdb_exit_segments(r); + pdb_exit_symbols(r); + free(r->ds.root); + pdb_reader_ds_exit(r); +} + +int pdb_init_from_file(const char *name, struct pdb_reader *reader) +{ + int err = 0; + int fd; + void *map; + struct stat st; + + fd = open(name, O_RDONLY, 0); + if (fd == -1) { + eprintf("Failed to open PDB file \'%s\'\n", name); + return 1; + } + reader->fd = fd; + + fstat(fd, &st); + reader->file_size = st.st_size; + + map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + eprintf("Failed to map PDB file\n"); + err = 1; + goto out_fd; + } + + if (pdb_reader_init(reader, map)) { + err = 1; + goto out_unmap; + } + + return 0; + +out_unmap: + munmap(map, st.st_size); +out_fd: + close(fd); + + return err; +} + +void pdb_exit(struct pdb_reader *reader) +{ + munmap(reader->ds.header, reader->file_size); + close(reader->fd); + pdb_reader_exit(reader); +} diff --git a/contrib/elf2dmp/pdb.h b/contrib/elf2dmp/pdb.h new file mode 100644 index 0000000000..4351a2dd61 --- /dev/null +++ b/contrib/elf2dmp/pdb.h @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#ifndef PDB_H +#define PDB_H + +#include +#include + +typedef struct GUID { + unsigned int Data1; + unsigned short Data2; + unsigned short Data3; + unsigned char Data4[8]; +} GUID; + +struct PDB_FILE { + uint32_t size; + uint32_t unknown; +}; + +typedef struct PDB_DS_HEADER { + char signature[32]; + uint32_t block_size; + uint32_t unknown1; + uint32_t num_pages; + uint32_t toc_size; + uint32_t unknown2; + uint32_t toc_page; +} PDB_DS_HEADER; + +typedef struct PDB_DS_TOC { + uint32_t num_files; + uint32_t file_size[1]; +} PDB_DS_TOC; + +typedef struct PDB_DS_ROOT { + uint32_t Version; + uint32_t TimeDateStamp; + uint32_t Age; + GUID guid; + uint32_t cbNames; + char names[1]; +} PDB_DS_ROOT; + +typedef struct PDB_TYPES_OLD { + uint32_t version; + uint16_t first_index; + uint16_t last_index; + uint32_t type_size; + uint16_t file; + uint16_t pad; +} PDB_TYPES_OLD; + +typedef struct PDB_TYPES { + uint32_t version; + uint32_t type_offset; + uint32_t first_index; + uint32_t last_index; + uint32_t type_size; + uint16_t file; + uint16_t pad; + uint32_t hash_size; + uint32_t hash_base; + uint32_t hash_offset; + uint32_t hash_len; + uint32_t search_offset; + uint32_t search_len; + uint32_t unknown_offset; + uint32_t unknown_len; +} PDB_TYPES; + +typedef struct PDB_SYMBOL_RANGE { + uint16_t segment; + uint16_t pad1; + uint32_t offset; + uint32_t size; + uint32_t characteristics; + uint16_t index; + uint16_t pad2; +} PDB_SYMBOL_RANGE; + +typedef struct PDB_SYMBOL_RANGE_EX { + uint16_t segment; + uint16_t pad1; + uint32_t offset; + uint32_t size; + uint32_t characteristics; + uint16_t index; + uint16_t pad2; + uint32_t timestamp; + uint32_t unknown; +} PDB_SYMBOL_RANGE_EX; + +typedef struct PDB_SYMBOL_FILE { + uint32_t unknown1; + PDB_SYMBOL_RANGE range; + uint16_t flag; + uint16_t file; + uint32_t symbol_size; + uint32_t lineno_size; + uint32_t unknown2; + uint32_t nSrcFiles; + uint32_t attribute; + char filename[1]; +} PDB_SYMBOL_FILE; + +typedef struct PDB_SYMBOL_FILE_EX { + uint32_t unknown1; + PDB_SYMBOL_RANGE_EX range; + uint16_t flag; + uint16_t file; + uint32_t symbol_size; + uint32_t lineno_size; + uint32_t unknown2; + uint32_t nSrcFiles; + uint32_t attribute; + uint32_t reserved[2]; + char filename[1]; +} PDB_SYMBOL_FILE_EX; + +typedef struct PDB_SYMBOL_SOURCE { + uint16_t nModules; + uint16_t nSrcFiles; + uint16_t table[1]; +} PDB_SYMBOL_SOURCE; + +typedef struct PDB_SYMBOL_IMPORT { + uint32_t unknown1; + uint32_t unknown2; + uint32_t TimeDateStamp; + uint32_t Age; + char filename[1]; +} PDB_SYMBOL_IMPORT; + +typedef struct PDB_SYMBOLS_OLD { + uint16_t hash1_file; + uint16_t hash2_file; + uint16_t gsym_file; + uint16_t pad; + uint32_t module_size; + uint32_t offset_size; + uint32_t hash_size; + uint32_t srcmodule_size; +} PDB_SYMBOLS_OLD; + +typedef struct PDB_SYMBOLS { + uint32_t signature; + uint32_t version; + uint32_t unknown; + uint32_t hash1_file; + uint32_t hash2_file; + uint16_t gsym_file; + uint16_t unknown1; + uint32_t module_size; + uint32_t offset_size; + uint32_t hash_size; + uint32_t srcmodule_size; + uint32_t pdbimport_size; + uint32_t resvd0; + uint32_t stream_index_size; + uint32_t unknown2_size; + uint16_t resvd3; + uint16_t machine; + uint32_t resvd4; +} PDB_SYMBOLS; + +typedef struct { + uint16_t FPO; + uint16_t unk0; + uint16_t unk1; + uint16_t unk2; + uint16_t unk3; + uint16_t segments; +} PDB_STREAM_INDEXES_OLD; + +typedef struct { + uint16_t FPO; + uint16_t unk0; + uint16_t unk1; + uint16_t unk2; + uint16_t unk3; + uint16_t segments; + uint16_t unk4; + uint16_t unk5; + uint16_t unk6; + uint16_t FPO_EXT; + uint16_t unk7; +} PDB_STREAM_INDEXES; + +union codeview_symbol { + struct { + int16_t len; + int16_t id; + } generic; + + struct { + int16_t len; + int16_t id; + uint32_t symtype; + uint32_t offset; + uint16_t segment; + char name[1]; + } public_v3; +}; + +#define S_PUB_V3 0x110E + +typedef struct pdb_seg { + uint32_t dword[8]; +} __attribute__ ((packed)) pdb_seg; + +#define IMAGE_FILE_MACHINE_I386 0x014c +#define IMAGE_FILE_MACHINE_AMD64 0x8664 + +struct pdb_reader { + int fd; + size_t file_size; + struct { + PDB_DS_HEADER *header; + PDB_DS_TOC *toc; + PDB_DS_ROOT *root; + } ds; + uint32_t file_used[1024]; + PDB_SYMBOLS *symbols; + PDB_STREAM_INDEXES sidx; + uint8_t *modimage; + char *segs; + size_t segs_size; +}; + +int pdb_init_from_file(const char *name, struct pdb_reader *reader); +void pdb_exit(struct pdb_reader *reader); +uint64_t pdb_resolve(uint64_t img_base, struct pdb_reader *r, const char *name); +uint64_t pdb_find_public_v3_symbol(struct pdb_reader *reader, const char *name); + +#endif /* PDB_H */ diff --git a/contrib/elf2dmp/pe.h b/contrib/elf2dmp/pe.h new file mode 100644 index 0000000000..374e06a9c5 --- /dev/null +++ b/contrib/elf2dmp/pe.h @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#ifndef PE_H +#define PE_H + +#include + +typedef struct IMAGE_DOS_HEADER { + uint16_t e_magic; /* 0x00: MZ Header signature */ + uint16_t e_cblp; /* 0x02: Bytes on last page of file */ + uint16_t e_cp; /* 0x04: Pages in file */ + uint16_t e_crlc; /* 0x06: Relocations */ + uint16_t e_cparhdr; /* 0x08: Size of header in paragraphs */ + uint16_t e_minalloc; /* 0x0a: Minimum extra paragraphs needed */ + uint16_t e_maxalloc; /* 0x0c: Maximum extra paragraphs needed */ + uint16_t e_ss; /* 0x0e: Initial (relative) SS value */ + uint16_t e_sp; /* 0x10: Initial SP value */ + uint16_t e_csum; /* 0x12: Checksum */ + uint16_t e_ip; /* 0x14: Initial IP value */ + uint16_t e_cs; /* 0x16: Initial (relative) CS value */ + uint16_t e_lfarlc; /* 0x18: File address of relocation table */ + uint16_t e_ovno; /* 0x1a: Overlay number */ + uint16_t e_res[4]; /* 0x1c: Reserved words */ + uint16_t e_oemid; /* 0x24: OEM identifier (for e_oeminfo) */ + uint16_t e_oeminfo; /* 0x26: OEM information; e_oemid specific */ + uint16_t e_res2[10]; /* 0x28: Reserved words */ + uint32_t e_lfanew; /* 0x3c: Offset to extended header */ +} __attribute__ ((packed)) IMAGE_DOS_HEADER; + +typedef struct IMAGE_FILE_HEADER { + uint16_t Machine; + uint16_t NumberOfSections; + uint32_t TimeDateStamp; + uint32_t PointerToSymbolTable; + uint32_t NumberOfSymbols; + uint16_t SizeOfOptionalHeader; + uint16_t Characteristics; +} __attribute__ ((packed)) IMAGE_FILE_HEADER; + +typedef struct IMAGE_DATA_DIRECTORY { + uint32_t VirtualAddress; + uint32_t Size; +} __attribute__ ((packed)) IMAGE_DATA_DIRECTORY; + +#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 + +typedef struct IMAGE_OPTIONAL_HEADER64 { + uint16_t Magic; /* 0x20b */ + uint8_t MajorLinkerVersion; + uint8_t MinorLinkerVersion; + uint32_t SizeOfCode; + uint32_t SizeOfInitializedData; + uint32_t SizeOfUninitializedData; + uint32_t AddressOfEntryPoint; + uint32_t BaseOfCode; + uint64_t ImageBase; + uint32_t SectionAlignment; + uint32_t FileAlignment; + uint16_t MajorOperatingSystemVersion; + uint16_t MinorOperatingSystemVersion; + uint16_t MajorImageVersion; + uint16_t MinorImageVersion; + uint16_t MajorSubsystemVersion; + uint16_t MinorSubsystemVersion; + uint32_t Win32VersionValue; + uint32_t SizeOfImage; + uint32_t SizeOfHeaders; + uint32_t CheckSum; + uint16_t Subsystem; + uint16_t DllCharacteristics; + uint64_t SizeOfStackReserve; + uint64_t SizeOfStackCommit; + uint64_t SizeOfHeapReserve; + uint64_t SizeOfHeapCommit; + uint32_t LoaderFlags; + uint32_t NumberOfRvaAndSizes; + IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; +} __attribute__ ((packed)) IMAGE_OPTIONAL_HEADER64; + +typedef struct IMAGE_NT_HEADERS64 { + uint32_t Signature; + IMAGE_FILE_HEADER FileHeader; + IMAGE_OPTIONAL_HEADER64 OptionalHeader; +} __attribute__ ((packed)) IMAGE_NT_HEADERS64; + +#define IMAGE_FILE_DEBUG_DIRECTORY 6 + +typedef struct IMAGE_DEBUG_DIRECTORY { + uint32_t Characteristics; + uint32_t TimeDateStamp; + uint16_t MajorVersion; + uint16_t MinorVersion; + uint32_t Type; + uint32_t SizeOfData; + uint32_t AddressOfRawData; + uint32_t PointerToRawData; +} __attribute__ ((packed)) IMAGE_DEBUG_DIRECTORY; + +#define IMAGE_DEBUG_TYPE_CODEVIEW 2 + +typedef struct guid_t { + uint32_t a; + uint16_t b; + uint16_t c; + uint8_t d[2]; + uint8_t e[6]; +} __attribute__ ((packed)) guid_t; + +typedef struct OMFSignatureRSDS { + char Signature[4]; + guid_t guid; + uint32_t age; + char name[]; +} __attribute__ ((packed)) OMFSignatureRSDS; + +#endif /* PE_H */ diff --git a/contrib/elf2dmp/qemu_elf.c b/contrib/elf2dmp/qemu_elf.c new file mode 100644 index 0000000000..e9c0d2534a --- /dev/null +++ b/contrib/elf2dmp/qemu_elf.c @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#include "qemu/osdep.h" +#include "err.h" +#include "qemu_elf.h" + +#define QEMU_NOTE_NAME "QEMU" + +#ifndef ROUND_UP +#define ROUND_UP(n, d) (((n) + (d) - 1) & -(0 ? (n) : (d))) +#endif + +#ifndef DIV_ROUND_UP +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +#endif + +#define ELF_NOTE_SIZE(hdr_size, name_size, desc_size) \ + ((DIV_ROUND_UP((hdr_size), 4) + \ + DIV_ROUND_UP((name_size), 4) + \ + DIV_ROUND_UP((desc_size), 4)) * 4) + +int is_system(QEMUCPUState *s) +{ + return s->gs.base >> 63; +} + +static char *nhdr_get_name(Elf64_Nhdr *nhdr) +{ + return (char *)nhdr + ROUND_UP(sizeof(*nhdr), 4); +} + +static void *nhdr_get_desc(Elf64_Nhdr *nhdr) +{ + return nhdr_get_name(nhdr) + ROUND_UP(nhdr->n_namesz, 4); +} + +static Elf64_Nhdr *nhdr_get_next(Elf64_Nhdr *nhdr) +{ + return (void *)((uint8_t *)nhdr + ELF_NOTE_SIZE(sizeof(*nhdr), + nhdr->n_namesz, nhdr->n_descsz)); +} + +Elf64_Phdr *elf64_getphdr(void *map) +{ + Elf64_Ehdr *ehdr = map; + Elf64_Phdr *phdr = (void *)((uint8_t *)map + ehdr->e_phoff); + + return phdr; +} + +Elf64_Half elf_getphdrnum(void *map) +{ + Elf64_Ehdr *ehdr = map; + + return ehdr->e_phnum; +} + +static int init_states(QEMU_Elf *qe) +{ + Elf64_Phdr *phdr = elf64_getphdr(qe->map); + Elf64_Nhdr *start = (void *)((uint8_t *)qe->map + phdr[0].p_offset); + Elf64_Nhdr *end = (void *)((uint8_t *)start + phdr[0].p_memsz); + Elf64_Nhdr *nhdr; + size_t cpu_nr = 0; + + if (phdr[0].p_type != PT_NOTE) { + eprintf("Failed to find PT_NOTE\n"); + return 1; + } + + qe->has_kernel_gs_base = 1; + + for (nhdr = start; nhdr < end; nhdr = nhdr_get_next(nhdr)) { + if (!strcmp(nhdr_get_name(nhdr), QEMU_NOTE_NAME)) { + QEMUCPUState *state = nhdr_get_desc(nhdr); + + if (state->size < sizeof(*state)) { + eprintf("CPU #%zu: QEMU CPU state size %u doesn't match\n", + cpu_nr, state->size); + /* + * We assume either every QEMU CPU state has KERNEL_GS_BASE or + * no one has. + */ + qe->has_kernel_gs_base = 0; + } + cpu_nr++; + } + } + + printf("%zu CPU states has been found\n", cpu_nr); + + qe->state = malloc(sizeof(*qe->state) * cpu_nr); + if (!qe->state) { + return 1; + } + + cpu_nr = 0; + + for (nhdr = start; nhdr < end; nhdr = nhdr_get_next(nhdr)) { + if (!strcmp(nhdr_get_name(nhdr), QEMU_NOTE_NAME)) { + qe->state[cpu_nr] = nhdr_get_desc(nhdr); + cpu_nr++; + } + } + + qe->state_nr = cpu_nr; + + return 0; +} + +static void exit_states(QEMU_Elf *qe) +{ + free(qe->state); +} + +int QEMU_Elf_init(QEMU_Elf *qe, const char *filename) +{ + int err = 0; + struct stat st; + + qe->fd = open(filename, O_RDONLY, 0); + if (qe->fd == -1) { + eprintf("Failed to open ELF dump file \'%s\'\n", filename); + return 1; + } + + fstat(qe->fd, &st); + qe->size = st.st_size; + + qe->map = mmap(NULL, qe->size, PROT_READ | PROT_WRITE, + MAP_PRIVATE, qe->fd, 0); + if (qe->map == MAP_FAILED) { + eprintf("Failed to map ELF file\n"); + err = 1; + goto out_fd; + } + + if (init_states(qe)) { + eprintf("Failed to extract QEMU CPU states\n"); + err = 1; + goto out_unmap; + } + + return 0; + +out_unmap: + munmap(qe->map, qe->size); +out_fd: + close(qe->fd); + + return err; +} + +void QEMU_Elf_exit(QEMU_Elf *qe) +{ + exit_states(qe); + munmap(qe->map, qe->size); + close(qe->fd); +} diff --git a/contrib/elf2dmp/qemu_elf.h b/contrib/elf2dmp/qemu_elf.h new file mode 100644 index 0000000000..d85d6558fa --- /dev/null +++ b/contrib/elf2dmp/qemu_elf.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 Virtuozzo International GmbH + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * + */ + +#ifndef QEMU_ELF_H +#define QEMU_ELF_H + +#include +#include + +typedef struct QEMUCPUSegment { + uint32_t selector; + uint32_t limit; + uint32_t flags; + uint32_t pad; + uint64_t base; +} QEMUCPUSegment; + +typedef struct QEMUCPUState { + uint32_t version; + uint32_t size; + uint64_t rax, rbx, rcx, rdx, rsi, rdi, rsp, rbp; + uint64_t r8, r9, r10, r11, r12, r13, r14, r15; + uint64_t rip, rflags; + QEMUCPUSegment cs, ds, es, fs, gs, ss; + QEMUCPUSegment ldt, tr, gdt, idt; + uint64_t cr[5]; + uint64_t kernel_gs_base; +} QEMUCPUState; + +int is_system(QEMUCPUState *s); + +typedef struct QEMU_Elf { + int fd; + size_t size; + void *map; + QEMUCPUState **state; + size_t state_nr; + int has_kernel_gs_base; +} QEMU_Elf; + +int QEMU_Elf_init(QEMU_Elf *qe, const char *filename); +void QEMU_Elf_exit(QEMU_Elf *qe); + +Elf64_Phdr *elf64_getphdr(void *map); +Elf64_Half elf_getphdrnum(void *map); + +#endif /* QEMU_ELF_H */