blob: 2335e25f94c4658e5005d579e15dc0f668b8570c [file] [log] [blame]
Philippe Mathieu-Daudé3d004a32020-01-30 17:32:25 +01001#!/usr/bin/env python3
Amit Shah426d1d02014-06-20 18:56:09 +05302#
3# Compares vmstate information stored in JSON format, obtained from
4# the -dump-vmstate QEMU command.
5#
6# Copyright 2014 Amit Shah <amit.shah@redhat.com>
7# Copyright 2014 Red Hat, Inc.
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, see <http://www.gnu.org/licenses/>.
21
22import argparse
23import json
24import sys
25
26# Count the number of errors found
27taint = 0
28
29def bump_taint():
30 global taint
31
32 # Ensure we don't wrap around or reset to 0 -- the shell only has
33 # an 8-bit return value.
34 if taint < 255:
35 taint = taint + 1
36
37
38def check_fields_match(name, s_field, d_field):
39 if s_field == d_field:
40 return True
41
42 # Some fields changed names between qemu versions. This list
Thomas Huth2d2e4842022-07-11 11:53:00 +020043 # is used to allow such changes in each section / description.
Amit Shah426d1d02014-06-20 18:56:09 +053044 changed_names = {
Thomas Huthe674fed2025-04-29 17:21:39 +020045 'acpi-ghes': ['ghes_addr_le', 'hw_error_le'],
Amit Shahbb9c3632014-07-22 13:06:08 +053046 'apic': ['timer', 'timer_expiry'],
Amit Shah426d1d02014-06-20 18:56:09 +053047 'e1000': ['dev', 'parent_obj'],
48 'ehci': ['dev', 'pcidev'],
49 'I440FX': ['dev', 'parent_obj'],
50 'ich9_ahci': ['card', 'parent_obj'],
Amit Shahbb9c3632014-07-22 13:06:08 +053051 'ich9-ahci': ['ahci', 'ich9_ahci'],
52 'ioh3420': ['PCIDevice', 'PCIEDevice'],
Amit Shah426d1d02014-06-20 18:56:09 +053053 'ioh-3240-express-root-port': ['port.br.dev',
54 'parent_obj.parent_obj.parent_obj',
55 'port.br.dev.exp.aer_log',
56 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
Amit Shah027f1562015-01-21 18:35:33 +053057 'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
58 'hw_cursor_y', 'vga.hw_cursor_y'],
Amit Shahbb9c3632014-07-22 13:06:08 +053059 'lsiscsi': ['dev', 'parent_obj'],
Amit Shah426d1d02014-06-20 18:56:09 +053060 'mch': ['d', 'parent_obj'],
61 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
62 'pcnet': ['pci_dev', 'parent_obj'],
63 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
64 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
Amit Shahbb9c3632014-07-22 13:06:08 +053065 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
66 'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
67 'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
68 'tmr.timer', 'ar.tmr.timer',
69 'tmr.overflow_time', 'ar.tmr.overflow_time',
70 'gpe', 'ar.gpe'],
Amit Shah426d1d02014-06-20 18:56:09 +053071 'rtl8139': ['dev', 'parent_obj'],
72 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
Amit Shahbb9c3632014-07-22 13:06:08 +053073 'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
Amit Shah426d1d02014-06-20 18:56:09 +053074 'usb-host': ['dev', 'parent_obj'],
75 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
76 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
Amit Shahbb9c3632014-07-22 13:06:08 +053077 'vmware_vga': ['card', 'parent_obj'],
78 'vmware_vga_internal': ['depth', 'new_depth'],
Amit Shah426d1d02014-06-20 18:56:09 +053079 'xhci': ['pci_dev', 'parent_obj'],
Amit Shahbb9c3632014-07-22 13:06:08 +053080 'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
Amit Shah426d1d02014-06-20 18:56:09 +053081 'xio3130-express-downstream-port': ['port.br.dev',
82 'parent_obj.parent_obj.parent_obj',
83 'port.br.dev.exp.aer_log',
84 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
Amit Shahbb9c3632014-07-22 13:06:08 +053085 'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
Amit Shah426d1d02014-06-20 18:56:09 +053086 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
87 'br.dev.exp.aer_log',
88 'parent_obj.parent_obj.exp.aer_log'],
Laurent Vivier9cd49022017-02-14 14:33:31 +010089 'spapr_pci': ['dma_liobn[0]', 'mig_liobn',
90 'mem_win_addr', 'mig_mem_win_addr',
91 'mem_win_size', 'mig_mem_win_size',
92 'io_win_addr', 'mig_io_win_addr',
93 'io_win_size', 'mig_io_win_size'],
Peter Xuc4f88b72025-05-01 11:12:35 -040094 'hpet': ['num_timers', 'num_timers_save'],
Amit Shah426d1d02014-06-20 18:56:09 +053095 }
96
97 if not name in changed_names:
98 return False
99
100 if s_field in changed_names[name] and d_field in changed_names[name]:
101 return True
102
103 return False
104
Amit Shah79fe16c2014-07-11 18:10:45 +0530105def get_changed_sec_name(sec):
106 # Section names can change -- see commit 292b1634 for an example.
107 changes = {
108 "ICH9 LPC": "ICH9-LPC",
Amit Shah1483e0d2015-12-18 11:05:47 +0530109 "e1000-82540em": "e1000",
Amit Shah79fe16c2014-07-11 18:10:45 +0530110 }
111
112 for item in changes:
113 if item == sec:
114 return changes[item]
115 if changes[item] == sec:
116 return item
117 return ""
Amit Shah426d1d02014-06-20 18:56:09 +0530118
119def exists_in_substruct(fields, item):
120 # Some QEMU versions moved a few fields inside a substruct. This
121 # kept the on-wire format the same. This function checks if
122 # something got shifted inside a substruct. For example, the
123 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
124
125 if not "Description" in fields:
126 return False
127
128 if not "Fields" in fields["Description"]:
129 return False
130
131 substruct_fields = fields["Description"]["Fields"]
132
133 if substruct_fields == []:
134 return False
135
136 return check_fields_match(fields["Description"]["name"],
137 substruct_fields[0]["field"], item)
138
Peter Xua67cceb2023-04-25 14:05:44 -0400139def size_total(entry):
140 size = entry["size"]
141 if "num" not in entry:
142 return size
143 return size * entry["num"]
Amit Shah426d1d02014-06-20 18:56:09 +0530144
145def check_fields(src_fields, dest_fields, desc, sec):
146 # This function checks for all the fields in a section. If some
147 # fields got embedded into a substruct, this function will also
148 # attempt to check inside the substruct.
149
150 d_iter = iter(dest_fields)
151 s_iter = iter(src_fields)
152
153 # Using these lists as stacks to store previous value of s_iter
154 # and d_iter, so that when time comes to exit out of a substruct,
155 # we can go back one level up and continue from where we left off.
156
157 s_iter_list = []
158 d_iter_list = []
159
160 advance_src = True
161 advance_dest = True
Amit Shah32ce1b42014-07-22 13:06:25 +0530162 unused_count = 0
Amit Shah426d1d02014-06-20 18:56:09 +0530163
164 while True:
165 if advance_src:
166 try:
Eduardo Habkostd24d5232018-06-08 09:29:45 -0300167 s_item = next(s_iter)
Amit Shah426d1d02014-06-20 18:56:09 +0530168 except StopIteration:
169 if s_iter_list == []:
170 break
171
172 s_iter = s_iter_list.pop()
173 continue
174 else:
Amit Shah32ce1b42014-07-22 13:06:25 +0530175 if unused_count == 0:
176 # We want to avoid advancing just once -- when entering a
177 # dest substruct, or when exiting one.
178 advance_src = True
Amit Shah426d1d02014-06-20 18:56:09 +0530179
180 if advance_dest:
181 try:
Eduardo Habkostd24d5232018-06-08 09:29:45 -0300182 d_item = next(d_iter)
Amit Shah426d1d02014-06-20 18:56:09 +0530183 except StopIteration:
184 if d_iter_list == []:
185 # We were not in a substruct
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300186 print("Section \"" + sec + "\",", end=' ')
187 print("Description " + "\"" + desc + "\":", end=' ')
188 print("expected field \"" + s_item["field"] + "\",", end=' ')
189 print("while dest has no further fields")
Amit Shah426d1d02014-06-20 18:56:09 +0530190 bump_taint()
191 break
192
193 d_iter = d_iter_list.pop()
194 advance_src = False
195 continue
196 else:
Amit Shah32ce1b42014-07-22 13:06:25 +0530197 if unused_count == 0:
198 advance_dest = True
199
Amit Shah0794d882016-06-17 17:46:39 +0530200 if unused_count != 0:
Amit Shah32ce1b42014-07-22 13:06:25 +0530201 if advance_dest == False:
202 unused_count = unused_count - s_item["size"]
203 if unused_count == 0:
204 advance_dest = True
205 continue
206 if unused_count < 0:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300207 print("Section \"" + sec + "\",", end=' ')
208 print("Description \"" + desc + "\":", end=' ')
209 print("unused size mismatch near \"", end=' ')
210 print(s_item["field"] + "\"")
Amit Shah32ce1b42014-07-22 13:06:25 +0530211 bump_taint()
212 break
213 continue
214
215 if advance_src == False:
216 unused_count = unused_count - d_item["size"]
217 if unused_count == 0:
218 advance_src = True
219 continue
220 if unused_count < 0:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300221 print("Section \"" + sec + "\",", end=' ')
222 print("Description \"" + desc + "\":", end=' ')
223 print("unused size mismatch near \"", end=' ')
224 print(d_item["field"] + "\"")
Amit Shah32ce1b42014-07-22 13:06:25 +0530225 bump_taint()
226 break
227 continue
Amit Shah426d1d02014-06-20 18:56:09 +0530228
229 if not check_fields_match(desc, s_item["field"], d_item["field"]):
230 # Some fields were put in substructs, keeping the
231 # on-wire format the same, but breaking static tools
232 # like this one.
233
234 # First, check if dest has a new substruct.
235 if exists_in_substruct(d_item, s_item["field"]):
236 # listiterators don't have a prev() function, so we
237 # have to store our current location, descend into the
238 # substruct, and ensure we come out as if nothing
239 # happened when the substruct is over.
240 #
241 # Essentially we're opening the substructs that got
242 # added which didn't change the wire format.
243 d_iter_list.append(d_iter)
244 substruct_fields = d_item["Description"]["Fields"]
245 d_iter = iter(substruct_fields)
246 advance_src = False
247 continue
248
249 # Next, check if src has substruct that dest removed
250 # (can happen in backward migration: 2.0 -> 1.5)
251 if exists_in_substruct(s_item, d_item["field"]):
252 s_iter_list.append(s_iter)
253 substruct_fields = s_item["Description"]["Fields"]
254 s_iter = iter(substruct_fields)
255 advance_dest = False
256 continue
257
Amit Shah32ce1b42014-07-22 13:06:25 +0530258 if s_item["field"] == "unused" or d_item["field"] == "unused":
Peter Xua67cceb2023-04-25 14:05:44 -0400259 s_size = size_total(s_item)
260 d_size = size_total(d_item)
261 if s_size == d_size:
Amit Shah32ce1b42014-07-22 13:06:25 +0530262 continue
263
264 if d_item["field"] == "unused":
265 advance_dest = False
Peter Xua67cceb2023-04-25 14:05:44 -0400266 unused_count = d_size - s_size;
Amit Shah32ce1b42014-07-22 13:06:25 +0530267 continue
268
269 if s_item["field"] == "unused":
270 advance_src = False
Peter Xua67cceb2023-04-25 14:05:44 -0400271 unused_count = s_size - d_size
Amit Shah32ce1b42014-07-22 13:06:25 +0530272 continue
273
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300274 print("Section \"" + sec + "\",", end=' ')
275 print("Description \"" + desc + "\":", end=' ')
276 print("expected field \"" + s_item["field"] + "\",", end=' ')
277 print("got \"" + d_item["field"] + "\"; skipping rest")
Amit Shah426d1d02014-06-20 18:56:09 +0530278 bump_taint()
279 break
280
281 check_version(s_item, d_item, sec, desc)
282
283 if not "Description" in s_item:
284 # Check size of this field only if it's not a VMSTRUCT entry
285 check_size(s_item, d_item, sec, desc, s_item["field"])
286
287 check_description_in_list(s_item, d_item, sec, desc)
288
289
290def check_subsections(src_sub, dest_sub, desc, sec):
291 for s_item in src_sub:
292 found = False
293 for d_item in dest_sub:
294 if s_item["name"] != d_item["name"]:
295 continue
296
297 found = True
298 check_descriptions(s_item, d_item, sec)
299
300 if not found:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300301 print("Section \"" + sec + "\", Description \"" + desc + "\":", end=' ')
302 print("Subsection \"" + s_item["name"] + "\" not found")
Amit Shah426d1d02014-06-20 18:56:09 +0530303 bump_taint()
304
305
306def check_description_in_list(s_item, d_item, sec, desc):
307 if not "Description" in s_item:
308 return
309
310 if not "Description" in d_item:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300311 print("Section \"" + sec + "\", Description \"" + desc + "\",", end=' ')
312 print("Field \"" + s_item["field"] + "\": missing description")
Amit Shah426d1d02014-06-20 18:56:09 +0530313 bump_taint()
314 return
315
316 check_descriptions(s_item["Description"], d_item["Description"], sec)
317
318
319def check_descriptions(src_desc, dest_desc, sec):
320 check_version(src_desc, dest_desc, sec, src_desc["name"])
321
322 if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300323 print("Section \"" + sec + "\":", end=' ')
324 print("Description \"" + src_desc["name"] + "\"", end=' ')
325 print("missing, got \"" + dest_desc["name"] + "\" instead; skipping")
Amit Shah426d1d02014-06-20 18:56:09 +0530326 bump_taint()
327 return
328
329 for f in src_desc:
330 if not f in dest_desc:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300331 print("Section \"" + sec + "\"", end=' ')
332 print("Description \"" + src_desc["name"] + "\":", end=' ')
333 print("Entry \"" + f + "\" missing")
Amit Shah426d1d02014-06-20 18:56:09 +0530334 bump_taint()
335 continue
336
337 if f == 'Fields':
338 check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
339
340 if f == 'Subsections':
341 check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
342
343
344def check_version(s, d, sec, desc=None):
345 if s["version_id"] > d["version_id"]:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300346 print("Section \"" + sec + "\"", end=' ')
Amit Shah426d1d02014-06-20 18:56:09 +0530347 if desc:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300348 print("Description \"" + desc + "\":", end=' ')
349 print("version error:", s["version_id"], ">", d["version_id"])
Amit Shah426d1d02014-06-20 18:56:09 +0530350 bump_taint()
351
352 if not "minimum_version_id" in d:
353 return
354
355 if s["version_id"] < d["minimum_version_id"]:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300356 print("Section \"" + sec + "\"", end=' ')
Amit Shah426d1d02014-06-20 18:56:09 +0530357 if desc:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300358 print("Description \"" + desc + "\":", end=' ')
359 print("minimum version error:", s["version_id"], "<", end=' ')
360 print(d["minimum_version_id"])
Amit Shah426d1d02014-06-20 18:56:09 +0530361 bump_taint()
362
363
364def check_size(s, d, sec, desc=None, field=None):
365 if s["size"] != d["size"]:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300366 print("Section \"" + sec + "\"", end=' ')
Amit Shah426d1d02014-06-20 18:56:09 +0530367 if desc:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300368 print("Description \"" + desc + "\"", end=' ')
Amit Shah426d1d02014-06-20 18:56:09 +0530369 if field:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300370 print("Field \"" + field + "\"", end=' ')
371 print("size mismatch:", s["size"], ",", d["size"])
Amit Shah426d1d02014-06-20 18:56:09 +0530372 bump_taint()
373
374
375def check_machine_type(s, d):
376 if s["Name"] != d["Name"]:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300377 print("Warning: checking incompatible machine types:", end=' ')
378 print("\"" + s["Name"] + "\", \"" + d["Name"] + "\"")
Amit Shah426d1d02014-06-20 18:56:09 +0530379
380
381def main():
382 help_text = "Parse JSON-formatted vmstate dumps from QEMU in files SRC and DEST. Checks whether migration from SRC to DEST QEMU versions would break based on the VMSTATE information contained within the JSON outputs. The JSON output is created from a QEMU invocation with the -dump-vmstate parameter and a filename argument to it. Other parameters to QEMU do not matter, except the -M (machine type) parameter."
383
384 parser = argparse.ArgumentParser(description=help_text)
Dr. David Alan Gilberte8d0ac52019-11-21 18:53:03 +0000385 parser.add_argument('-s', '--src', type=argparse.FileType('r'),
386 required=True,
Amit Shah426d1d02014-06-20 18:56:09 +0530387 help='json dump from src qemu')
Dr. David Alan Gilberte8d0ac52019-11-21 18:53:03 +0000388 parser.add_argument('-d', '--dest', type=argparse.FileType('r'),
389 required=True,
Amit Shah426d1d02014-06-20 18:56:09 +0530390 help='json dump from dest qemu')
391 parser.add_argument('--reverse', required=False, default=False,
392 action='store_true',
393 help='reverse the direction')
394 args = parser.parse_args()
395
396 src_data = json.load(args.src)
397 dest_data = json.load(args.dest)
398 args.src.close()
399 args.dest.close()
400
401 if args.reverse:
402 temp = src_data
403 src_data = dest_data
404 dest_data = temp
405
406 for sec in src_data:
Amit Shah79fe16c2014-07-11 18:10:45 +0530407 dest_sec = sec
408 if not dest_sec in dest_data:
409 # Either the section name got changed, or the section
410 # doesn't exist in dest.
411 dest_sec = get_changed_sec_name(sec)
412 if not dest_sec in dest_data:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300413 print("Section \"" + sec + "\" does not exist in dest")
Amit Shah79fe16c2014-07-11 18:10:45 +0530414 bump_taint()
415 continue
Amit Shah426d1d02014-06-20 18:56:09 +0530416
417 s = src_data[sec]
Amit Shah79fe16c2014-07-11 18:10:45 +0530418 d = dest_data[dest_sec]
Amit Shah426d1d02014-06-20 18:56:09 +0530419
420 if sec == "vmschkmachine":
421 check_machine_type(s, d)
422 continue
423
424 check_version(s, d, sec)
425
426 for entry in s:
427 if not entry in d:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300428 print("Section \"" + sec + "\": Entry \"" + entry + "\"", end=' ')
429 print("missing")
Amit Shah426d1d02014-06-20 18:56:09 +0530430 bump_taint()
431 continue
432
433 if entry == "Description":
434 check_descriptions(s[entry], d[entry], sec)
435
436 return taint
437
438
439if __name__ == '__main__':
440 sys.exit(main())