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'] : []) + \