|
|
9ae3a8 |
From 11d85a217f6b3b15710bbc786adebd943774be09 Mon Sep 17 00:00:00 2001
|
|
|
9ae3a8 |
From: Laszlo Ersek <lersek@redhat.com>
|
|
|
9ae3a8 |
Date: Fri, 20 Feb 2015 09:53:20 +0100
|
|
|
9ae3a8 |
Subject: [PATCH 14/16] Python-lang gdb script to extract x86_64 guest vmcore
|
|
|
9ae3a8 |
from qemu coredump
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
Message-id: <1424426001-3543-2-git-send-email-lersek@redhat.com>
|
|
|
9ae3a8 |
Patchwork-id: 63908
|
|
|
9ae3a8 |
O-Subject: [RHEL-7.2 qemu-kvm PATCH v2 1/2] Python-lang gdb script to extract x86_64 guest vmcore from qemu coredump
|
|
|
9ae3a8 |
Bugzilla: 828493
|
|
|
9ae3a8 |
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
|
|
|
9ae3a8 |
RH-Acked-by: Jeff Nelson <jenelson@redhat.com>
|
|
|
9ae3a8 |
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
When qemu dies unexpectedly, for example in response to an explicit
|
|
|
9ae3a8 |
abort() call, or (more importantly) when an external signal is delivered
|
|
|
9ae3a8 |
to it that results in a coredump, sometimes it is useful to extract the
|
|
|
9ae3a8 |
guest vmcore from the qemu process' memory image. The guest vmcore might
|
|
|
9ae3a8 |
help understand an emulation problem in qemu, or help debug the guest.
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
This script reimplements (and cuts many features of) the
|
|
|
9ae3a8 |
qmp_dump_guest_memory() command in gdb/Python,
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
https://sourceware.org/gdb/current/onlinedocs/gdb/Python-API.html
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
working off the saved memory image of the qemu process. The docstring in
|
|
|
9ae3a8 |
the patch (serving as gdb help text) describes the limitations relative to
|
|
|
9ae3a8 |
the QMP command.
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
Dependencies of qmp_dump_guest_memory() have been reimplemented as needed.
|
|
|
9ae3a8 |
I sought to follow the general structure, sticking to original function
|
|
|
9ae3a8 |
names where possible. However, keeping it simple prevailed in some places.
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
The patch has been tested with a 4 VCPU, 768 MB, RHEL-6.4
|
|
|
9ae3a8 |
(2.6.32-358.el6.x86_64) guest:
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
- The script printed
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
> guest RAM blocks:
|
|
|
9ae3a8 |
> target_start target_end host_addr message count
|
|
|
9ae3a8 |
> ---------------- ---------------- ---------------- ------- -----
|
|
|
9ae3a8 |
> 0000000000000000 00000000000a0000 00007f95d0000000 added 1
|
|
|
9ae3a8 |
> 00000000000a0000 00000000000b0000 00007f960ac00000 added 2
|
|
|
9ae3a8 |
> 00000000000c0000 00000000000ca000 00007f95d00c0000 added 3
|
|
|
9ae3a8 |
> 00000000000ca000 00000000000cd000 00007f95d00ca000 joined 3
|
|
|
9ae3a8 |
> 00000000000cd000 00000000000d0000 00007f95d00cd000 joined 3
|
|
|
9ae3a8 |
> 00000000000d0000 00000000000f0000 00007f95d00d0000 joined 3
|
|
|
9ae3a8 |
> 00000000000f0000 0000000000100000 00007f95d00f0000 joined 3
|
|
|
9ae3a8 |
> 0000000000100000 0000000030000000 00007f95d0100000 joined 3
|
|
|
9ae3a8 |
> 00000000fc000000 00000000fc800000 00007f960ac00000 added 4
|
|
|
9ae3a8 |
> 00000000fffe0000 0000000100000000 00007f9618800000 added 5
|
|
|
9ae3a8 |
> dumping range at 00007f95d0000000 for length 00000000000a0000
|
|
|
9ae3a8 |
> dumping range at 00007f960ac00000 for length 0000000000010000
|
|
|
9ae3a8 |
> dumping range at 00007f95d00c0000 for length 000000002ff40000
|
|
|
9ae3a8 |
> dumping range at 00007f960ac00000 for length 0000000000800000
|
|
|
9ae3a8 |
> dumping range at 00007f9618800000 for length 0000000000020000
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
- The vmcore was checked with "readelf", comparing the results against a
|
|
|
9ae3a8 |
vmcore written by qmp_dump_guest_memory():
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
> --- theirs 2013-09-12 17:38:59.797289404 +0200
|
|
|
9ae3a8 |
> +++ mine 2013-09-12 17:39:03.820289404 +0200
|
|
|
9ae3a8 |
> @@ -27,16 +27,16 @@
|
|
|
9ae3a8 |
> Type Offset VirtAddr PhysAddr
|
|
|
9ae3a8 |
> FileSiz MemSiz Flags Align
|
|
|
9ae3a8 |
> NOTE 0x0000000000000190 0x0000000000000000 0x0000000000000000
|
|
|
9ae3a8 |
> - 0x0000000000000ca0 0x0000000000000ca0 0
|
|
|
9ae3a8 |
> - LOAD 0x0000000000000e30 0x0000000000000000 0x0000000000000000
|
|
|
9ae3a8 |
> + 0x000000000000001c 0x000000000000001c 0
|
|
|
9ae3a8 |
> + LOAD 0x00000000000001ac 0x0000000000000000 0x0000000000000000
|
|
|
9ae3a8 |
> 0x00000000000a0000 0x00000000000a0000 0
|
|
|
9ae3a8 |
> - LOAD 0x00000000000a0e30 0x0000000000000000 0x00000000000a0000
|
|
|
9ae3a8 |
> + LOAD 0x00000000000a01ac 0x0000000000000000 0x00000000000a0000
|
|
|
9ae3a8 |
> 0x0000000000010000 0x0000000000010000 0
|
|
|
9ae3a8 |
> - LOAD 0x00000000000b0e30 0x0000000000000000 0x00000000000c0000
|
|
|
9ae3a8 |
> + LOAD 0x00000000000b01ac 0x0000000000000000 0x00000000000c0000
|
|
|
9ae3a8 |
> 0x000000002ff40000 0x000000002ff40000 0
|
|
|
9ae3a8 |
> - LOAD 0x000000002fff0e30 0x0000000000000000 0x00000000fc000000
|
|
|
9ae3a8 |
> + LOAD 0x000000002fff01ac 0x0000000000000000 0x00000000fc000000
|
|
|
9ae3a8 |
> 0x0000000000800000 0x0000000000800000 0
|
|
|
9ae3a8 |
> - LOAD 0x00000000307f0e30 0x0000000000000000 0x00000000fffe0000
|
|
|
9ae3a8 |
> + LOAD 0x00000000307f01ac 0x0000000000000000 0x00000000fffe0000
|
|
|
9ae3a8 |
> 0x0000000000020000 0x0000000000020000 0
|
|
|
9ae3a8 |
>
|
|
|
9ae3a8 |
> There is no dynamic section in this file.
|
|
|
9ae3a8 |
> @@ -47,13 +47,6 @@
|
|
|
9ae3a8 |
>
|
|
|
9ae3a8 |
> No version information found in this file.
|
|
|
9ae3a8 |
>
|
|
|
9ae3a8 |
> -Notes at offset 0x00000190 with length 0x00000ca0:
|
|
|
9ae3a8 |
> +Notes at offset 0x00000190 with length 0x0000001c:
|
|
|
9ae3a8 |
> Owner Data size Description
|
|
|
9ae3a8 |
> - CORE 0x00000150 NT_PRSTATUS (prstatus structure)
|
|
|
9ae3a8 |
> - CORE 0x00000150 NT_PRSTATUS (prstatus structure)
|
|
|
9ae3a8 |
> - CORE 0x00000150 NT_PRSTATUS (prstatus structure)
|
|
|
9ae3a8 |
> - CORE 0x00000150 NT_PRSTATUS (prstatus structure)
|
|
|
9ae3a8 |
> - QEMU 0x000001b0 Unknown note type: (0x00000000)
|
|
|
9ae3a8 |
> - QEMU 0x000001b0 Unknown note type: (0x00000000)
|
|
|
9ae3a8 |
> - QEMU 0x000001b0 Unknown note type: (0x00000000)
|
|
|
9ae3a8 |
> - QEMU 0x000001b0 Unknown note type: (0x00000000)
|
|
|
9ae3a8 |
> + NONE 0x00000005 Unknown note type: (0x00000000)
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
- The vmcore was checked with "crash" too, again comparing the results
|
|
|
9ae3a8 |
against a vmcore written by qmp_dump_guest_memory():
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
> --- guest.vmcore.log2 2013-09-12 17:52:27.074289201 +0200
|
|
|
9ae3a8 |
> +++ example.dump.log2 2013-09-12 17:52:15.904289203 +0200
|
|
|
9ae3a8 |
> @@ -22,11 +22,11 @@
|
|
|
9ae3a8 |
> This GDB was configured as "x86_64-unknown-linux-gnu"...
|
|
|
9ae3a8 |
>
|
|
|
9ae3a8 |
> KERNEL: /usr/lib/debug/lib/modules/2.6.32-358.el6.x86_64/vmlinux
|
|
|
9ae3a8 |
> - DUMPFILE: /home/lacos/tmp/guest.vmcore
|
|
|
9ae3a8 |
> + DUMPFILE: /home/lacos/tmp/example.dump
|
|
|
9ae3a8 |
> CPUS: 4
|
|
|
9ae3a8 |
> - DATE: Thu Sep 12 17:16:11 2013
|
|
|
9ae3a8 |
> - UPTIME: 00:01:09
|
|
|
9ae3a8 |
> -LOAD AVERAGE: 0.07, 0.03, 0.00
|
|
|
9ae3a8 |
> + DATE: Thu Sep 12 17:17:41 2013
|
|
|
9ae3a8 |
> + UPTIME: 00:00:38
|
|
|
9ae3a8 |
> +LOAD AVERAGE: 0.18, 0.05, 0.01
|
|
|
9ae3a8 |
> TASKS: 130
|
|
|
9ae3a8 |
> NODENAME: localhost.localdomain
|
|
|
9ae3a8 |
> RELEASE: 2.6.32-358.el6.x86_64
|
|
|
9ae3a8 |
> @@ -38,12 +38,12 @@
|
|
|
9ae3a8 |
> COMMAND: "swapper"
|
|
|
9ae3a8 |
> TASK: ffffffff81a8d020 (1 of 4) [THREAD_INFO: ffffffff81a00000]
|
|
|
9ae3a8 |
> CPU: 0
|
|
|
9ae3a8 |
> - STATE: TASK_RUNNING (PANIC)
|
|
|
9ae3a8 |
> + STATE: TASK_RUNNING (ACTIVE)
|
|
|
9ae3a8 |
> + WARNING: panic task not found
|
|
|
9ae3a8 |
>
|
|
|
9ae3a8 |
> crash> bt
|
|
|
9ae3a8 |
> PID: 0 TASK: ffffffff81a8d020 CPU: 0 COMMAND: "swapper"
|
|
|
9ae3a8 |
> - #0 [ffffffff81a01ed0] default_idle at ffffffff8101495d
|
|
|
9ae3a8 |
> - #1 [ffffffff81a01ef0] cpu_idle at ffffffff81009fc6
|
|
|
9ae3a8 |
> + #0 [ffffffff81a01ef0] cpu_idle at ffffffff81009fc6
|
|
|
9ae3a8 |
> crash> task ffffffff81a8d020
|
|
|
9ae3a8 |
> PID: 0 TASK: ffffffff81a8d020 CPU: 0 COMMAND: "swapper"
|
|
|
9ae3a8 |
> struct task_struct {
|
|
|
9ae3a8 |
> @@ -75,7 +75,7 @@
|
|
|
9ae3a8 |
> prev = 0xffffffff81a8d080
|
|
|
9ae3a8 |
> },
|
|
|
9ae3a8 |
> on_rq = 0,
|
|
|
9ae3a8 |
> - exec_start = 8618466836,
|
|
|
9ae3a8 |
> + exec_start = 7469214014,
|
|
|
9ae3a8 |
> sum_exec_runtime = 0,
|
|
|
9ae3a8 |
> vruntime = 0,
|
|
|
9ae3a8 |
> prev_sum_exec_runtime = 0,
|
|
|
9ae3a8 |
> @@ -149,7 +149,7 @@
|
|
|
9ae3a8 |
> },
|
|
|
9ae3a8 |
> tasks = {
|
|
|
9ae3a8 |
> next = 0xffff88002d621948,
|
|
|
9ae3a8 |
> - prev = 0xffff880029618f28
|
|
|
9ae3a8 |
> + prev = 0xffff880023b74488
|
|
|
9ae3a8 |
> },
|
|
|
9ae3a8 |
> pushable_tasks = {
|
|
|
9ae3a8 |
> prio = 140,
|
|
|
9ae3a8 |
> @@ -165,7 +165,7 @@
|
|
|
9ae3a8 |
> }
|
|
|
9ae3a8 |
> },
|
|
|
9ae3a8 |
> mm = 0x0,
|
|
|
9ae3a8 |
> - active_mm = 0xffff88002929b780,
|
|
|
9ae3a8 |
> + active_mm = 0xffff8800297eb980,
|
|
|
9ae3a8 |
> exit_state = 0,
|
|
|
9ae3a8 |
> exit_code = 0,
|
|
|
9ae3a8 |
> exit_signal = 0,
|
|
|
9ae3a8 |
> @@ -177,7 +177,7 @@
|
|
|
9ae3a8 |
> sched_reset_on_fork = 0,
|
|
|
9ae3a8 |
> pid = 0,
|
|
|
9ae3a8 |
> tgid = 0,
|
|
|
9ae3a8 |
> - stack_canary = 2483693585637059287,
|
|
|
9ae3a8 |
> + stack_canary = 7266362296181431986,
|
|
|
9ae3a8 |
> real_parent = 0xffffffff81a8d020,
|
|
|
9ae3a8 |
> parent = 0xffffffff81a8d020,
|
|
|
9ae3a8 |
> children = {
|
|
|
9ae3a8 |
> @@ -224,14 +224,14 @@
|
|
|
9ae3a8 |
> set_child_tid = 0x0,
|
|
|
9ae3a8 |
> clear_child_tid = 0x0,
|
|
|
9ae3a8 |
> utime = 0,
|
|
|
9ae3a8 |
> - stime = 3,
|
|
|
9ae3a8 |
> + stime = 2,
|
|
|
9ae3a8 |
> utimescaled = 0,
|
|
|
9ae3a8 |
> - stimescaled = 3,
|
|
|
9ae3a8 |
> + stimescaled = 2,
|
|
|
9ae3a8 |
> gtime = 0,
|
|
|
9ae3a8 |
> prev_utime = 0,
|
|
|
9ae3a8 |
> prev_stime = 0,
|
|
|
9ae3a8 |
> nvcsw = 0,
|
|
|
9ae3a8 |
> - nivcsw = 1000,
|
|
|
9ae3a8 |
> + nivcsw = 1764,
|
|
|
9ae3a8 |
> start_time = {
|
|
|
9ae3a8 |
> tv_sec = 0,
|
|
|
9ae3a8 |
> tv_nsec = 0
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
- <name_dropping>I asked for Dave Anderson's help with verifying the
|
|
|
9ae3a8 |
extracted vmcore, and his comments make me think I should post
|
|
|
9ae3a8 |
this.</name_dropping>
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
|
|
|
9ae3a8 |
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
|
|
|
9ae3a8 |
(cherry picked from commit 3e16d14fd93ca6059134ba6b4f65c1c3e4cd3a18)
|
|
|
9ae3a8 |
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
|
9ae3a8 |
---
|
|
|
9ae3a8 |
scripts/dump-guest-memory.py | 339 +++++++++++++++++++++++++++++++++++++++++++
|
|
|
9ae3a8 |
1 file changed, 339 insertions(+)
|
|
|
9ae3a8 |
create mode 100644 scripts/dump-guest-memory.py
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
diff --git a/scripts/dump-guest-memory.py b/scripts/dump-guest-memory.py
|
|
|
9ae3a8 |
new file mode 100644
|
|
|
9ae3a8 |
index 0000000..1ed8b67
|
|
|
9ae3a8 |
--- /dev/null
|
|
|
9ae3a8 |
+++ b/scripts/dump-guest-memory.py
|
|
|
9ae3a8 |
@@ -0,0 +1,339 @@
|
|
|
9ae3a8 |
+# This python script adds a new gdb command, "dump-guest-memory". It
|
|
|
9ae3a8 |
+# should be loaded with "source dump-guest-memory.py" at the (gdb)
|
|
|
9ae3a8 |
+# prompt.
|
|
|
9ae3a8 |
+#
|
|
|
9ae3a8 |
+# Copyright (C) 2013, Red Hat, Inc.
|
|
|
9ae3a8 |
+#
|
|
|
9ae3a8 |
+# Authors:
|
|
|
9ae3a8 |
+# Laszlo Ersek <lersek@redhat.com>
|
|
|
9ae3a8 |
+#
|
|
|
9ae3a8 |
+# This work is licensed under the terms of the GNU GPL, version 2 or later. See
|
|
|
9ae3a8 |
+# the COPYING file in the top-level directory.
|
|
|
9ae3a8 |
+#
|
|
|
9ae3a8 |
+# The leading docstring doesn't have idiomatic Python formatting. It is
|
|
|
9ae3a8 |
+# printed by gdb's "help" command (the first line is printed in the
|
|
|
9ae3a8 |
+# "help data" summary), and it should match how other help texts look in
|
|
|
9ae3a8 |
+# gdb.
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+import struct
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+class DumpGuestMemory(gdb.Command):
|
|
|
9ae3a8 |
+ """Extract guest vmcore from qemu process coredump.
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+The sole argument is FILE, identifying the target file to write the
|
|
|
9ae3a8 |
+guest vmcore to.
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+This GDB command reimplements the dump-guest-memory QMP command in
|
|
|
9ae3a8 |
+python, using the representation of guest memory as captured in the qemu
|
|
|
9ae3a8 |
+coredump. The qemu process that has been dumped must have had the
|
|
|
9ae3a8 |
+command line option "-machine dump-guest-core=on".
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+For simplicity, the "paging", "begin" and "end" parameters of the QMP
|
|
|
9ae3a8 |
+command are not supported -- no attempt is made to get the guest's
|
|
|
9ae3a8 |
+internal paging structures (ie. paging=false is hard-wired), and guest
|
|
|
9ae3a8 |
+memory is always fully dumped.
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+Only x86_64 guests are supported.
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+The CORE/NT_PRSTATUS and QEMU notes (that is, the VCPUs' statuses) are
|
|
|
9ae3a8 |
+not written to the vmcore. Preparing these would require context that is
|
|
|
9ae3a8 |
+only present in the KVM host kernel module when the guest is alive. A
|
|
|
9ae3a8 |
+fake ELF note is written instead, only to keep the ELF parser of "crash"
|
|
|
9ae3a8 |
+happy.
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+Dependent on how busted the qemu process was at the time of the
|
|
|
9ae3a8 |
+coredump, this command might produce unpredictable results. If qemu
|
|
|
9ae3a8 |
+deliberately called abort(), or it was dumped in response to a signal at
|
|
|
9ae3a8 |
+a halfway fortunate point, then its coredump should be in reasonable
|
|
|
9ae3a8 |
+shape and this command should mostly work."""
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ TARGET_PAGE_SIZE = 0x1000
|
|
|
9ae3a8 |
+ TARGET_PAGE_MASK = 0xFFFFFFFFFFFFF000
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # Various ELF constants
|
|
|
9ae3a8 |
+ EM_X86_64 = 62 # AMD x86-64 target machine
|
|
|
9ae3a8 |
+ ELFDATA2LSB = 1 # little endian
|
|
|
9ae3a8 |
+ ELFCLASS64 = 2
|
|
|
9ae3a8 |
+ ELFMAG = "\x7FELF"
|
|
|
9ae3a8 |
+ EV_CURRENT = 1
|
|
|
9ae3a8 |
+ ET_CORE = 4
|
|
|
9ae3a8 |
+ PT_LOAD = 1
|
|
|
9ae3a8 |
+ PT_NOTE = 4
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # Special value for e_phnum. This indicates that the real number of
|
|
|
9ae3a8 |
+ # program headers is too large to fit into e_phnum. Instead the real
|
|
|
9ae3a8 |
+ # value is in the field sh_info of section 0.
|
|
|
9ae3a8 |
+ PN_XNUM = 0xFFFF
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # Format strings for packing and header size calculation.
|
|
|
9ae3a8 |
+ ELF64_EHDR = ("4s" # e_ident/magic
|
|
|
9ae3a8 |
+ "B" # e_ident/class
|
|
|
9ae3a8 |
+ "B" # e_ident/data
|
|
|
9ae3a8 |
+ "B" # e_ident/version
|
|
|
9ae3a8 |
+ "B" # e_ident/osabi
|
|
|
9ae3a8 |
+ "8s" # e_ident/pad
|
|
|
9ae3a8 |
+ "H" # e_type
|
|
|
9ae3a8 |
+ "H" # e_machine
|
|
|
9ae3a8 |
+ "I" # e_version
|
|
|
9ae3a8 |
+ "Q" # e_entry
|
|
|
9ae3a8 |
+ "Q" # e_phoff
|
|
|
9ae3a8 |
+ "Q" # e_shoff
|
|
|
9ae3a8 |
+ "I" # e_flags
|
|
|
9ae3a8 |
+ "H" # e_ehsize
|
|
|
9ae3a8 |
+ "H" # e_phentsize
|
|
|
9ae3a8 |
+ "H" # e_phnum
|
|
|
9ae3a8 |
+ "H" # e_shentsize
|
|
|
9ae3a8 |
+ "H" # e_shnum
|
|
|
9ae3a8 |
+ "H" # e_shstrndx
|
|
|
9ae3a8 |
+ )
|
|
|
9ae3a8 |
+ ELF64_PHDR = ("I" # p_type
|
|
|
9ae3a8 |
+ "I" # p_flags
|
|
|
9ae3a8 |
+ "Q" # p_offset
|
|
|
9ae3a8 |
+ "Q" # p_vaddr
|
|
|
9ae3a8 |
+ "Q" # p_paddr
|
|
|
9ae3a8 |
+ "Q" # p_filesz
|
|
|
9ae3a8 |
+ "Q" # p_memsz
|
|
|
9ae3a8 |
+ "Q" # p_align
|
|
|
9ae3a8 |
+ )
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def __init__(self):
|
|
|
9ae3a8 |
+ super(DumpGuestMemory, self).__init__("dump-guest-memory",
|
|
|
9ae3a8 |
+ gdb.COMMAND_DATA,
|
|
|
9ae3a8 |
+ gdb.COMPLETE_FILENAME)
|
|
|
9ae3a8 |
+ self.uintptr_t = gdb.lookup_type("uintptr_t")
|
|
|
9ae3a8 |
+ self.elf64_ehdr_le = struct.Struct("<%s" % self.ELF64_EHDR)
|
|
|
9ae3a8 |
+ self.elf64_phdr_le = struct.Struct("<%s" % self.ELF64_PHDR)
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def int128_get64(self, val):
|
|
|
9ae3a8 |
+ assert (val["hi"] == 0)
|
|
|
9ae3a8 |
+ return val["lo"]
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def qtailq_foreach(self, head, field_str):
|
|
|
9ae3a8 |
+ var_p = head["tqh_first"]
|
|
|
9ae3a8 |
+ while (var_p != 0):
|
|
|
9ae3a8 |
+ var = var_p.dereference()
|
|
|
9ae3a8 |
+ yield var
|
|
|
9ae3a8 |
+ var_p = var[field_str]["tqe_next"]
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def qemu_get_ram_block(self, ram_addr):
|
|
|
9ae3a8 |
+ ram_blocks = gdb.parse_and_eval("ram_list.blocks")
|
|
|
9ae3a8 |
+ for block in self.qtailq_foreach(ram_blocks, "next"):
|
|
|
9ae3a8 |
+ if (ram_addr - block["offset"] < block["length"]):
|
|
|
9ae3a8 |
+ return block
|
|
|
9ae3a8 |
+ raise gdb.GdbError("Bad ram offset %x" % ram_addr)
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def qemu_get_ram_ptr(self, ram_addr):
|
|
|
9ae3a8 |
+ block = self.qemu_get_ram_block(ram_addr)
|
|
|
9ae3a8 |
+ return block["host"] + (ram_addr - block["offset"])
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def memory_region_get_ram_ptr(self, mr):
|
|
|
9ae3a8 |
+ if (mr["alias"] != 0):
|
|
|
9ae3a8 |
+ return (self.memory_region_get_ram_ptr(mr["alias"].dereference()) +
|
|
|
9ae3a8 |
+ mr["alias_offset"])
|
|
|
9ae3a8 |
+ return self.qemu_get_ram_ptr(mr["ram_addr"] & self.TARGET_PAGE_MASK)
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def guest_phys_blocks_init(self):
|
|
|
9ae3a8 |
+ self.guest_phys_blocks = []
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def guest_phys_blocks_append(self):
|
|
|
9ae3a8 |
+ print "guest RAM blocks:"
|
|
|
9ae3a8 |
+ print ("target_start target_end host_addr message "
|
|
|
9ae3a8 |
+ "count")
|
|
|
9ae3a8 |
+ print ("---------------- ---------------- ---------------- ------- "
|
|
|
9ae3a8 |
+ "-----")
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ current_map_p = gdb.parse_and_eval("address_space_memory.current_map")
|
|
|
9ae3a8 |
+ current_map = current_map_p.dereference()
|
|
|
9ae3a8 |
+ for cur in range(current_map["nr"]):
|
|
|
9ae3a8 |
+ flat_range = (current_map["ranges"] + cur).dereference()
|
|
|
9ae3a8 |
+ mr = flat_range["mr"].dereference()
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # we only care about RAM
|
|
|
9ae3a8 |
+ if (not mr["ram"]):
|
|
|
9ae3a8 |
+ continue
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ section_size = self.int128_get64(flat_range["addr"]["size"])
|
|
|
9ae3a8 |
+ target_start = self.int128_get64(flat_range["addr"]["start"])
|
|
|
9ae3a8 |
+ target_end = target_start + section_size
|
|
|
9ae3a8 |
+ host_addr = (self.memory_region_get_ram_ptr(mr) +
|
|
|
9ae3a8 |
+ flat_range["offset_in_region"])
|
|
|
9ae3a8 |
+ predecessor = None
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # find continuity in guest physical address space
|
|
|
9ae3a8 |
+ if (len(self.guest_phys_blocks) > 0):
|
|
|
9ae3a8 |
+ predecessor = self.guest_phys_blocks[-1]
|
|
|
9ae3a8 |
+ predecessor_size = (predecessor["target_end"] -
|
|
|
9ae3a8 |
+ predecessor["target_start"])
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # the memory API guarantees monotonically increasing
|
|
|
9ae3a8 |
+ # traversal
|
|
|
9ae3a8 |
+ assert (predecessor["target_end"] <= target_start)
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # we want continuity in both guest-physical and
|
|
|
9ae3a8 |
+ # host-virtual memory
|
|
|
9ae3a8 |
+ if (predecessor["target_end"] < target_start or
|
|
|
9ae3a8 |
+ predecessor["host_addr"] + predecessor_size != host_addr):
|
|
|
9ae3a8 |
+ predecessor = None
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ if (predecessor is None):
|
|
|
9ae3a8 |
+ # isolated mapping, add it to the list
|
|
|
9ae3a8 |
+ self.guest_phys_blocks.append({"target_start": target_start,
|
|
|
9ae3a8 |
+ "target_end" : target_end,
|
|
|
9ae3a8 |
+ "host_addr" : host_addr})
|
|
|
9ae3a8 |
+ message = "added"
|
|
|
9ae3a8 |
+ else:
|
|
|
9ae3a8 |
+ # expand predecessor until @target_end; predecessor's
|
|
|
9ae3a8 |
+ # start doesn't change
|
|
|
9ae3a8 |
+ predecessor["target_end"] = target_end
|
|
|
9ae3a8 |
+ message = "joined"
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ print ("%016x %016x %016x %-7s %5u" %
|
|
|
9ae3a8 |
+ (target_start, target_end, host_addr.cast(self.uintptr_t),
|
|
|
9ae3a8 |
+ message, len(self.guest_phys_blocks)))
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def cpu_get_dump_info(self):
|
|
|
9ae3a8 |
+ # We can't synchronize the registers with KVM post-mortem, and
|
|
|
9ae3a8 |
+ # the bits in (first_x86_cpu->env.hflags) seem to be stale; they
|
|
|
9ae3a8 |
+ # may not reflect long mode for example. Hence just assume the
|
|
|
9ae3a8 |
+ # most common values. This also means that instruction pointer
|
|
|
9ae3a8 |
+ # etc. will be bogus in the dump, but at least the RAM contents
|
|
|
9ae3a8 |
+ # should be valid.
|
|
|
9ae3a8 |
+ self.dump_info = {"d_machine": self.EM_X86_64,
|
|
|
9ae3a8 |
+ "d_endian" : self.ELFDATA2LSB,
|
|
|
9ae3a8 |
+ "d_class" : self.ELFCLASS64}
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def encode_elf64_ehdr_le(self):
|
|
|
9ae3a8 |
+ return self.elf64_ehdr_le.pack(
|
|
|
9ae3a8 |
+ self.ELFMAG, # e_ident/magic
|
|
|
9ae3a8 |
+ self.dump_info["d_class"], # e_ident/class
|
|
|
9ae3a8 |
+ self.dump_info["d_endian"], # e_ident/data
|
|
|
9ae3a8 |
+ self.EV_CURRENT, # e_ident/version
|
|
|
9ae3a8 |
+ 0, # e_ident/osabi
|
|
|
9ae3a8 |
+ "", # e_ident/pad
|
|
|
9ae3a8 |
+ self.ET_CORE, # e_type
|
|
|
9ae3a8 |
+ self.dump_info["d_machine"], # e_machine
|
|
|
9ae3a8 |
+ self.EV_CURRENT, # e_version
|
|
|
9ae3a8 |
+ 0, # e_entry
|
|
|
9ae3a8 |
+ self.elf64_ehdr_le.size, # e_phoff
|
|
|
9ae3a8 |
+ 0, # e_shoff
|
|
|
9ae3a8 |
+ 0, # e_flags
|
|
|
9ae3a8 |
+ self.elf64_ehdr_le.size, # e_ehsize
|
|
|
9ae3a8 |
+ self.elf64_phdr_le.size, # e_phentsize
|
|
|
9ae3a8 |
+ self.phdr_num, # e_phnum
|
|
|
9ae3a8 |
+ 0, # e_shentsize
|
|
|
9ae3a8 |
+ 0, # e_shnum
|
|
|
9ae3a8 |
+ 0 # e_shstrndx
|
|
|
9ae3a8 |
+ )
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def encode_elf64_note_le(self):
|
|
|
9ae3a8 |
+ return self.elf64_phdr_le.pack(self.PT_NOTE, # p_type
|
|
|
9ae3a8 |
+ 0, # p_flags
|
|
|
9ae3a8 |
+ (self.memory_offset -
|
|
|
9ae3a8 |
+ len(self.note)), # p_offset
|
|
|
9ae3a8 |
+ 0, # p_vaddr
|
|
|
9ae3a8 |
+ 0, # p_paddr
|
|
|
9ae3a8 |
+ len(self.note), # p_filesz
|
|
|
9ae3a8 |
+ len(self.note), # p_memsz
|
|
|
9ae3a8 |
+ 0 # p_align
|
|
|
9ae3a8 |
+ )
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def encode_elf64_load_le(self, offset, start_hwaddr, range_size):
|
|
|
9ae3a8 |
+ return self.elf64_phdr_le.pack(self.PT_LOAD, # p_type
|
|
|
9ae3a8 |
+ 0, # p_flags
|
|
|
9ae3a8 |
+ offset, # p_offset
|
|
|
9ae3a8 |
+ 0, # p_vaddr
|
|
|
9ae3a8 |
+ start_hwaddr, # p_paddr
|
|
|
9ae3a8 |
+ range_size, # p_filesz
|
|
|
9ae3a8 |
+ range_size, # p_memsz
|
|
|
9ae3a8 |
+ 0 # p_align
|
|
|
9ae3a8 |
+ )
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def note_init(self, name, desc, type):
|
|
|
9ae3a8 |
+ # name must include a trailing NUL
|
|
|
9ae3a8 |
+ namesz = (len(name) + 1 + 3) / 4 * 4
|
|
|
9ae3a8 |
+ descsz = (len(desc) + 3) / 4 * 4
|
|
|
9ae3a8 |
+ fmt = ("<" # little endian
|
|
|
9ae3a8 |
+ "I" # n_namesz
|
|
|
9ae3a8 |
+ "I" # n_descsz
|
|
|
9ae3a8 |
+ "I" # n_type
|
|
|
9ae3a8 |
+ "%us" # name
|
|
|
9ae3a8 |
+ "%us" # desc
|
|
|
9ae3a8 |
+ % (namesz, descsz))
|
|
|
9ae3a8 |
+ self.note = struct.pack(fmt,
|
|
|
9ae3a8 |
+ len(name) + 1, len(desc), type, name, desc)
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def dump_init(self):
|
|
|
9ae3a8 |
+ self.guest_phys_blocks_init()
|
|
|
9ae3a8 |
+ self.guest_phys_blocks_append()
|
|
|
9ae3a8 |
+ self.cpu_get_dump_info()
|
|
|
9ae3a8 |
+ # we have no way to retrieve the VCPU status from KVM
|
|
|
9ae3a8 |
+ # post-mortem
|
|
|
9ae3a8 |
+ self.note_init("NONE", "EMPTY", 0)
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # Account for PT_NOTE.
|
|
|
9ae3a8 |
+ self.phdr_num = 1
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # We should never reach PN_XNUM for paging=false dumps: there's
|
|
|
9ae3a8 |
+ # just a handful of discontiguous ranges after merging.
|
|
|
9ae3a8 |
+ self.phdr_num += len(self.guest_phys_blocks)
|
|
|
9ae3a8 |
+ assert (self.phdr_num < self.PN_XNUM)
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ # Calculate the ELF file offset where the memory dump commences:
|
|
|
9ae3a8 |
+ #
|
|
|
9ae3a8 |
+ # ELF header
|
|
|
9ae3a8 |
+ # PT_NOTE
|
|
|
9ae3a8 |
+ # PT_LOAD: 1
|
|
|
9ae3a8 |
+ # PT_LOAD: 2
|
|
|
9ae3a8 |
+ # ...
|
|
|
9ae3a8 |
+ # PT_LOAD: len(self.guest_phys_blocks)
|
|
|
9ae3a8 |
+ # ELF note
|
|
|
9ae3a8 |
+ # memory dump
|
|
|
9ae3a8 |
+ self.memory_offset = (self.elf64_ehdr_le.size +
|
|
|
9ae3a8 |
+ self.elf64_phdr_le.size * self.phdr_num +
|
|
|
9ae3a8 |
+ len(self.note))
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def dump_begin(self, vmcore):
|
|
|
9ae3a8 |
+ vmcore.write(self.encode_elf64_ehdr_le())
|
|
|
9ae3a8 |
+ vmcore.write(self.encode_elf64_note_le())
|
|
|
9ae3a8 |
+ running = self.memory_offset
|
|
|
9ae3a8 |
+ for block in self.guest_phys_blocks:
|
|
|
9ae3a8 |
+ range_size = block["target_end"] - block["target_start"]
|
|
|
9ae3a8 |
+ vmcore.write(self.encode_elf64_load_le(running,
|
|
|
9ae3a8 |
+ block["target_start"],
|
|
|
9ae3a8 |
+ range_size))
|
|
|
9ae3a8 |
+ running += range_size
|
|
|
9ae3a8 |
+ vmcore.write(self.note)
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def dump_iterate(self, vmcore):
|
|
|
9ae3a8 |
+ qemu_core = gdb.inferiors()[0]
|
|
|
9ae3a8 |
+ for block in self.guest_phys_blocks:
|
|
|
9ae3a8 |
+ cur = block["host_addr"]
|
|
|
9ae3a8 |
+ left = block["target_end"] - block["target_start"]
|
|
|
9ae3a8 |
+ print ("dumping range at %016x for length %016x" %
|
|
|
9ae3a8 |
+ (cur.cast(self.uintptr_t), left))
|
|
|
9ae3a8 |
+ while (left > 0):
|
|
|
9ae3a8 |
+ chunk_size = min(self.TARGET_PAGE_SIZE, left)
|
|
|
9ae3a8 |
+ chunk = qemu_core.read_memory(cur, chunk_size)
|
|
|
9ae3a8 |
+ vmcore.write(chunk)
|
|
|
9ae3a8 |
+ cur += chunk_size
|
|
|
9ae3a8 |
+ left -= chunk_size
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def create_vmcore(self, filename):
|
|
|
9ae3a8 |
+ vmcore = open(filename, "wb")
|
|
|
9ae3a8 |
+ self.dump_begin(vmcore)
|
|
|
9ae3a8 |
+ self.dump_iterate(vmcore)
|
|
|
9ae3a8 |
+ vmcore.close()
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ def invoke(self, args, from_tty):
|
|
|
9ae3a8 |
+ # Unwittingly pressing the Enter key after the command should
|
|
|
9ae3a8 |
+ # not dump the same multi-gig coredump to the same file.
|
|
|
9ae3a8 |
+ self.dont_repeat()
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ argv = gdb.string_to_argv(args)
|
|
|
9ae3a8 |
+ if (len(argv) != 1):
|
|
|
9ae3a8 |
+ raise gdb.GdbError("usage: dump-guest-memory FILE")
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+ self.dump_init()
|
|
|
9ae3a8 |
+ self.create_vmcore(argv[0])
|
|
|
9ae3a8 |
+
|
|
|
9ae3a8 |
+DumpGuestMemory()
|
|
|
9ae3a8 |
--
|
|
|
9ae3a8 |
1.8.3.1
|
|
|
9ae3a8 |
|