blob: 539ead62b498202fc40b42bff05f133469ed4bb8 [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
43 # is used to whitelist such changes in each section / description.
44 changed_names = {
Amit Shahbb9c3632014-07-22 13:06:08 +053045 'apic': ['timer', 'timer_expiry'],
Amit Shah426d1d02014-06-20 18:56:09 +053046 'e1000': ['dev', 'parent_obj'],
47 'ehci': ['dev', 'pcidev'],
48 'I440FX': ['dev', 'parent_obj'],
49 'ich9_ahci': ['card', 'parent_obj'],
Amit Shahbb9c3632014-07-22 13:06:08 +053050 'ich9-ahci': ['ahci', 'ich9_ahci'],
51 'ioh3420': ['PCIDevice', 'PCIEDevice'],
Amit Shah426d1d02014-06-20 18:56:09 +053052 'ioh-3240-express-root-port': ['port.br.dev',
53 'parent_obj.parent_obj.parent_obj',
54 'port.br.dev.exp.aer_log',
55 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
Amit Shah027f1562015-01-21 18:35:33 +053056 'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
57 'hw_cursor_y', 'vga.hw_cursor_y'],
Amit Shahbb9c3632014-07-22 13:06:08 +053058 'lsiscsi': ['dev', 'parent_obj'],
Amit Shah426d1d02014-06-20 18:56:09 +053059 'mch': ['d', 'parent_obj'],
60 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
61 'pcnet': ['pci_dev', 'parent_obj'],
62 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
63 'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
Amit Shahbb9c3632014-07-22 13:06:08 +053064 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
65 'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
66 'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
67 'tmr.timer', 'ar.tmr.timer',
68 'tmr.overflow_time', 'ar.tmr.overflow_time',
69 'gpe', 'ar.gpe'],
Amit Shah426d1d02014-06-20 18:56:09 +053070 'rtl8139': ['dev', 'parent_obj'],
71 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
Amit Shahbb9c3632014-07-22 13:06:08 +053072 'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
Amit Shah426d1d02014-06-20 18:56:09 +053073 'usb-host': ['dev', 'parent_obj'],
74 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
75 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
Amit Shahbb9c3632014-07-22 13:06:08 +053076 'vmware_vga': ['card', 'parent_obj'],
77 'vmware_vga_internal': ['depth', 'new_depth'],
Amit Shah426d1d02014-06-20 18:56:09 +053078 'xhci': ['pci_dev', 'parent_obj'],
Amit Shahbb9c3632014-07-22 13:06:08 +053079 'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
Amit Shah426d1d02014-06-20 18:56:09 +053080 'xio3130-express-downstream-port': ['port.br.dev',
81 'parent_obj.parent_obj.parent_obj',
82 'port.br.dev.exp.aer_log',
83 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
Amit Shahbb9c3632014-07-22 13:06:08 +053084 'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
Amit Shah426d1d02014-06-20 18:56:09 +053085 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
86 'br.dev.exp.aer_log',
87 'parent_obj.parent_obj.exp.aer_log'],
Laurent Vivier9cd49022017-02-14 14:33:31 +010088 'spapr_pci': ['dma_liobn[0]', 'mig_liobn',
89 'mem_win_addr', 'mig_mem_win_addr',
90 'mem_win_size', 'mig_mem_win_size',
91 'io_win_addr', 'mig_io_win_addr',
92 'io_win_size', 'mig_io_win_size'],
Amit Shah426d1d02014-06-20 18:56:09 +053093 }
94
95 if not name in changed_names:
96 return False
97
98 if s_field in changed_names[name] and d_field in changed_names[name]:
99 return True
100
101 return False
102
Amit Shah79fe16c2014-07-11 18:10:45 +0530103def get_changed_sec_name(sec):
104 # Section names can change -- see commit 292b1634 for an example.
105 changes = {
106 "ICH9 LPC": "ICH9-LPC",
Amit Shah1483e0d2015-12-18 11:05:47 +0530107 "e1000-82540em": "e1000",
Amit Shah79fe16c2014-07-11 18:10:45 +0530108 }
109
110 for item in changes:
111 if item == sec:
112 return changes[item]
113 if changes[item] == sec:
114 return item
115 return ""
Amit Shah426d1d02014-06-20 18:56:09 +0530116
117def exists_in_substruct(fields, item):
118 # Some QEMU versions moved a few fields inside a substruct. This
119 # kept the on-wire format the same. This function checks if
120 # something got shifted inside a substruct. For example, the
121 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
122
123 if not "Description" in fields:
124 return False
125
126 if not "Fields" in fields["Description"]:
127 return False
128
129 substruct_fields = fields["Description"]["Fields"]
130
131 if substruct_fields == []:
132 return False
133
134 return check_fields_match(fields["Description"]["name"],
135 substruct_fields[0]["field"], item)
136
137
138def check_fields(src_fields, dest_fields, desc, sec):
139 # This function checks for all the fields in a section. If some
140 # fields got embedded into a substruct, this function will also
141 # attempt to check inside the substruct.
142
143 d_iter = iter(dest_fields)
144 s_iter = iter(src_fields)
145
146 # Using these lists as stacks to store previous value of s_iter
147 # and d_iter, so that when time comes to exit out of a substruct,
148 # we can go back one level up and continue from where we left off.
149
150 s_iter_list = []
151 d_iter_list = []
152
153 advance_src = True
154 advance_dest = True
Amit Shah32ce1b42014-07-22 13:06:25 +0530155 unused_count = 0
Amit Shah426d1d02014-06-20 18:56:09 +0530156
157 while True:
158 if advance_src:
159 try:
Eduardo Habkostd24d5232018-06-08 09:29:45 -0300160 s_item = next(s_iter)
Amit Shah426d1d02014-06-20 18:56:09 +0530161 except StopIteration:
162 if s_iter_list == []:
163 break
164
165 s_iter = s_iter_list.pop()
166 continue
167 else:
Amit Shah32ce1b42014-07-22 13:06:25 +0530168 if unused_count == 0:
169 # We want to avoid advancing just once -- when entering a
170 # dest substruct, or when exiting one.
171 advance_src = True
Amit Shah426d1d02014-06-20 18:56:09 +0530172
173 if advance_dest:
174 try:
Eduardo Habkostd24d5232018-06-08 09:29:45 -0300175 d_item = next(d_iter)
Amit Shah426d1d02014-06-20 18:56:09 +0530176 except StopIteration:
177 if d_iter_list == []:
178 # We were not in a substruct
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300179 print("Section \"" + sec + "\",", end=' ')
180 print("Description " + "\"" + desc + "\":", end=' ')
181 print("expected field \"" + s_item["field"] + "\",", end=' ')
182 print("while dest has no further fields")
Amit Shah426d1d02014-06-20 18:56:09 +0530183 bump_taint()
184 break
185
186 d_iter = d_iter_list.pop()
187 advance_src = False
188 continue
189 else:
Amit Shah32ce1b42014-07-22 13:06:25 +0530190 if unused_count == 0:
191 advance_dest = True
192
Amit Shah0794d882016-06-17 17:46:39 +0530193 if unused_count != 0:
Amit Shah32ce1b42014-07-22 13:06:25 +0530194 if advance_dest == False:
195 unused_count = unused_count - s_item["size"]
196 if unused_count == 0:
197 advance_dest = True
198 continue
199 if unused_count < 0:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300200 print("Section \"" + sec + "\",", end=' ')
201 print("Description \"" + desc + "\":", end=' ')
202 print("unused size mismatch near \"", end=' ')
203 print(s_item["field"] + "\"")
Amit Shah32ce1b42014-07-22 13:06:25 +0530204 bump_taint()
205 break
206 continue
207
208 if advance_src == False:
209 unused_count = unused_count - d_item["size"]
210 if unused_count == 0:
211 advance_src = True
212 continue
213 if unused_count < 0:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300214 print("Section \"" + sec + "\",", end=' ')
215 print("Description \"" + desc + "\":", end=' ')
216 print("unused size mismatch near \"", end=' ')
217 print(d_item["field"] + "\"")
Amit Shah32ce1b42014-07-22 13:06:25 +0530218 bump_taint()
219 break
220 continue
Amit Shah426d1d02014-06-20 18:56:09 +0530221
222 if not check_fields_match(desc, s_item["field"], d_item["field"]):
223 # Some fields were put in substructs, keeping the
224 # on-wire format the same, but breaking static tools
225 # like this one.
226
227 # First, check if dest has a new substruct.
228 if exists_in_substruct(d_item, s_item["field"]):
229 # listiterators don't have a prev() function, so we
230 # have to store our current location, descend into the
231 # substruct, and ensure we come out as if nothing
232 # happened when the substruct is over.
233 #
234 # Essentially we're opening the substructs that got
235 # added which didn't change the wire format.
236 d_iter_list.append(d_iter)
237 substruct_fields = d_item["Description"]["Fields"]
238 d_iter = iter(substruct_fields)
239 advance_src = False
240 continue
241
242 # Next, check if src has substruct that dest removed
243 # (can happen in backward migration: 2.0 -> 1.5)
244 if exists_in_substruct(s_item, d_item["field"]):
245 s_iter_list.append(s_iter)
246 substruct_fields = s_item["Description"]["Fields"]
247 s_iter = iter(substruct_fields)
248 advance_dest = False
249 continue
250
Amit Shah32ce1b42014-07-22 13:06:25 +0530251 if s_item["field"] == "unused" or d_item["field"] == "unused":
252 if s_item["size"] == d_item["size"]:
253 continue
254
255 if d_item["field"] == "unused":
256 advance_dest = False
257 unused_count = d_item["size"] - s_item["size"]
258 continue
259
260 if s_item["field"] == "unused":
261 advance_src = False
262 unused_count = s_item["size"] - d_item["size"]
263 continue
264
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300265 print("Section \"" + sec + "\",", end=' ')
266 print("Description \"" + desc + "\":", end=' ')
267 print("expected field \"" + s_item["field"] + "\",", end=' ')
268 print("got \"" + d_item["field"] + "\"; skipping rest")
Amit Shah426d1d02014-06-20 18:56:09 +0530269 bump_taint()
270 break
271
272 check_version(s_item, d_item, sec, desc)
273
274 if not "Description" in s_item:
275 # Check size of this field only if it's not a VMSTRUCT entry
276 check_size(s_item, d_item, sec, desc, s_item["field"])
277
278 check_description_in_list(s_item, d_item, sec, desc)
279
280
281def check_subsections(src_sub, dest_sub, desc, sec):
282 for s_item in src_sub:
283 found = False
284 for d_item in dest_sub:
285 if s_item["name"] != d_item["name"]:
286 continue
287
288 found = True
289 check_descriptions(s_item, d_item, sec)
290
291 if not found:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300292 print("Section \"" + sec + "\", Description \"" + desc + "\":", end=' ')
293 print("Subsection \"" + s_item["name"] + "\" not found")
Amit Shah426d1d02014-06-20 18:56:09 +0530294 bump_taint()
295
296
297def check_description_in_list(s_item, d_item, sec, desc):
298 if not "Description" in s_item:
299 return
300
301 if not "Description" in d_item:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300302 print("Section \"" + sec + "\", Description \"" + desc + "\",", end=' ')
303 print("Field \"" + s_item["field"] + "\": missing description")
Amit Shah426d1d02014-06-20 18:56:09 +0530304 bump_taint()
305 return
306
307 check_descriptions(s_item["Description"], d_item["Description"], sec)
308
309
310def check_descriptions(src_desc, dest_desc, sec):
311 check_version(src_desc, dest_desc, sec, src_desc["name"])
312
313 if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300314 print("Section \"" + sec + "\":", end=' ')
315 print("Description \"" + src_desc["name"] + "\"", end=' ')
316 print("missing, got \"" + dest_desc["name"] + "\" instead; skipping")
Amit Shah426d1d02014-06-20 18:56:09 +0530317 bump_taint()
318 return
319
320 for f in src_desc:
321 if not f in dest_desc:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300322 print("Section \"" + sec + "\"", end=' ')
323 print("Description \"" + src_desc["name"] + "\":", end=' ')
324 print("Entry \"" + f + "\" missing")
Amit Shah426d1d02014-06-20 18:56:09 +0530325 bump_taint()
326 continue
327
328 if f == 'Fields':
329 check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
330
331 if f == 'Subsections':
332 check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
333
334
335def check_version(s, d, sec, desc=None):
336 if s["version_id"] > d["version_id"]:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300337 print("Section \"" + sec + "\"", end=' ')
Amit Shah426d1d02014-06-20 18:56:09 +0530338 if desc:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300339 print("Description \"" + desc + "\":", end=' ')
340 print("version error:", s["version_id"], ">", d["version_id"])
Amit Shah426d1d02014-06-20 18:56:09 +0530341 bump_taint()
342
343 if not "minimum_version_id" in d:
344 return
345
346 if s["version_id"] < d["minimum_version_id"]:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300347 print("Section \"" + sec + "\"", end=' ')
Amit Shah426d1d02014-06-20 18:56:09 +0530348 if desc:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300349 print("Description \"" + desc + "\":", end=' ')
350 print("minimum version error:", s["version_id"], "<", end=' ')
351 print(d["minimum_version_id"])
Amit Shah426d1d02014-06-20 18:56:09 +0530352 bump_taint()
353
354
355def check_size(s, d, sec, desc=None, field=None):
356 if s["size"] != d["size"]:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300357 print("Section \"" + sec + "\"", end=' ')
Amit Shah426d1d02014-06-20 18:56:09 +0530358 if desc:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300359 print("Description \"" + desc + "\"", end=' ')
Amit Shah426d1d02014-06-20 18:56:09 +0530360 if field:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300361 print("Field \"" + field + "\"", end=' ')
362 print("size mismatch:", s["size"], ",", d["size"])
Amit Shah426d1d02014-06-20 18:56:09 +0530363 bump_taint()
364
365
366def check_machine_type(s, d):
367 if s["Name"] != d["Name"]:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300368 print("Warning: checking incompatible machine types:", end=' ')
369 print("\"" + s["Name"] + "\", \"" + d["Name"] + "\"")
Amit Shah426d1d02014-06-20 18:56:09 +0530370 return
371
372
373def main():
374 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."
375
376 parser = argparse.ArgumentParser(description=help_text)
Dr. David Alan Gilberte8d0ac52019-11-21 18:53:03 +0000377 parser.add_argument('-s', '--src', type=argparse.FileType('r'),
378 required=True,
Amit Shah426d1d02014-06-20 18:56:09 +0530379 help='json dump from src qemu')
Dr. David Alan Gilberte8d0ac52019-11-21 18:53:03 +0000380 parser.add_argument('-d', '--dest', type=argparse.FileType('r'),
381 required=True,
Amit Shah426d1d02014-06-20 18:56:09 +0530382 help='json dump from dest qemu')
383 parser.add_argument('--reverse', required=False, default=False,
384 action='store_true',
385 help='reverse the direction')
386 args = parser.parse_args()
387
388 src_data = json.load(args.src)
389 dest_data = json.load(args.dest)
390 args.src.close()
391 args.dest.close()
392
393 if args.reverse:
394 temp = src_data
395 src_data = dest_data
396 dest_data = temp
397
398 for sec in src_data:
Amit Shah79fe16c2014-07-11 18:10:45 +0530399 dest_sec = sec
400 if not dest_sec in dest_data:
401 # Either the section name got changed, or the section
402 # doesn't exist in dest.
403 dest_sec = get_changed_sec_name(sec)
404 if not dest_sec in dest_data:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300405 print("Section \"" + sec + "\" does not exist in dest")
Amit Shah79fe16c2014-07-11 18:10:45 +0530406 bump_taint()
407 continue
Amit Shah426d1d02014-06-20 18:56:09 +0530408
409 s = src_data[sec]
Amit Shah79fe16c2014-07-11 18:10:45 +0530410 d = dest_data[dest_sec]
Amit Shah426d1d02014-06-20 18:56:09 +0530411
412 if sec == "vmschkmachine":
413 check_machine_type(s, d)
414 continue
415
416 check_version(s, d, sec)
417
418 for entry in s:
419 if not entry in d:
Eduardo Habkostf03868b2018-06-08 09:29:43 -0300420 print("Section \"" + sec + "\": Entry \"" + entry + "\"", end=' ')
421 print("missing")
Amit Shah426d1d02014-06-20 18:56:09 +0530422 bump_taint()
423 continue
424
425 if entry == "Description":
426 check_descriptions(s[entry], d[entry], sec)
427
428 return taint
429
430
431if __name__ == '__main__':
432 sys.exit(main())