xref: /openbmc/qemu/dump/win_dump.c (revision 9c4888c9)
1 /*
2  * Windows crashdump
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 "qemu/cutils.h"
13 #include "elf.h"
14 #include "exec/hwaddr.h"
15 #include "monitor/monitor.h"
16 #include "sysemu/kvm.h"
17 #include "sysemu/dump.h"
18 #include "sysemu/memory_mapping.h"
19 #include "sysemu/cpus.h"
20 #include "qapi/error.h"
21 #include "qapi/qmp/qerror.h"
22 #include "qemu/error-report.h"
23 #include "hw/misc/vmcoreinfo.h"
24 #include "win_dump.h"
25 
26 static size_t write_run(WinDumpPhyMemRun64 *run, int fd, Error **errp)
27 {
28     void *buf;
29     uint64_t addr = run->BasePage << TARGET_PAGE_BITS;
30     uint64_t size = run->PageCount << TARGET_PAGE_BITS;
31     uint64_t len, l;
32     size_t total = 0;
33 
34     while (size) {
35         len = size;
36 
37         buf = cpu_physical_memory_map(addr, &len, false);
38         if (!buf) {
39             error_setg(errp, "win-dump: failed to map physical range"
40                              " 0x%016" PRIx64 "-0x%016" PRIx64, addr, addr + size - 1);
41             return 0;
42         }
43 
44         l = qemu_write_full(fd, buf, len);
45         cpu_physical_memory_unmap(buf, addr, false, len);
46         if (l != len) {
47             error_setg(errp, QERR_IO_ERROR);
48             return 0;
49         }
50 
51         addr += l;
52         size -= l;
53         total += l;
54     }
55 
56     return total;
57 }
58 
59 static void write_runs(DumpState *s, WinDumpHeader64 *h, Error **errp)
60 {
61     WinDumpPhyMemDesc64 *desc = &h->PhysicalMemoryBlock;
62     WinDumpPhyMemRun64 *run = desc->Run;
63     Error *local_err = NULL;
64     int i;
65 
66     for (i = 0; i < desc->NumberOfRuns; i++) {
67         s->written_size += write_run(run + i, s->fd, &local_err);
68         if (local_err) {
69             error_propagate(errp, local_err);
70             return;
71         }
72     }
73 }
74 
75 static void patch_mm_pfn_database(WinDumpHeader64 *h, Error **errp)
76 {
77     if (cpu_memory_rw_debug(first_cpu,
78             h->KdDebuggerDataBlock + KDBG_MM_PFN_DATABASE_OFFSET64,
79             (uint8_t *)&h->PfnDatabase, sizeof(h->PfnDatabase), 0)) {
80         error_setg(errp, "win-dump: failed to read MmPfnDatabase");
81         return;
82     }
83 }
84 
85 static void patch_bugcheck_data(WinDumpHeader64 *h, Error **errp)
86 {
87     uint64_t KiBugcheckData;
88 
89     if (cpu_memory_rw_debug(first_cpu,
90             h->KdDebuggerDataBlock + KDBG_KI_BUGCHECK_DATA_OFFSET64,
91             (uint8_t *)&KiBugcheckData, sizeof(KiBugcheckData), 0)) {
92         error_setg(errp, "win-dump: failed to read KiBugcheckData");
93         return;
94     }
95 
96     if (cpu_memory_rw_debug(first_cpu,
97             KiBugcheckData,
98             h->BugcheckData, sizeof(h->BugcheckData), 0)) {
99         error_setg(errp, "win-dump: failed to read bugcheck data");
100         return;
101     }
102 
103     /*
104      * If BugcheckCode wasn't saved, we consider guest OS as alive.
105      */
106 
107     if (!h->BugcheckCode) {
108         h->BugcheckCode = LIVE_SYSTEM_DUMP;
109     }
110 }
111 
112 /*
113  * This routine tries to correct mistakes in crashdump header.
114  */
115 static void patch_header(WinDumpHeader64 *h)
116 {
117     Error *local_err = NULL;
118 
119     h->RequiredDumpSpace = sizeof(WinDumpHeader64) +
120             (h->PhysicalMemoryBlock.NumberOfPages << TARGET_PAGE_BITS);
121     h->PhysicalMemoryBlock.unused = 0;
122     h->unused1 = 0;
123 
124     patch_mm_pfn_database(h, &local_err);
125     if (local_err) {
126         warn_report_err(local_err);
127         local_err = NULL;
128     }
129     patch_bugcheck_data(h, &local_err);
130     if (local_err) {
131         warn_report_err(local_err);
132     }
133 }
134 
135 static void check_header(WinDumpHeader64 *h, Error **errp)
136 {
137     const char Signature[] = "PAGE";
138     const char ValidDump[] = "DU64";
139 
140     if (memcmp(h->Signature, Signature, sizeof(h->Signature))) {
141         error_setg(errp, "win-dump: invalid header, expected '%.4s',"
142                          " got '%.4s'", Signature, h->Signature);
143         return;
144     }
145 
146     if (memcmp(h->ValidDump, ValidDump, sizeof(h->ValidDump))) {
147         error_setg(errp, "win-dump: invalid header, expected '%.4s',"
148                          " got '%.4s'", ValidDump, h->ValidDump);
149         return;
150     }
151 }
152 
153 static void check_kdbg(WinDumpHeader64 *h, Error **errp)
154 {
155     const char OwnerTag[] = "KDBG";
156     char read_OwnerTag[4];
157     uint64_t KdDebuggerDataBlock = h->KdDebuggerDataBlock;
158     bool try_fallback = true;
159 
160 try_again:
161     if (cpu_memory_rw_debug(first_cpu,
162             KdDebuggerDataBlock + KDBG_OWNER_TAG_OFFSET64,
163             (uint8_t *)&read_OwnerTag, sizeof(read_OwnerTag), 0)) {
164         error_setg(errp, "win-dump: failed to read OwnerTag");
165         return;
166     }
167 
168     if (memcmp(read_OwnerTag, OwnerTag, sizeof(read_OwnerTag))) {
169         if (try_fallback) {
170             /*
171              * If attempt to use original KDBG failed
172              * (most likely because of its encryption),
173              * we try to use KDBG obtained by guest driver.
174              */
175 
176             KdDebuggerDataBlock = h->BugcheckParameter1;
177             try_fallback = false;
178             goto try_again;
179         } else {
180             error_setg(errp, "win-dump: invalid KDBG OwnerTag,"
181                              " expected '%.4s', got '%.4s'",
182                              OwnerTag, read_OwnerTag);
183             return;
184         }
185     }
186 
187     h->KdDebuggerDataBlock = KdDebuggerDataBlock;
188 }
189 
190 struct saved_context {
191     WinContext ctx;
192     uint64_t addr;
193 };
194 
195 static void patch_and_save_context(WinDumpHeader64 *h,
196                                    struct saved_context *saved_ctx,
197                                    Error **errp)
198 {
199     uint64_t KiProcessorBlock;
200     uint16_t OffsetPrcbContext;
201     CPUState *cpu;
202     int i = 0;
203 
204     if (cpu_memory_rw_debug(first_cpu,
205             h->KdDebuggerDataBlock + KDBG_KI_PROCESSOR_BLOCK_OFFSET64,
206             (uint8_t *)&KiProcessorBlock, sizeof(KiProcessorBlock), 0)) {
207         error_setg(errp, "win-dump: failed to read KiProcessorBlock");
208         return;
209     }
210 
211     if (cpu_memory_rw_debug(first_cpu,
212             h->KdDebuggerDataBlock + KDBG_OFFSET_PRCB_CONTEXT_OFFSET64,
213             (uint8_t *)&OffsetPrcbContext, sizeof(OffsetPrcbContext), 0)) {
214         error_setg(errp, "win-dump: failed to read OffsetPrcbContext");
215         return;
216     }
217 
218     CPU_FOREACH(cpu) {
219         X86CPU *x86_cpu = X86_CPU(cpu);
220         CPUX86State *env = &x86_cpu->env;
221         uint64_t Prcb;
222         uint64_t Context;
223         WinContext ctx;
224 
225         if (cpu_memory_rw_debug(first_cpu,
226                 KiProcessorBlock + i * sizeof(uint64_t),
227                 (uint8_t *)&Prcb, sizeof(Prcb), 0)) {
228             error_setg(errp, "win-dump: failed to read"
229                              " CPU #%d PRCB location", i);
230             return;
231         }
232 
233         if (cpu_memory_rw_debug(first_cpu,
234                 Prcb + OffsetPrcbContext,
235                 (uint8_t *)&Context, sizeof(Context), 0)) {
236             error_setg(errp, "win-dump: failed to read"
237                              " CPU #%d ContextFrame location", i);
238             return;
239         }
240 
241         saved_ctx[i].addr = Context;
242 
243         ctx = (WinContext){
244             .ContextFlags = WIN_CTX_ALL,
245             .MxCsr = env->mxcsr,
246 
247             .SegEs = env->segs[0].selector,
248             .SegCs = env->segs[1].selector,
249             .SegSs = env->segs[2].selector,
250             .SegDs = env->segs[3].selector,
251             .SegFs = env->segs[4].selector,
252             .SegGs = env->segs[5].selector,
253             .EFlags = cpu_compute_eflags(env),
254 
255             .Dr0 = env->dr[0],
256             .Dr1 = env->dr[1],
257             .Dr2 = env->dr[2],
258             .Dr3 = env->dr[3],
259             .Dr6 = env->dr[6],
260             .Dr7 = env->dr[7],
261 
262             .Rax = env->regs[R_EAX],
263             .Rbx = env->regs[R_EBX],
264             .Rcx = env->regs[R_ECX],
265             .Rdx = env->regs[R_EDX],
266             .Rsp = env->regs[R_ESP],
267             .Rbp = env->regs[R_EBP],
268             .Rsi = env->regs[R_ESI],
269             .Rdi = env->regs[R_EDI],
270             .R8  = env->regs[8],
271             .R9  = env->regs[9],
272             .R10 = env->regs[10],
273             .R11 = env->regs[11],
274             .R12 = env->regs[12],
275             .R13 = env->regs[13],
276             .R14 = env->regs[14],
277             .R15 = env->regs[15],
278 
279             .Rip = env->eip,
280             .FltSave = {
281                 .MxCsr = env->mxcsr,
282             },
283         };
284 
285         if (cpu_memory_rw_debug(first_cpu, Context,
286                 (uint8_t *)&saved_ctx[i].ctx, sizeof(WinContext), 0)) {
287             error_setg(errp, "win-dump: failed to save CPU #%d context", i);
288             return;
289         }
290 
291         if (cpu_memory_rw_debug(first_cpu, Context,
292                 (uint8_t *)&ctx, sizeof(WinContext), 1)) {
293             error_setg(errp, "win-dump: failed to write CPU #%d context", i);
294             return;
295         }
296 
297         i++;
298     }
299 }
300 
301 static void restore_context(WinDumpHeader64 *h,
302                             struct saved_context *saved_ctx)
303 {
304     int i;
305 
306     for (i = 0; i < h->NumberProcessors; i++) {
307         if (cpu_memory_rw_debug(first_cpu, saved_ctx[i].addr,
308                 (uint8_t *)&saved_ctx[i].ctx, sizeof(WinContext), 1)) {
309             warn_report("win-dump: failed to restore CPU #%d context", i);
310         }
311     }
312 }
313 
314 void create_win_dump(DumpState *s, Error **errp)
315 {
316     WinDumpHeader64 *h = (WinDumpHeader64 *)(s->guest_note +
317             VMCOREINFO_ELF_NOTE_HDR_SIZE);
318     X86CPU *first_x86_cpu = X86_CPU(first_cpu);
319     uint64_t saved_cr3 = first_x86_cpu->env.cr[3];
320     struct saved_context *saved_ctx = NULL;
321     Error *local_err = NULL;
322 
323     if (s->guest_note_size != sizeof(WinDumpHeader64) +
324             VMCOREINFO_ELF_NOTE_HDR_SIZE) {
325         error_setg(errp, "win-dump: invalid vmcoreinfo note size");
326         return;
327     }
328 
329     check_header(h, &local_err);
330     if (local_err) {
331         error_propagate(errp, local_err);
332         return;
333     }
334 
335     /*
336      * Further access to kernel structures by virtual addresses
337      * should be made from system context.
338      */
339 
340     first_x86_cpu->env.cr[3] = h->DirectoryTableBase;
341 
342     check_kdbg(h, &local_err);
343     if (local_err) {
344         error_propagate(errp, local_err);
345         goto out_cr3;
346     }
347 
348     patch_header(h);
349 
350     saved_ctx = g_new(struct saved_context, h->NumberProcessors);
351 
352     /*
353      * Always patch context because there is no way
354      * to determine if the system-saved context is valid
355      */
356 
357     patch_and_save_context(h, saved_ctx, &local_err);
358     if (local_err) {
359         error_propagate(errp, local_err);
360         goto out_free;
361     }
362 
363     s->total_size = h->RequiredDumpSpace;
364 
365     s->written_size = qemu_write_full(s->fd, h, sizeof(*h));
366     if (s->written_size != sizeof(*h)) {
367         error_setg(errp, QERR_IO_ERROR);
368         goto out_restore;
369     }
370 
371     write_runs(s, h, &local_err);
372     if (local_err) {
373         error_propagate(errp, local_err);
374         goto out_restore;
375     }
376 
377 out_restore:
378     restore_context(h, saved_ctx);
379 out_free:
380     g_free(saved_ctx);
381 out_cr3:
382     first_x86_cpu->env.cr[3] = saved_cr3;
383 
384     return;
385 }
386