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