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