xref: /qemu/dump/win_dump.c (revision 21c06f57)
1 /*
2  * Windows crashdump (target specific implementations)
3  *
4  * Copyright (c) 2018 Virtuozzo International GmbH
5  *
6  * This work is licensed under the terms of the GNU GPL, version 2 or later.
7  * See the COPYING file in the top-level directory.
8  *
9  */
10 
11 #include "qemu/osdep.h"
12 #include "sysemu/dump.h"
13 #include "qapi/error.h"
14 #include "qemu/error-report.h"
15 #include "exec/cpu-defs.h"
16 #include "hw/core/cpu.h"
17 #include "qemu/win_dump_defs.h"
18 #include "win_dump.h"
19 #include "cpu.h"
20 
21 #if defined(TARGET_X86_64)
22 
win_dump_available(Error ** errp)23 bool win_dump_available(Error **errp)
24 {
25     return true;
26 }
27 
win_dump_ptr_size(bool x64)28 static size_t win_dump_ptr_size(bool x64)
29 {
30     return x64 ? sizeof(uint64_t) : sizeof(uint32_t);
31 }
32 
33 #define _WIN_DUMP_FIELD(f) (x64 ? h->x64.f : h->x32.f)
34 #define WIN_DUMP_FIELD(field) _WIN_DUMP_FIELD(field)
35 
36 #define _WIN_DUMP_FIELD_PTR(f) (x64 ? (void *)&h->x64.f : (void *)&h->x32.f)
37 #define WIN_DUMP_FIELD_PTR(field) _WIN_DUMP_FIELD_PTR(field)
38 
39 #define _WIN_DUMP_FIELD_SIZE(f) (x64 ? sizeof(h->x64.f) : sizeof(h->x32.f))
40 #define WIN_DUMP_FIELD_SIZE(field) _WIN_DUMP_FIELD_SIZE(field)
41 
win_dump_ctx_size(bool x64)42 static size_t win_dump_ctx_size(bool x64)
43 {
44     return x64 ? sizeof(WinContext64) : sizeof(WinContext32);
45 }
46 
write_run(uint64_t base_page,uint64_t page_count,int fd,Error ** errp)47 static size_t write_run(uint64_t base_page, uint64_t page_count,
48         int fd, Error **errp)
49 {
50     void *buf;
51     uint64_t addr = base_page << TARGET_PAGE_BITS;
52     uint64_t size = page_count << TARGET_PAGE_BITS;
53     uint64_t len, l;
54     int eno;
55     size_t total = 0;
56 
57     while (size) {
58         len = size;
59 
60         buf = cpu_physical_memory_map(addr, &len, false);
61         if (!buf) {
62             error_setg(errp, "win-dump: failed to map physical range"
63                              " 0x%016" PRIx64 "-0x%016" PRIx64, addr, addr + size - 1);
64             return 0;
65         }
66 
67         l = qemu_write_full(fd, buf, len);
68         eno = errno;
69         cpu_physical_memory_unmap(buf, addr, false, len);
70         if (l != len) {
71             error_setg_errno(errp, eno, "win-dump: failed to save memory");
72             return 0;
73         }
74 
75         addr += l;
76         size -= l;
77         total += l;
78     }
79 
80     return total;
81 }
82 
write_runs(DumpState * s,WinDumpHeader * h,bool x64,Error ** errp)83 static void write_runs(DumpState *s, WinDumpHeader *h, bool x64, Error **errp)
84 {
85     uint64_t BasePage, PageCount;
86     Error *local_err = NULL;
87     int i;
88 
89     for (i = 0; i < WIN_DUMP_FIELD(PhysicalMemoryBlock.NumberOfRuns); i++) {
90         BasePage = WIN_DUMP_FIELD(PhysicalMemoryBlock.Run[i].BasePage);
91         PageCount = WIN_DUMP_FIELD(PhysicalMemoryBlock.Run[i].PageCount);
92         s->written_size += write_run(BasePage, PageCount, s->fd, &local_err);
93         if (local_err) {
94             error_propagate(errp, local_err);
95             return;
96         }
97     }
98 }
99 
cpu_read_ptr(bool x64,CPUState * cpu,uint64_t addr,uint64_t * ptr)100 static int cpu_read_ptr(bool x64, CPUState *cpu, uint64_t addr, uint64_t *ptr)
101 {
102     int ret;
103     uint32_t ptr32;
104     uint64_t ptr64;
105 
106     ret = cpu_memory_rw_debug(cpu, addr, x64 ? (void *)&ptr64 : (void *)&ptr32,
107             win_dump_ptr_size(x64), 0);
108 
109     *ptr = x64 ? ptr64 : ptr32;
110 
111     return ret;
112 }
113 
patch_mm_pfn_database(WinDumpHeader * h,bool x64,Error ** errp)114 static void patch_mm_pfn_database(WinDumpHeader *h, bool x64, Error **errp)
115 {
116     if (cpu_memory_rw_debug(first_cpu,
117             WIN_DUMP_FIELD(KdDebuggerDataBlock) + KDBG_MM_PFN_DATABASE_OFFSET,
118             WIN_DUMP_FIELD_PTR(PfnDatabase),
119             WIN_DUMP_FIELD_SIZE(PfnDatabase), 0)) {
120         error_setg(errp, "win-dump: failed to read MmPfnDatabase");
121         return;
122     }
123 }
124 
patch_bugcheck_data(WinDumpHeader * h,bool x64,Error ** errp)125 static void patch_bugcheck_data(WinDumpHeader *h, bool x64, Error **errp)
126 {
127     uint64_t KiBugcheckData;
128 
129     if (cpu_read_ptr(x64, first_cpu,
130             WIN_DUMP_FIELD(KdDebuggerDataBlock) + KDBG_KI_BUGCHECK_DATA_OFFSET,
131             &KiBugcheckData)) {
132         error_setg(errp, "win-dump: failed to read KiBugcheckData");
133         return;
134     }
135 
136     if (cpu_memory_rw_debug(first_cpu, KiBugcheckData,
137             WIN_DUMP_FIELD(BugcheckData),
138             WIN_DUMP_FIELD_SIZE(BugcheckData), 0)) {
139         error_setg(errp, "win-dump: failed to read bugcheck data");
140         return;
141     }
142 
143     /*
144      * If BugcheckCode wasn't saved, we consider guest OS as alive.
145      */
146 
147     if (!WIN_DUMP_FIELD(BugcheckCode)) {
148         *(uint32_t *)WIN_DUMP_FIELD_PTR(BugcheckCode) = LIVE_SYSTEM_DUMP;
149     }
150 }
151 
152 /*
153  * This routine tries to correct mistakes in crashdump header.
154  */
patch_header(WinDumpHeader * h,bool x64)155 static void patch_header(WinDumpHeader *h, bool x64)
156 {
157     Error *local_err = NULL;
158 
159     if (x64) {
160         h->x64.RequiredDumpSpace = sizeof(WinDumpHeader64) +
161             (h->x64.PhysicalMemoryBlock.NumberOfPages << TARGET_PAGE_BITS);
162         h->x64.PhysicalMemoryBlock.unused = 0;
163         h->x64.unused1 = 0;
164     } else {
165         h->x32.RequiredDumpSpace = sizeof(WinDumpHeader32) +
166             (h->x32.PhysicalMemoryBlock.NumberOfPages << TARGET_PAGE_BITS);
167     }
168 
169     patch_mm_pfn_database(h, x64, &local_err);
170     if (local_err) {
171         warn_report_err(local_err);
172         local_err = NULL;
173     }
174     patch_bugcheck_data(h, x64, &local_err);
175     if (local_err) {
176         warn_report_err(local_err);
177     }
178 }
179 
check_header(WinDumpHeader * h,bool * x64,Error ** errp)180 static bool check_header(WinDumpHeader *h, bool *x64, Error **errp)
181 {
182     const char Signature[] = "PAGE";
183 
184     if (memcmp(h->Signature, Signature, sizeof(h->Signature))) {
185         error_setg(errp, "win-dump: invalid header, expected '%.4s',"
186                          " got '%.4s'", Signature, h->Signature);
187         return false;
188     }
189 
190     if (!memcmp(h->ValidDump, "DUMP", sizeof(h->ValidDump))) {
191         *x64 = false;
192     } else if (!memcmp(h->ValidDump, "DU64", sizeof(h->ValidDump))) {
193         *x64 = true;
194     } else {
195         error_setg(errp, "win-dump: invalid header, expected 'DUMP' or 'DU64',"
196                    " got '%.4s'", h->ValidDump);
197         return false;
198     }
199 
200     return true;
201 }
202 
check_kdbg(WinDumpHeader * h,bool x64,Error ** errp)203 static void check_kdbg(WinDumpHeader *h, bool x64, Error **errp)
204 {
205     const char OwnerTag[] = "KDBG";
206     char read_OwnerTag[4];
207     uint64_t KdDebuggerDataBlock = WIN_DUMP_FIELD(KdDebuggerDataBlock);
208     bool try_fallback = true;
209 
210 try_again:
211     if (cpu_memory_rw_debug(first_cpu,
212             KdDebuggerDataBlock + KDBG_OWNER_TAG_OFFSET,
213             (uint8_t *)&read_OwnerTag, sizeof(read_OwnerTag), 0)) {
214         error_setg(errp, "win-dump: failed to read OwnerTag");
215         return;
216     }
217 
218     if (memcmp(read_OwnerTag, OwnerTag, sizeof(read_OwnerTag))) {
219         if (try_fallback) {
220             /*
221              * If attempt to use original KDBG failed
222              * (most likely because of its encryption),
223              * we try to use KDBG obtained by guest driver.
224              */
225 
226             KdDebuggerDataBlock = WIN_DUMP_FIELD(BugcheckParameter1);
227             try_fallback = false;
228             goto try_again;
229         } else {
230             error_setg(errp, "win-dump: invalid KDBG OwnerTag,"
231                              " expected '%.4s', got '%.4s'",
232                              OwnerTag, read_OwnerTag);
233             return;
234         }
235     }
236 
237     if (x64) {
238         h->x64.KdDebuggerDataBlock = KdDebuggerDataBlock;
239     } else {
240         h->x32.KdDebuggerDataBlock = KdDebuggerDataBlock;
241     }
242 }
243 
244 struct saved_context {
245     WinContext ctx;
246     uint64_t addr;
247 };
248 
patch_and_save_context(WinDumpHeader * h,bool x64,struct saved_context * saved_ctx,Error ** errp)249 static void patch_and_save_context(WinDumpHeader *h, bool x64,
250                                    struct saved_context *saved_ctx,
251                                    Error **errp)
252 {
253     uint64_t KdDebuggerDataBlock = WIN_DUMP_FIELD(KdDebuggerDataBlock);
254     uint64_t KiProcessorBlock;
255     uint16_t OffsetPrcbContext;
256     CPUState *cpu;
257     int i = 0;
258 
259     if (cpu_read_ptr(x64, first_cpu,
260             KdDebuggerDataBlock + KDBG_KI_PROCESSOR_BLOCK_OFFSET,
261             &KiProcessorBlock)) {
262         error_setg(errp, "win-dump: failed to read KiProcessorBlock");
263         return;
264     }
265 
266     if (cpu_memory_rw_debug(first_cpu,
267             KdDebuggerDataBlock + KDBG_OFFSET_PRCB_CONTEXT_OFFSET,
268             (uint8_t *)&OffsetPrcbContext, sizeof(OffsetPrcbContext), 0)) {
269         error_setg(errp, "win-dump: failed to read OffsetPrcbContext");
270         return;
271     }
272 
273     CPU_FOREACH(cpu) {
274         X86CPU *x86_cpu = X86_CPU(cpu);
275         CPUX86State *env = &x86_cpu->env;
276         uint64_t Prcb;
277         uint64_t Context;
278         WinContext ctx;
279 
280         if (i >= WIN_DUMP_FIELD(NumberProcessors)) {
281             warn_report("win-dump: number of QEMU CPUs is bigger than"
282                         " NumberProcessors (%u) in guest Windows",
283                         WIN_DUMP_FIELD(NumberProcessors));
284             return;
285         }
286 
287         if (cpu_read_ptr(x64, first_cpu,
288                 KiProcessorBlock + i * win_dump_ptr_size(x64),
289                 &Prcb)) {
290             error_setg(errp, "win-dump: failed to read"
291                              " CPU #%d PRCB location", i);
292             return;
293         }
294 
295         if (cpu_read_ptr(x64, first_cpu,
296                 Prcb + OffsetPrcbContext,
297                 &Context)) {
298             error_setg(errp, "win-dump: failed to read"
299                              " CPU #%d ContextFrame location", i);
300             return;
301         }
302 
303         saved_ctx[i].addr = Context;
304 
305         if (x64) {
306             ctx.x64 = (WinContext64){
307                 .ContextFlags = WIN_CTX64_ALL,
308                 .MxCsr = env->mxcsr,
309 
310                 .SegEs = env->segs[0].selector,
311                 .SegCs = env->segs[1].selector,
312                 .SegSs = env->segs[2].selector,
313                 .SegDs = env->segs[3].selector,
314                 .SegFs = env->segs[4].selector,
315                 .SegGs = env->segs[5].selector,
316                 .EFlags = cpu_compute_eflags(env),
317 
318                 .Dr0 = env->dr[0],
319                 .Dr1 = env->dr[1],
320                 .Dr2 = env->dr[2],
321                 .Dr3 = env->dr[3],
322                 .Dr6 = env->dr[6],
323                 .Dr7 = env->dr[7],
324 
325                 .Rax = env->regs[R_EAX],
326                 .Rbx = env->regs[R_EBX],
327                 .Rcx = env->regs[R_ECX],
328                 .Rdx = env->regs[R_EDX],
329                 .Rsp = env->regs[R_ESP],
330                 .Rbp = env->regs[R_EBP],
331                 .Rsi = env->regs[R_ESI],
332                 .Rdi = env->regs[R_EDI],
333                 .R8  = env->regs[8],
334                 .R9  = env->regs[9],
335                 .R10 = env->regs[10],
336                 .R11 = env->regs[11],
337                 .R12 = env->regs[12],
338                 .R13 = env->regs[13],
339                 .R14 = env->regs[14],
340                 .R15 = env->regs[15],
341 
342                 .Rip = env->eip,
343                 .FltSave = {
344                     .MxCsr = env->mxcsr,
345                 },
346             };
347         } else {
348             ctx.x32 = (WinContext32){
349                 .ContextFlags = WIN_CTX32_FULL | WIN_CTX_DBG,
350 
351                 .SegEs = env->segs[0].selector,
352                 .SegCs = env->segs[1].selector,
353                 .SegSs = env->segs[2].selector,
354                 .SegDs = env->segs[3].selector,
355                 .SegFs = env->segs[4].selector,
356                 .SegGs = env->segs[5].selector,
357                 .EFlags = cpu_compute_eflags(env),
358 
359                 .Dr0 = env->dr[0],
360                 .Dr1 = env->dr[1],
361                 .Dr2 = env->dr[2],
362                 .Dr3 = env->dr[3],
363                 .Dr6 = env->dr[6],
364                 .Dr7 = env->dr[7],
365 
366                 .Eax = env->regs[R_EAX],
367                 .Ebx = env->regs[R_EBX],
368                 .Ecx = env->regs[R_ECX],
369                 .Edx = env->regs[R_EDX],
370                 .Esp = env->regs[R_ESP],
371                 .Ebp = env->regs[R_EBP],
372                 .Esi = env->regs[R_ESI],
373                 .Edi = env->regs[R_EDI],
374 
375                 .Eip = env->eip,
376             };
377         }
378 
379         if (cpu_memory_rw_debug(first_cpu, Context,
380                 &saved_ctx[i].ctx, win_dump_ctx_size(x64), 0)) {
381             error_setg(errp, "win-dump: failed to save CPU #%d context", i);
382             return;
383         }
384 
385         if (cpu_memory_rw_debug(first_cpu, Context,
386                 &ctx, win_dump_ctx_size(x64), 1)) {
387             error_setg(errp, "win-dump: failed to write CPU #%d context", i);
388             return;
389         }
390 
391         i++;
392     }
393 }
394 
restore_context(WinDumpHeader * h,bool x64,struct saved_context * saved_ctx)395 static void restore_context(WinDumpHeader *h, bool x64,
396                             struct saved_context *saved_ctx)
397 {
398     int i;
399 
400     for (i = 0; i < WIN_DUMP_FIELD(NumberProcessors); i++) {
401         if (cpu_memory_rw_debug(first_cpu, saved_ctx[i].addr,
402                 &saved_ctx[i].ctx, win_dump_ctx_size(x64), 1)) {
403             warn_report("win-dump: failed to restore CPU #%d context", i);
404         }
405     }
406 }
407 
create_win_dump(DumpState * s,Error ** errp)408 void create_win_dump(DumpState *s, Error **errp)
409 {
410     WinDumpHeader *h = (void *)(s->guest_note + VMCOREINFO_ELF_NOTE_HDR_SIZE);
411     X86CPU *first_x86_cpu = X86_CPU(first_cpu);
412     uint64_t saved_cr3 = first_x86_cpu->env.cr[3];
413     struct saved_context *saved_ctx = NULL;
414     Error *local_err = NULL;
415     bool x64 = true;
416     size_t hdr_size;
417 
418     if (s->guest_note_size != VMCOREINFO_WIN_DUMP_NOTE_SIZE32 &&
419             s->guest_note_size != VMCOREINFO_WIN_DUMP_NOTE_SIZE64) {
420         error_setg(errp, "win-dump: invalid vmcoreinfo note size");
421         return;
422     }
423 
424     if (!check_header(h, &x64, &local_err)) {
425         error_propagate(errp, local_err);
426         return;
427     }
428 
429     hdr_size = x64 ? sizeof(WinDumpHeader64) : sizeof(WinDumpHeader32);
430 
431     /*
432      * Further access to kernel structures by virtual addresses
433      * should be made from system context.
434      */
435 
436     first_x86_cpu->env.cr[3] = WIN_DUMP_FIELD(DirectoryTableBase);
437 
438     check_kdbg(h, x64, &local_err);
439     if (local_err) {
440         error_propagate(errp, local_err);
441         goto out_cr3;
442     }
443 
444     patch_header(h, x64);
445 
446     saved_ctx = g_new(struct saved_context, WIN_DUMP_FIELD(NumberProcessors));
447 
448     /*
449      * Always patch context because there is no way
450      * to determine if the system-saved context is valid
451      */
452 
453     patch_and_save_context(h, x64, saved_ctx, &local_err);
454     if (local_err) {
455         error_propagate(errp, local_err);
456         goto out_free;
457     }
458 
459     s->total_size = WIN_DUMP_FIELD(RequiredDumpSpace);
460 
461     s->written_size = qemu_write_full(s->fd, h, hdr_size);
462     if (s->written_size != hdr_size) {
463         error_setg_errno(errp, errno, "win-dump: failed to write header");
464         goto out_restore;
465     }
466 
467     write_runs(s, h, x64, &local_err);
468     if (local_err) {
469         error_propagate(errp, local_err);
470         goto out_restore;
471     }
472 
473 out_restore:
474     restore_context(h, x64, saved_ctx);
475 out_free:
476     g_free(saved_ctx);
477 out_cr3:
478     first_x86_cpu->env.cr[3] = saved_cr3;
479 
480     return;
481 }
482 
483 #else /* !TARGET_X86_64 */
484 
win_dump_available(Error ** errp)485 bool win_dump_available(Error **errp)
486 {
487     error_setg(errp, "Windows dump is only available for x86-64");
488 
489     return false;
490 }
491 
create_win_dump(DumpState * s,Error ** errp)492 void create_win_dump(DumpState *s, Error **errp)
493 {
494     win_dump_available(errp);
495 }
496 
497 #endif
498