@samitouri / QOSamiQemu / commits / 952303c355

tests/qtest: add dump-guest-memory test

There is currently almost no coverage for the dump-guest-memory QMP command beyond the test-hmp smoke test. Add a qtest that runs on a bare machine (no guest OS) and checks: - query-dump-guest-memory-capability always advertises 'elf'; - an ELF dump is produced and starts with the ELF magic; - a non-raw kdump dump is emitted in makedumpfile flattened format; - a raw kdump dump starts with the on-disk KDUMP header; - an unknown protocol is rejected without killing the VM, and dumping still works afterwards. Signed-off-by: Denis V. Lunev <den@openvz.org> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com> Message-Id: <20260619101834.228432-6-den@openvz.org>

Denis V. Lunev committed Jun 19, 2026 at 12:18 UTC 952303c355d20b97bd000ba616f1e73565707eeb
3 files changed +190
MAINTAINERS
+1
@@ -3309,6 +3309,7 @@ F: scripts/dump-guest-memory.py
3309 F: stubs/dump.c
3310 F: docs/specs/vmcoreinfo.rst
3311 F: tests/qtest/vmcoreinfo-test.c
3312 +F: tests/qtest/dump-test.c
3313
3314 Error reporting
3315 M: Markus Armbruster <armbru@redhat.com>
tests/qtest/dump-test.c new
+188
@@ -0,0 +1,188 @@
1 +/*
2 + * QTest testcase for dump-guest-memory
3 + *
4 + * Generic coverage for the dump-guest-memory QMP command and the
5 + * query-dump-guest-memory-capability reporting, exercised on a bare
6 + * machine (no guest OS required).
7 + *
8 + * Copyright (c) 2026 Virtuozzo International GmbH
9 + *
10 + * SPDX-License-Identifier: GPL-2.0-or-later
11 + */
12 +
13 +#include "qemu/osdep.h"
14 +#include "libqtest.h"
15 +#include "qobject/qdict.h"
16 +#include "qobject/qlist.h"
17 +#include "qobject/qstring.h"
18 +#include "qemu/bswap.h"
19 +#include "elf.h"
20 +
21 +#define KDUMP_RAW_MAGIC "KDUMP "
22 +#define KDUMP_FLAT_MAGIC "makedumpfile"
23 +
24 +static QTestState *dump_test_start(void)
25 +{
26 + return qtest_initf("-machine q35 -accel qtest -m 16");
27 +}
28 +
29 +static void assert_file_magic(const char *path, const char *magic, size_t len)
30 +{
31 + g_autofree char *buf = g_malloc0(len);
32 + FILE *f = fopen(path, "rb");
33 +
34 + g_assert_nonnull(f);
35 + g_assert_cmpint(fread(buf, 1, len, f), ==, len);
36 + fclose(f);
37 + g_assert_cmpint(memcmp(buf, magic, len), ==, 0);
38 +}
39 +
40 +/* validate that the file is a sane x86 ELF core, not just the leading magic */
41 +static void assert_valid_elf_core(const char *path)
42 +{
43 + unsigned char e[64];
44 + FILE *f = fopen(path, "rb");
45 + uint16_t e_type, e_machine, e_phnum;
46 +
47 + g_assert_nonnull(f);
48 + g_assert_cmpint(fread(e, 1, sizeof(e), f), ==, sizeof(e));
49 + fclose(f);
50 +
51 + g_assert_cmpint(memcmp(e, ELFMAG, SELFMAG), ==, 0);
52 +
53 + /* e_type and e_machine sit at the same offset for ELF32 and ELF64 */
54 + e_type = lduw_le_p(e + 16);
55 + e_machine = lduw_le_p(e + 18);
56 + g_assert_cmpint(e_type, ==, ET_CORE);
57 + g_assert(e_machine == EM_386 || e_machine == EM_X86_64);
58 +
59 + /* e_phnum lives at a class-dependent offset */
60 + if (e[EI_CLASS] == ELFCLASS64) {
61 + e_phnum = lduw_le_p(e + 56);
62 + } else {
63 + e_phnum = lduw_le_p(e + 44);
64 + }
65 + g_assert_cmpint(e_phnum, >, 0);
66 +}
67 +
68 +/* dump-guest-memory to a fresh temp file; returns the path (caller frees) */
69 +static char *do_dump(QTestState *qts, const char *format)
70 +{
71 + g_autofree char *tmp = NULL;
72 + g_autofree char *proto = NULL;
73 + GError *err = NULL;
74 + int fd;
75 +
76 + fd = g_file_open_tmp("dump-test-XXXXXX", &tmp, &err);
77 + g_assert_no_error(err);
78 + close(fd);
79 + proto = g_strdup_printf("file:%s", tmp);
80 +
81 + if (format) {
82 + qtest_qmp_assert_success(qts,
83 + "{ 'execute': 'dump-guest-memory',"
84 + " 'arguments': { 'paging': false, 'protocol': %s,"
85 + " 'format': %s } }", proto, format);
86 + } else {
87 + qtest_qmp_assert_success(qts,
88 + "{ 'execute': 'dump-guest-memory',"
89 + " 'arguments': { 'paging': false, 'protocol': %s } }", proto);
90 + }
91 +
92 + return g_steal_pointer(&tmp);
93 +}
94 +
95 +/* query-dump-guest-memory-capability must always advertise at least 'elf' */
96 +static void test_query_capability(void)
97 +{
98 + QTestState *qts = dump_test_start();
99 + QDict *resp, *ret;
100 + QList *formats;
101 + QListEntry *e;
102 + bool has_elf = false;
103 +
104 + resp = qtest_qmp(qts,
105 + "{ 'execute': 'query-dump-guest-memory-capability' }");
106 + g_assert(qdict_haskey(resp, "return"));
107 + ret = qdict_get_qdict(resp, "return");
108 + formats = qdict_get_qlist(ret, "formats");
109 + g_assert_nonnull(formats);
110 +
111 + QLIST_FOREACH_ENTRY(formats, e) {
112 + QString *qs = qobject_to(QString, qlist_entry_obj(e));
113 +
114 + if (g_str_equal(qstring_get_str(qs), "elf")) {
115 + has_elf = true;
116 + }
117 + }
118 + g_assert_true(has_elf);
119 +
120 + qobject_unref(resp);
121 + qtest_quit(qts);
122 +}
123 +
124 +static void test_dump_elf(void)
125 +{
126 + QTestState *qts = dump_test_start();
127 + g_autofree char *path = do_dump(qts, NULL);
128 +
129 + assert_valid_elf_core(path);
130 + unlink(path);
131 + qtest_quit(qts);
132 +}
133 +
134 +/* non-raw kdump is emitted in makedumpfile flattened format */
135 +static void test_dump_kdump_zlib(void)
136 +{
137 + QTestState *qts = dump_test_start();
138 + g_autofree char *path = do_dump(qts, "kdump-zlib");
139 +
140 + assert_file_magic(path, KDUMP_FLAT_MAGIC, strlen(KDUMP_FLAT_MAGIC));
141 + unlink(path);
142 + qtest_quit(qts);
143 +}
144 +
145 +/* raw kdump starts with the on-disk KDUMP header */
146 +static void test_dump_kdump_raw_zlib(void)
147 +{
148 + QTestState *qts = dump_test_start();
149 + g_autofree char *path = do_dump(qts, "kdump-raw-zlib");
150 +
151 + assert_file_magic(path, KDUMP_RAW_MAGIC, strlen(KDUMP_RAW_MAGIC));
152 + unlink(path);
153 + qtest_quit(qts);
154 +}
155 +
156 +/* an unknown protocol must be rejected, not crash the VM */
157 +static void test_dump_invalid_protocol(void)
158 +{
159 + QTestState *qts = dump_test_start();
160 + g_autofree char *path = NULL;
161 + QDict *resp;
162 +
163 + resp = qtest_qmp(qts,
164 + "{ 'execute': 'dump-guest-memory',"
165 + " 'arguments': { 'paging': false, 'protocol': 'bogus:/x' } }");
166 + g_assert(qdict_haskey(resp, "error"));
167 + qobject_unref(resp);
168 +
169 + /* VM is still alive and dumping still works afterwards */
170 + path = do_dump(qts, NULL);
171 + assert_valid_elf_core(path);
172 + unlink(path);
173 +
174 + qtest_quit(qts);
175 +}
176 +
177 +int main(int argc, char **argv)
178 +{
179 + g_test_init(&argc, &argv, NULL);
180 +
181 + qtest_add_func("/dump/query-capability", test_query_capability);
182 + qtest_add_func("/dump/elf", test_dump_elf);
183 + qtest_add_func("/dump/kdump-zlib", test_dump_kdump_zlib);
184 + qtest_add_func("/dump/kdump-raw-zlib", test_dump_kdump_raw_zlib);
185 + qtest_add_func("/dump/invalid-protocol", test_dump_invalid_protocol);
186 +
187 + return g_test_run();
188 +}
tests/qtest/meson.build
+1
@@ -59,6 +59,7 @@ qtests_i386 = \
59 (config_all_devices.has_key('CONFIG_FDC_ISA') ? ['fdc-test'] : []) + \
60 (config_all_devices.has_key('CONFIG_I440FX') ? ['fw_cfg-test'] : []) + \
61 (config_all_devices.has_key('CONFIG_FW_CFG_DMA') ? ['vmcoreinfo-test'] : []) + \
62 + (config_all_devices.has_key('CONFIG_Q35') ? ['dump-test'] : []) + \
63 (config_all_devices.has_key('CONFIG_I440FX') ? ['i440fx-test'] : []) + \
64 (config_all_devices.has_key('CONFIG_I440FX') ? ['ide-test'] : []) + \
65 (config_all_devices.has_key('CONFIG_I440FX') ? ['numa-test'] : []) + \