diff mbox

[RFC,8/8,v3] introduce a new monitor command 'dump' to dump guest's memory

Message ID 4EF0522D.4040906@cn.fujitsu.com
State New
Headers show

Commit Message

Wen Congyang Dec. 20, 2011, 9:15 a.m. UTC
Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
---
 Makefile.target |    8 +-
 dump.c          |  452 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 dump.h          |    4 +
 hmp-commands.hx |   16 ++
 monitor.c       |    3 +
 qmp-commands.hx |   24 +++
 6 files changed, 503 insertions(+), 4 deletions(-)
 create mode 100644 dump.c

Comments

Eric Blake Dec. 20, 2011, 4:25 p.m. UTC | #1
On 12/20/2011 02:15 AM, Wen Congyang wrote:
> Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
> ---
>  Makefile.target |    8 +-
>  dump.c          |  452 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  dump.h          |    4 +
>  hmp-commands.hx |   16 ++
>  monitor.c       |    3 +
>  qmp-commands.hx |   24 +++
>  6 files changed, 503 insertions(+), 4 deletions(-)
>  create mode 100644 dump.c
> 
> +++ b/qmp-commands.hx
> @@ -469,6 +469,30 @@ Notes:
>  EQMP
>  
>      {
> +        .name       = "dump",
> +        .args_type  = "file:s",
> +        .params     = "file",
> +        .help       = "dump to file",
> +        .user_print = monitor_user_noop,
> +        .mhandler.cmd_new = do_dump,
> +    },

From a libvirt perspective, we would like the option to be able to pass
in an already-open fd rather than just a file name.  This is possible if
the 'file' argument is required to start with '/' for an absolute path,
vs. 'file:name' for an fd previously passed in via the getfd monitor
command.

Also, does this command block?  It sounds like it is long-running, which
means it probably needs to be asynchronous, as well as issue an event
upon completion, so that other monitor commands can be issued in the
meantime.
Wen Congyang Dec. 21, 2011, 1:10 a.m. UTC | #2
At 12/21/2011 12:25 AM, Eric Blake Write:
> On 12/20/2011 02:15 AM, Wen Congyang wrote:
>> Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
>> ---
>>  Makefile.target |    8 +-
>>  dump.c          |  452 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  dump.h          |    4 +
>>  hmp-commands.hx |   16 ++
>>  monitor.c       |    3 +
>>  qmp-commands.hx |   24 +++
>>  6 files changed, 503 insertions(+), 4 deletions(-)
>>  create mode 100644 dump.c
>>
>> +++ b/qmp-commands.hx
>> @@ -469,6 +469,30 @@ Notes:
>>  EQMP
>>  
>>      {
>> +        .name       = "dump",
>> +        .args_type  = "file:s",
>> +        .params     = "file",
>> +        .help       = "dump to file",
>> +        .user_print = monitor_user_noop,
>> +        .mhandler.cmd_new = do_dump,
>> +    },
> 
> From a libvirt perspective, we would like the option to be able to pass
> in an already-open fd rather than just a file name.  This is possible if
> the 'file' argument is required to start with '/' for an absolute path,
> vs. 'file:name' for an fd previously passed in via the getfd monitor
> command.

file:s means the parameter is a file, and the type is string.
s can be file:path or fd:fd's name here. Sorry for confusing you.

> 
> Also, does this command block?  It sounds like it is long-running, which
> means it probably needs to be asynchronous, as well as issue an event
> upon completion, so that other monitor commands can be issued in the
> meantime.
> 
Good idea, i will try to implement it in the next version.

Thanks
Wen Congyang
andrzej zaborowski Dec. 21, 2011, 2:42 a.m. UTC | #3
On 20 December 2011 17:25, Eric Blake <eblake@redhat.com> wrote:
> On 12/20/2011 02:15 AM, Wen Congyang wrote:
>> Signed-off-by: Wen Congyang <wency@cn.fujitsu.com>
>> ---
>>  Makefile.target |    8 +-
>>  dump.c          |  452 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>>  dump.h          |    4 +
>>  hmp-commands.hx |   16 ++
>>  monitor.c       |    3 +
>>  qmp-commands.hx |   24 +++
>>  6 files changed, 503 insertions(+), 4 deletions(-)
>>  create mode 100644 dump.c
>>
>> +++ b/qmp-commands.hx
>> @@ -469,6 +469,30 @@ Notes:
>>  EQMP
>>
>>      {
>> +        .name       = "dump",
>> +        .args_type  = "file:s",
>> +        .params     = "file",
>> +        .help       = "dump to file",
>> +        .user_print = monitor_user_noop,
>> +        .mhandler.cmd_new = do_dump,
>> +    },
>
> From a libvirt perspective, we would like the option to be able to pass
> in an already-open fd rather than just a file name.  This is possible if
> the 'file' argument is required to start with '/' for an absolute path,
> vs. 'file:name' for an fd previously passed in via the getfd monitor
> command.
>
> Also, does this command block?  It sounds like it is long-running, which
> means it probably needs to be asynchronous, as well as issue an event
> upon completion, so that other monitor commands can be issued in the
> meantime.

Note that it needs to stop the VM and it'd need to prevent other
commands from resuming if this command becomes asynchronous, like
during migration.

Cheers
diff mbox

Patch

diff --git a/Makefile.target b/Makefile.target
index 29562ad..f7cc2b9 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -110,7 +110,7 @@  $(call set-vpath, $(SRC_PATH)/linux-user:$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR
 QEMU_CFLAGS+=-I$(SRC_PATH)/linux-user/$(TARGET_ABI_DIR) -I$(SRC_PATH)/linux-user
 obj-y = main.o syscall.o strace.o mmap.o signal.o thunk.o \
       elfload.o linuxload.o uaccess.o gdbstub.o cpu-uname.o \
-      user-exec.o $(oslib-obj-y)
+      user-exec.o $(oslib-obj-y) dump.o
 
 obj-$(TARGET_HAS_BFLT) += flatload.o
 
@@ -148,7 +148,7 @@  LDFLAGS+=-Wl,-segaddr,__STD_PROG_ZONE,0x1000 -image_base 0x0e000000
 LIBS+=-lmx
 
 obj-y = main.o commpage.o machload.o mmap.o signal.o syscall.o thunk.o \
-        gdbstub.o user-exec.o
+        gdbstub.o user-exec.o dump.o
 
 obj-i386-y += ioport-user.o
 
@@ -170,7 +170,7 @@  $(call set-vpath, $(SRC_PATH)/bsd-user)
 QEMU_CFLAGS+=-I$(SRC_PATH)/bsd-user -I$(SRC_PATH)/bsd-user/$(TARGET_ARCH)
 
 obj-y = main.o bsdload.o elfload.o mmap.o signal.o strace.o syscall.o \
-        gdbstub.o uaccess.o user-exec.o
+        gdbstub.o uaccess.o user-exec.o dump.o
 
 obj-i386-y += ioport-user.o
 
@@ -186,7 +186,7 @@  endif #CONFIG_BSD_USER
 # System emulator target
 ifdef CONFIG_SOFTMMU
 
-obj-y = arch_init.o cpus.o monitor.o machine.o gdbstub.o balloon.o ioport.o
+obj-y = arch_init.o cpus.o monitor.o machine.o gdbstub.o balloon.o ioport.o dump.o
 # virtio has to be here due to weird dependency between PCI and virtio-net.
 # need to fix this properly
 obj-$(CONFIG_NO_PCI) += pci-stub.o
diff --git a/dump.c b/dump.c
new file mode 100644
index 0000000..81e466f
--- /dev/null
+++ b/dump.c
@@ -0,0 +1,452 @@ 
+/*
+ * QEMU dump
+ *
+ * Copyright Fujitsu, Corp. 2011
+ *
+ * Authors:
+ *     Wen Congyang <wency@cn.fujitsu.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.  See
+ * the COPYING file in the top-level directory.
+ *
+ */
+
+#include "qemu-common.h"
+#include <unistd.h>
+#include <elf.h>
+#include <sys/procfs.h>
+#include "cpu.h"
+#include "cpu-all.h"
+#include "targphys.h"
+#include "monitor.h"
+#include "kvm.h"
+#include "dump.h"
+#include "sysemu.h"
+#include "bswap.h"
+#include "memory_mapping.h"
+
+#define CPU_CONVERT_TO_TARGET16(val) \
+({ \
+    uint16_t _val = (val); \
+    if (endian == ELFDATA2LSB) { \
+        _val = cpu_to_le16(_val); \
+    } else {\
+        _val = cpu_to_be16(_val); \
+    } \
+    _val; \
+})
+
+#define CPU_CONVERT_TO_TARGET32(val) \
+({ \
+    uint32_t _val = (val); \
+    if (endian == ELFDATA2LSB) { \
+        _val = cpu_to_le32(_val); \
+    } else {\
+        _val = cpu_to_be32(_val); \
+    } \
+    _val; \
+})
+
+#define CPU_CONVERT_TO_TARGET64(val) \
+({ \
+    uint64_t _val = (val); \
+    if (endian == ELFDATA2LSB) { \
+        _val = cpu_to_le64(_val); \
+    } else {\
+        _val = cpu_to_be64(_val); \
+    } \
+    _val; \
+})
+
+static inline int cpuid(CPUState *env)
+{
+#if defined(CONFIG_USER_ONLY) && defined(CONFIG_USE_NPTL)
+    return env->host_tid;
+#else
+    return env->cpu_index + 1;
+#endif
+}
+
+static int write_elf64_header(Monitor *mon, int fd, int phdr_num, int machine,
+                              int endian)
+{
+    Elf64_Ehdr elf_header;
+    int ret;
+
+    memset(&elf_header, 0, sizeof(Elf64_Ehdr));
+    memcpy(&elf_header, ELFMAG, 4);
+    elf_header.e_ident[EI_CLASS] = ELFCLASS64;
+    elf_header.e_ident[EI_DATA] = endian;
+    elf_header.e_ident[EI_VERSION] = EV_CURRENT;
+    elf_header.e_type = CPU_CONVERT_TO_TARGET16(ET_CORE);
+    elf_header.e_machine = CPU_CONVERT_TO_TARGET16(machine);
+    elf_header.e_version = CPU_CONVERT_TO_TARGET32(EV_CURRENT);
+    elf_header.e_ehsize = CPU_CONVERT_TO_TARGET16(sizeof(elf_header));
+    elf_header.e_phoff = CPU_CONVERT_TO_TARGET64(sizeof(Elf64_Ehdr));
+    elf_header.e_phentsize = CPU_CONVERT_TO_TARGET16(sizeof(Elf64_Phdr));
+    elf_header.e_phnum = CPU_CONVERT_TO_TARGET16(phdr_num);
+
+    lseek(fd, 0, SEEK_SET);
+    ret = write(fd, &elf_header, sizeof(elf_header));
+    if (ret < 0) {
+        monitor_printf(mon, "dump: failed to write elf header.\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int write_elf32_header(Monitor *mon, int fd, int phdr_num, int machine,
+                              int endian)
+{
+    Elf32_Ehdr elf_header;
+    int ret;
+
+    memset(&elf_header, 0, sizeof(Elf32_Ehdr));
+    memcpy(&elf_header, ELFMAG, 4);
+    elf_header.e_ident[EI_CLASS] = ELFCLASS32;
+    elf_header.e_ident[EI_DATA] = endian;
+    elf_header.e_ident[EI_VERSION] = EV_CURRENT;
+    elf_header.e_type = CPU_CONVERT_TO_TARGET16(ET_CORE);
+    elf_header.e_machine = CPU_CONVERT_TO_TARGET16(machine);
+    elf_header.e_version = CPU_CONVERT_TO_TARGET32(EV_CURRENT);
+    elf_header.e_ehsize = CPU_CONVERT_TO_TARGET16(sizeof(elf_header));
+    elf_header.e_phoff = CPU_CONVERT_TO_TARGET32(sizeof(Elf32_Ehdr));
+    elf_header.e_phentsize = CPU_CONVERT_TO_TARGET16(sizeof(Elf32_Phdr));
+    elf_header.e_phnum = CPU_CONVERT_TO_TARGET16(phdr_num);
+
+    lseek(fd, 0, SEEK_SET);
+    ret = write(fd, &elf_header, sizeof(elf_header));
+    if (ret < 0) {
+        monitor_printf(mon, "dump: failed to write elf header.\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int write_elf64_load(Monitor *mon, int fd, MemoryMapping *memory_mapping,
+                            int phdr_index, target_phys_addr_t offset,
+                            int endian)
+{
+    Elf64_Phdr phdr;
+    off_t phdr_offset;
+    int ret;
+
+    memset(&phdr, 0, sizeof(Elf64_Phdr));
+    phdr.p_type = CPU_CONVERT_TO_TARGET32(PT_LOAD);
+    phdr.p_offset = CPU_CONVERT_TO_TARGET64(offset);
+    phdr.p_paddr = CPU_CONVERT_TO_TARGET64(memory_mapping->phys_addr);
+    if (offset == -1) {
+        phdr.p_filesz = 0;
+    } else {
+        phdr.p_filesz = CPU_CONVERT_TO_TARGET64(memory_mapping->length);
+    }
+    phdr.p_memsz = CPU_CONVERT_TO_TARGET64(memory_mapping->length);
+    phdr.p_vaddr = CPU_CONVERT_TO_TARGET64(memory_mapping->virt_addr);
+
+    phdr_offset = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr)*phdr_index;
+    lseek(fd, phdr_offset, SEEK_SET);
+    ret = write(fd, &phdr, sizeof(Elf64_Phdr));
+    if (ret < 0) {
+        monitor_printf(mon, "dump: failed to write program header table.\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int write_elf32_load(Monitor *mon, int fd, MemoryMapping *memory_mapping,
+                            int phdr_index, target_phys_addr_t offset,
+                            int endian)
+{
+    Elf32_Phdr phdr;
+    off_t phdr_offset;
+    int ret;
+
+    memset(&phdr, 0, sizeof(Elf32_Phdr));
+    phdr.p_type = CPU_CONVERT_TO_TARGET32(PT_LOAD);
+    phdr.p_offset = CPU_CONVERT_TO_TARGET32(offset);
+    phdr.p_paddr = CPU_CONVERT_TO_TARGET32(memory_mapping->phys_addr);
+    if (offset == -1) {
+        phdr.p_filesz = 0;
+    } else {
+        phdr.p_filesz = CPU_CONVERT_TO_TARGET32(memory_mapping->length);
+    }
+    phdr.p_memsz = CPU_CONVERT_TO_TARGET32(memory_mapping->length);
+    phdr.p_vaddr = CPU_CONVERT_TO_TARGET32(memory_mapping->virt_addr);
+
+    phdr_offset = sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr)*phdr_index;
+    lseek(fd, phdr_offset, SEEK_SET);
+    ret = write(fd, &phdr, sizeof(Elf32_Phdr));
+    if (ret < 0) {
+        monitor_printf(mon, "dump: failed to write program header table.\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int write_elf64_notes(Monitor *mon, int fd, int phdr_index,
+                             target_phys_addr_t *offset, int endian)
+{
+    CPUState *env;
+    int ret;
+    target_phys_addr_t begin = *offset;
+    Elf64_Phdr phdr;
+    off_t phdr_offset;
+    int id;
+
+    for (env = first_cpu; env != NULL; env = env->next_cpu) {
+        id = cpuid(env);
+        ret = cpu_write_elf64_note(mon, fd, env, id, offset);
+        if (ret < 0) {
+            monitor_printf(mon, "dump: failed to write elf notes.\n");
+            return -1;
+        }
+    }
+
+    memset(&phdr, 0, sizeof(Elf64_Phdr));
+    phdr.p_type = CPU_CONVERT_TO_TARGET32(PT_NOTE);
+    phdr.p_offset = CPU_CONVERT_TO_TARGET64(begin);
+    phdr.p_paddr = 0;
+    phdr.p_filesz = CPU_CONVERT_TO_TARGET64(*offset - begin);
+    phdr.p_memsz = CPU_CONVERT_TO_TARGET64(*offset - begin);
+    phdr.p_vaddr = 0;
+
+    phdr_offset = sizeof(Elf64_Ehdr);
+    lseek(fd, phdr_offset, SEEK_SET);
+    ret = write(fd, &phdr, sizeof(Elf64_Phdr));
+    if (ret < 0) {
+        monitor_printf(mon, "dump: failed to write program header table.\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int write_elf32_notes(Monitor *mon, int fd, int phdr_index,
+                             target_phys_addr_t *offset, int endian)
+{
+    CPUState *env;
+    int ret;
+    target_phys_addr_t begin = *offset;
+    Elf32_Phdr phdr;
+    off_t phdr_offset;
+    int id;
+
+    for (env = first_cpu; env != NULL; env = env->next_cpu) {
+        id = cpuid(env);
+        ret = cpu_write_elf32_note(mon, fd, env, id, offset);
+        if (ret < 0) {
+            monitor_printf(mon, "dump: failed to write elf notes.\n");
+            return -1;
+        }
+    }
+
+    memset(&phdr, 0, sizeof(Elf32_Phdr));
+    phdr.p_type = CPU_CONVERT_TO_TARGET32(PT_NOTE);
+    phdr.p_offset = CPU_CONVERT_TO_TARGET32(begin);
+    phdr.p_paddr = 0;
+    phdr.p_filesz = CPU_CONVERT_TO_TARGET32(*offset - begin);
+    phdr.p_memsz = CPU_CONVERT_TO_TARGET32(*offset - begin);
+    phdr.p_vaddr = 0;
+
+    phdr_offset = sizeof(Elf32_Ehdr);
+    lseek(fd, phdr_offset, SEEK_SET);
+    ret = write(fd, &phdr, sizeof(Elf32_Phdr));
+    if (ret < 0) {
+        monitor_printf(mon, "dump: failed to write program header table.\n");
+        return -1;
+    }
+
+    return 0;
+}
+
+static int write_data(Monitor *mon, int fd, void *buf, int length,
+                      target_phys_addr_t *offset)
+{
+    int ret;
+
+    lseek(fd, *offset, SEEK_SET);
+    ret = write(fd, buf, length);
+    if (ret < 0) {
+        monitor_printf(mon, "dump: failed to save memory.\n");
+        return -1;
+    }
+
+    *offset += length;
+    return 0;
+}
+
+/* write the memroy to vmcore. 1 page per I/O. */
+static int write_memory(Monitor *mon, int fd, RAMBlock *block,
+                        target_phys_addr_t *offset)
+{
+    int i, ret;
+
+    for (i = 0; i < block->length / TARGET_PAGE_SIZE; i++) {
+        ret = write_data(mon, fd, block->host + i * TARGET_PAGE_SIZE,
+                         TARGET_PAGE_SIZE, offset);
+        if (ret < 0) {
+            return -1;
+        }
+    }
+
+    if ((block->length % TARGET_PAGE_SIZE) != 0) {
+        ret = write_data(mon, fd, block->host + i * TARGET_PAGE_SIZE,
+                        block->length % TARGET_PAGE_SIZE, offset);
+        if (ret < 0) {
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+/* get the memory's offset in the vmcore */
+static target_phys_addr_t get_offset(target_phys_addr_t phys_addr,
+                                     target_phys_addr_t memory_offset)
+{
+    RAMBlock *block;
+    target_phys_addr_t offset = memory_offset;
+
+    QLIST_FOREACH(block, &ram_list.blocks, next) {
+        if (phys_addr >= block->offset &&
+            phys_addr < block->offset + block->length) {
+            return phys_addr - block->offset + offset;
+        }
+        offset += block->length;
+    }
+
+    return -1;
+}
+
+static int create_vmcore(Monitor *mon, int fd)
+{
+    CPUState *env;
+    target_phys_addr_t offset, memory_offset;
+    int phdr_num, phdr_index;
+    RAMBlock *block;
+    int ret;
+    MemoryMappingList list;
+    MemoryMapping *memory_mapping;
+    DumpInfo info;
+
+    for (env = first_cpu; env != NULL; env = env->next_cpu) {
+        cpu_synchronize_state(env);
+    }
+
+    list.num = 0;
+    QTAILQ_INIT(&list.head);
+    get_memory_mapping(&list);
+
+    ret = cpu_get_dump_info(&info);
+    if (ret < 0) {
+        monitor_printf(mon, "dump: unsupported target.\n");
+        goto error;
+    }
+
+    phdr_num = 1; /* PT_NOTE */
+    /* the type of phdr->num is uint16_t, so we should avoid overflow */
+    if (list.num > (1 << 16) - 2) {
+        phdr_num = (1 << 16) - 1;
+    } else {
+        phdr_num += list.num;
+    }
+
+    /* write elf header to vmcore */
+    if (info.d_class == ELFCLASS64) {
+        ret = write_elf64_header(mon, fd, phdr_num, info.d_machine,
+                                 info.d_endian);
+    } else {
+        ret = write_elf32_header(mon, fd, phdr_num, info.d_machine,
+                                 info.d_endian);
+    }
+    if (ret < 0) {
+        goto error;
+    }
+
+    /* write elf notes to vmcore */
+    phdr_index = 0;
+    if (info.d_class == ELFCLASS64) {
+        offset = sizeof(Elf64_Ehdr) + sizeof(Elf64_Phdr)*phdr_num;
+        ret = write_elf64_notes(mon, fd, phdr_index++, &offset, info.d_endian);
+    } else {
+        offset = sizeof(Elf32_Ehdr) + sizeof(Elf32_Phdr)*phdr_num;
+        ret = write_elf32_notes(mon, fd, phdr_index++, &offset, info.d_endian);
+    }
+
+    if (ret < 0) {
+        goto error;
+    }
+
+    memory_offset = offset;
+    /* write all memory to vmcore */
+    QLIST_FOREACH(block, &ram_list.blocks, next) {
+        ret = write_memory(mon, fd, block, &offset);
+        if (ret < 0) {
+            goto error;
+        }
+    }
+
+    /* write PT_LOAD program header to vmcore */
+    QTAILQ_FOREACH(memory_mapping, &list.head, next) {
+        offset = get_offset(memory_mapping->phys_addr, memory_offset);
+        if (info.d_class == ELFCLASS64) {
+            ret = write_elf64_load(mon, fd, memory_mapping, phdr_index++,
+                                   offset, info.d_endian);
+        } else {
+            ret = write_elf32_load(mon, fd, memory_mapping, phdr_index++,
+                                   offset, info.d_endian);
+        }
+        if (ret < 0) {
+            goto error;
+        }
+    }
+
+    free_memory_mapping_list(&list);
+    return 0;
+
+error:
+    free_memory_mapping_list(&list);
+    return -1;
+}
+
+int do_dump(Monitor *mon, const QDict *qdict, QObject **ret_data)
+{
+    const char *file = qdict_get_str(qdict, "file");
+    const char *p;
+    int fd = -1;
+
+#if !defined(WIN32)
+    if (strstart(file, "fd:", &p)) {
+        fd = monitor_get_fd(mon, p);
+        if (fd == -1) {
+            monitor_printf(mon, "dump: invalid file descriptor"
+                           " identifier\n");
+            return -1;
+        }
+    }
+#endif
+
+    if  (strstart(file, "file:", &p)) {
+        fd = open(p, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY);
+        if (fd < 0) {
+            monitor_printf(mon, "dump: failed to open %s\n", p);
+            return -1;
+        }
+    }
+
+    if (fd == -1) {
+        monitor_printf(mon, "unknown dump protocol: %s\n", file);
+        return -1;
+    }
+
+    vm_stop(RUN_STATE_PAUSED);
+    if (create_vmcore(mon, fd) < 0) {
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/dump.h b/dump.h
index a37de45..0280215 100644
--- a/dump.h
+++ b/dump.h
@@ -1,10 +1,14 @@ 
 #ifndef DUMP_H
 #define DUMP_H
 
+#include "qdict.h"
+
 typedef struct DumpInfo {
     int d_machine;  /* Architecture */
     int d_endian;   /* ELFDATA2LSB or ELFDATA2MSB */
     int d_class;    /* ELFCLASS32 or ELFCLASS64 */
 } DumpInfo;
 
+int do_dump(Monitor *mon, const QDict *qdict, QObject **ret_data);
+
 #endif
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 14838b7..49faa01 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -767,6 +767,22 @@  Migrate to @var{uri} (using -d to not wait for completion).
 ETEXI
 
     {
+        .name       = "dump",
+        .args_type  = "file:s",
+        .params     = "file",
+        .help       = "dump to file",
+        .user_print = monitor_user_noop,
+        .mhandler.cmd_new = do_dump,
+    },
+
+
+STEXI
+@item dump @var{file}
+@findex dump
+Dump to @var{file}.
+ETEXI
+
+    {
         .name       = "migrate_cancel",
         .args_type  = "",
         .params     = "",
diff --git a/monitor.c b/monitor.c
index 7334401..edd6aa7 100644
--- a/monitor.c
+++ b/monitor.c
@@ -73,6 +73,9 @@ 
 #endif
 #include "hw/lm32_pic.h"
 
+/* for dump */
+#include "dump.h"
+
 //#define DEBUG
 //#define DEBUG_COMPLETION
 
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 7e3f4b9..34c7593 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -469,6 +469,30 @@  Notes:
 EQMP
 
     {
+        .name       = "dump",
+        .args_type  = "file:s",
+        .params     = "file",
+        .help       = "dump to file",
+        .user_print = monitor_user_noop,
+        .mhandler.cmd_new = do_dump,
+    },
+
+SQMP
+dump
+-------
+
+Dump to file.
+
+Arguments: None.
+
+Example:
+
+-> { "execute": "dump", "arguments": { "file": "fd:dump" } }
+<- { "return": {} }
+
+EQMP
+
+    {
         .name       = "migrate_cancel",
         .args_type  = "",
         .mhandler.cmd_new = qmp_marshal_input_migrate_cancel,