]> git.proxmox.com Git - mirror_qemu.git/blame - scripts/vmstate-static-checker.py
iotests: add filter_qmp_generated_node_ids()
[mirror_qemu.git] / scripts / vmstate-static-checker.py
CommitLineData
3d004a37 1#!/usr/bin/env python3
426d1d01
AS
2#
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
2d2e4843 43 # is used to allow such changes in each section / description.
426d1d01 44 changed_names = {
bb9c3636 45 'apic': ['timer', 'timer_expiry'],
426d1d01
AS
46 'e1000': ['dev', 'parent_obj'],
47 'ehci': ['dev', 'pcidev'],
48 'I440FX': ['dev', 'parent_obj'],
49 'ich9_ahci': ['card', 'parent_obj'],
bb9c3636
AS
50 'ich9-ahci': ['ahci', 'ich9_ahci'],
51 'ioh3420': ['PCIDevice', 'PCIEDevice'],
426d1d01
AS
52 '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'],
027f1569
AS
56 'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
57 'hw_cursor_y', 'vga.hw_cursor_y'],
bb9c3636 58 'lsiscsi': ['dev', 'parent_obj'],
426d1d01
AS
59 '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',
bb9c3636
AS
64 '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'],
426d1d01
AS
70 'rtl8139': ['dev', 'parent_obj'],
71 'qxl': ['num_surfaces', 'ssd.num_surfaces'],
bb9c3636 72 'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
426d1d01
AS
73 'usb-host': ['dev', 'parent_obj'],
74 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
75 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
bb9c3636
AS
76 'vmware_vga': ['card', 'parent_obj'],
77 'vmware_vga_internal': ['depth', 'new_depth'],
426d1d01 78 'xhci': ['pci_dev', 'parent_obj'],
bb9c3636 79 'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
426d1d01
AS
80 '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'],
bb9c3636 84 'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
426d1d01
AS
85 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
86 'br.dev.exp.aer_log',
87 'parent_obj.parent_obj.exp.aer_log'],
9cd49026
LV
88 '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'],
426d1d01
AS
93 }
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
79fe16c0
AS
103def get_changed_sec_name(sec):
104 # Section names can change -- see commit 292b1634 for an example.
105 changes = {
106 "ICH9 LPC": "ICH9-LPC",
1483e0d7 107 "e1000-82540em": "e1000",
79fe16c0
AS
108 }
109
110 for item in changes:
111 if item == sec:
112 return changes[item]
113 if changes[item] == sec:
114 return item
115 return ""
426d1d01
AS
116
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
a67cceb0
PX
137def size_total(entry):
138 size = entry["size"]
139 if "num" not in entry:
140 return size
141 return size * entry["num"]
426d1d01
AS
142
143def check_fields(src_fields, dest_fields, desc, sec):
144 # This function checks for all the fields in a section. If some
145 # fields got embedded into a substruct, this function will also
146 # attempt to check inside the substruct.
147
148 d_iter = iter(dest_fields)
149 s_iter = iter(src_fields)
150
151 # Using these lists as stacks to store previous value of s_iter
152 # and d_iter, so that when time comes to exit out of a substruct,
153 # we can go back one level up and continue from where we left off.
154
155 s_iter_list = []
156 d_iter_list = []
157
158 advance_src = True
159 advance_dest = True
32ce1b48 160 unused_count = 0
426d1d01
AS
161
162 while True:
163 if advance_src:
164 try:
d24d523c 165 s_item = next(s_iter)
426d1d01
AS
166 except StopIteration:
167 if s_iter_list == []:
168 break
169
170 s_iter = s_iter_list.pop()
171 continue
172 else:
32ce1b48
AS
173 if unused_count == 0:
174 # We want to avoid advancing just once -- when entering a
175 # dest substruct, or when exiting one.
176 advance_src = True
426d1d01
AS
177
178 if advance_dest:
179 try:
d24d523c 180 d_item = next(d_iter)
426d1d01
AS
181 except StopIteration:
182 if d_iter_list == []:
183 # We were not in a substruct
f03868bd
EH
184 print("Section \"" + sec + "\",", end=' ')
185 print("Description " + "\"" + desc + "\":", end=' ')
186 print("expected field \"" + s_item["field"] + "\",", end=' ')
187 print("while dest has no further fields")
426d1d01
AS
188 bump_taint()
189 break
190
191 d_iter = d_iter_list.pop()
192 advance_src = False
193 continue
194 else:
32ce1b48
AS
195 if unused_count == 0:
196 advance_dest = True
197
0794d889 198 if unused_count != 0:
32ce1b48
AS
199 if advance_dest == False:
200 unused_count = unused_count - s_item["size"]
201 if unused_count == 0:
202 advance_dest = True
203 continue
204 if unused_count < 0:
f03868bd
EH
205 print("Section \"" + sec + "\",", end=' ')
206 print("Description \"" + desc + "\":", end=' ')
207 print("unused size mismatch near \"", end=' ')
208 print(s_item["field"] + "\"")
32ce1b48
AS
209 bump_taint()
210 break
211 continue
212
213 if advance_src == False:
214 unused_count = unused_count - d_item["size"]
215 if unused_count == 0:
216 advance_src = True
217 continue
218 if unused_count < 0:
f03868bd
EH
219 print("Section \"" + sec + "\",", end=' ')
220 print("Description \"" + desc + "\":", end=' ')
221 print("unused size mismatch near \"", end=' ')
222 print(d_item["field"] + "\"")
32ce1b48
AS
223 bump_taint()
224 break
225 continue
426d1d01
AS
226
227 if not check_fields_match(desc, s_item["field"], d_item["field"]):
228 # Some fields were put in substructs, keeping the
229 # on-wire format the same, but breaking static tools
230 # like this one.
231
232 # First, check if dest has a new substruct.
233 if exists_in_substruct(d_item, s_item["field"]):
234 # listiterators don't have a prev() function, so we
235 # have to store our current location, descend into the
236 # substruct, and ensure we come out as if nothing
237 # happened when the substruct is over.
238 #
239 # Essentially we're opening the substructs that got
240 # added which didn't change the wire format.
241 d_iter_list.append(d_iter)
242 substruct_fields = d_item["Description"]["Fields"]
243 d_iter = iter(substruct_fields)
244 advance_src = False
245 continue
246
247 # Next, check if src has substruct that dest removed
248 # (can happen in backward migration: 2.0 -> 1.5)
249 if exists_in_substruct(s_item, d_item["field"]):
250 s_iter_list.append(s_iter)
251 substruct_fields = s_item["Description"]["Fields"]
252 s_iter = iter(substruct_fields)
253 advance_dest = False
254 continue
255
32ce1b48 256 if s_item["field"] == "unused" or d_item["field"] == "unused":
a67cceb0
PX
257 s_size = size_total(s_item)
258 d_size = size_total(d_item)
259 if s_size == d_size:
32ce1b48
AS
260 continue
261
262 if d_item["field"] == "unused":
263 advance_dest = False
a67cceb0 264 unused_count = d_size - s_size;
32ce1b48
AS
265 continue
266
267 if s_item["field"] == "unused":
268 advance_src = False
a67cceb0 269 unused_count = s_size - d_size
32ce1b48
AS
270 continue
271
f03868bd
EH
272 print("Section \"" + sec + "\",", end=' ')
273 print("Description \"" + desc + "\":", end=' ')
274 print("expected field \"" + s_item["field"] + "\",", end=' ')
275 print("got \"" + d_item["field"] + "\"; skipping rest")
426d1d01
AS
276 bump_taint()
277 break
278
279 check_version(s_item, d_item, sec, desc)
280
281 if not "Description" in s_item:
282 # Check size of this field only if it's not a VMSTRUCT entry
283 check_size(s_item, d_item, sec, desc, s_item["field"])
284
285 check_description_in_list(s_item, d_item, sec, desc)
286
287
288def check_subsections(src_sub, dest_sub, desc, sec):
289 for s_item in src_sub:
290 found = False
291 for d_item in dest_sub:
292 if s_item["name"] != d_item["name"]:
293 continue
294
295 found = True
296 check_descriptions(s_item, d_item, sec)
297
298 if not found:
f03868bd
EH
299 print("Section \"" + sec + "\", Description \"" + desc + "\":", end=' ')
300 print("Subsection \"" + s_item["name"] + "\" not found")
426d1d01
AS
301 bump_taint()
302
303
304def check_description_in_list(s_item, d_item, sec, desc):
305 if not "Description" in s_item:
306 return
307
308 if not "Description" in d_item:
f03868bd
EH
309 print("Section \"" + sec + "\", Description \"" + desc + "\",", end=' ')
310 print("Field \"" + s_item["field"] + "\": missing description")
426d1d01
AS
311 bump_taint()
312 return
313
314 check_descriptions(s_item["Description"], d_item["Description"], sec)
315
316
317def check_descriptions(src_desc, dest_desc, sec):
318 check_version(src_desc, dest_desc, sec, src_desc["name"])
319
320 if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
f03868bd
EH
321 print("Section \"" + sec + "\":", end=' ')
322 print("Description \"" + src_desc["name"] + "\"", end=' ')
323 print("missing, got \"" + dest_desc["name"] + "\" instead; skipping")
426d1d01
AS
324 bump_taint()
325 return
326
327 for f in src_desc:
328 if not f in dest_desc:
f03868bd
EH
329 print("Section \"" + sec + "\"", end=' ')
330 print("Description \"" + src_desc["name"] + "\":", end=' ')
331 print("Entry \"" + f + "\" missing")
426d1d01
AS
332 bump_taint()
333 continue
334
335 if f == 'Fields':
336 check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
337
338 if f == 'Subsections':
339 check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
340
341
342def check_version(s, d, sec, desc=None):
343 if s["version_id"] > d["version_id"]:
f03868bd 344 print("Section \"" + sec + "\"", end=' ')
426d1d01 345 if desc:
f03868bd
EH
346 print("Description \"" + desc + "\":", end=' ')
347 print("version error:", s["version_id"], ">", d["version_id"])
426d1d01
AS
348 bump_taint()
349
350 if not "minimum_version_id" in d:
351 return
352
353 if s["version_id"] < d["minimum_version_id"]:
f03868bd 354 print("Section \"" + sec + "\"", end=' ')
426d1d01 355 if desc:
f03868bd
EH
356 print("Description \"" + desc + "\":", end=' ')
357 print("minimum version error:", s["version_id"], "<", end=' ')
358 print(d["minimum_version_id"])
426d1d01
AS
359 bump_taint()
360
361
362def check_size(s, d, sec, desc=None, field=None):
363 if s["size"] != d["size"]:
f03868bd 364 print("Section \"" + sec + "\"", end=' ')
426d1d01 365 if desc:
f03868bd 366 print("Description \"" + desc + "\"", end=' ')
426d1d01 367 if field:
f03868bd
EH
368 print("Field \"" + field + "\"", end=' ')
369 print("size mismatch:", s["size"], ",", d["size"])
426d1d01
AS
370 bump_taint()
371
372
373def check_machine_type(s, d):
374 if s["Name"] != d["Name"]:
f03868bd
EH
375 print("Warning: checking incompatible machine types:", end=' ')
376 print("\"" + s["Name"] + "\", \"" + d["Name"] + "\"")
426d1d01
AS
377
378
379def main():
380 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."
381
382 parser = argparse.ArgumentParser(description=help_text)
e8d0ac58
DDAG
383 parser.add_argument('-s', '--src', type=argparse.FileType('r'),
384 required=True,
426d1d01 385 help='json dump from src qemu')
e8d0ac58
DDAG
386 parser.add_argument('-d', '--dest', type=argparse.FileType('r'),
387 required=True,
426d1d01
AS
388 help='json dump from dest qemu')
389 parser.add_argument('--reverse', required=False, default=False,
390 action='store_true',
391 help='reverse the direction')
392 args = parser.parse_args()
393
394 src_data = json.load(args.src)
395 dest_data = json.load(args.dest)
396 args.src.close()
397 args.dest.close()
398
399 if args.reverse:
400 temp = src_data
401 src_data = dest_data
402 dest_data = temp
403
404 for sec in src_data:
79fe16c0
AS
405 dest_sec = sec
406 if not dest_sec in dest_data:
407 # Either the section name got changed, or the section
408 # doesn't exist in dest.
409 dest_sec = get_changed_sec_name(sec)
410 if not dest_sec in dest_data:
f03868bd 411 print("Section \"" + sec + "\" does not exist in dest")
79fe16c0
AS
412 bump_taint()
413 continue
426d1d01
AS
414
415 s = src_data[sec]
79fe16c0 416 d = dest_data[dest_sec]
426d1d01
AS
417
418 if sec == "vmschkmachine":
419 check_machine_type(s, d)
420 continue
421
422 check_version(s, d, sec)
423
424 for entry in s:
425 if not entry in d:
f03868bd
EH
426 print("Section \"" + sec + "\": Entry \"" + entry + "\"", end=' ')
427 print("missing")
426d1d01
AS
428 bump_taint()
429 continue
430
431 if entry == "Description":
432 check_descriptions(s[entry], d[entry], sec)
433
434 return taint
435
436
437if __name__ == '__main__':
438 sys.exit(main())