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 <viktor.prutyanov@virtuozzo.com>
Message-Id: <1535546488-30208-3-git-send-email-viktor.prutyanov@virtuozzo.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
This commit is contained in:
Viktor Prutyanov 2018-08-29 15:41:25 +03:00 committed by Paolo Bonzini
parent a52fbc37a4
commit 3fa2d384c2
16 changed files with 2042 additions and 0 deletions

View File

@ -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, $^)

View File

@ -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/

3
configure vendored
View File

@ -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

View File

@ -0,0 +1 @@
elf2dmp-obj-y = main.o addrspace.o download.o pdb.o qemu_elf.o

233
contrib/elf2dmp/addrspace.c Normal file
View File

@ -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;
}

View File

@ -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 */

View File

@ -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 <curl/curl.h>
#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;
}

View File

@ -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 */

13
contrib/elf2dmp/err.h Normal file
View File

@ -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 */

194
contrib/elf2dmp/kdbg.h Normal file
View File

@ -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 */

589
contrib/elf2dmp/main.c Normal file
View File

@ -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;
}

322
contrib/elf2dmp/pdb.c Normal file
View File

@ -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;
}