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