]> git.proxmox.com Git - mirror_qemu.git/blob - scripts/vmstate-static-checker.py
vmstate static checker: whitelist additions
[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
148 while True:
149 if advance_src:
150 try:
151 s_item = s_iter.next()
152 except StopIteration:
153 if s_iter_list == []:
154 break
155
156 s_iter = s_iter_list.pop()
157 continue
158 else:
159 # We want to avoid advancing just once -- when entering a
160 # dest substruct, or when exiting one.
161 advance_src = True
162
163 if advance_dest:
164 try:
165 d_item = d_iter.next()
166 except StopIteration:
167 if d_iter_list == []:
168 # We were not in a substruct
169 print "Section \"" + sec + "\",",
170 print "Description " + "\"" + desc + "\":",
171 print "expected field \"" + s_item["field"] + "\",",
172 print "while dest has no further fields"
173 bump_taint()
174 break
175
176 d_iter = d_iter_list.pop()
177 advance_src = False
178 continue
179 else:
180 advance_dest = True
181
182 if not check_fields_match(desc, s_item["field"], d_item["field"]):
183 # Some fields were put in substructs, keeping the
184 # on-wire format the same, but breaking static tools
185 # like this one.
186
187 # First, check if dest has a new substruct.
188 if exists_in_substruct(d_item, s_item["field"]):
189 # listiterators don't have a prev() function, so we
190 # have to store our current location, descend into the
191 # substruct, and ensure we come out as if nothing
192 # happened when the substruct is over.
193 #
194 # Essentially we're opening the substructs that got
195 # added which didn't change the wire format.
196 d_iter_list.append(d_iter)
197 substruct_fields = d_item["Description"]["Fields"]
198 d_iter = iter(substruct_fields)
199 advance_src = False
200 continue
201
202 # Next, check if src has substruct that dest removed
203 # (can happen in backward migration: 2.0 -> 1.5)
204 if exists_in_substruct(s_item, d_item["field"]):
205 s_iter_list.append(s_iter)
206 substruct_fields = s_item["Description"]["Fields"]
207 s_iter = iter(substruct_fields)
208 advance_dest = False
209 continue
210
211 print "Section \"" + sec + "\",",
212 print "Description \"" + desc + "\":",
213 print "expected field \"" + s_item["field"] + "\",",
214 print "got \"" + d_item["field"] + "\"; skipping rest"
215 bump_taint()
216 break
217
218 check_version(s_item, d_item, sec, desc)
219
220 if not "Description" in s_item:
221 # Check size of this field only if it's not a VMSTRUCT entry
222 check_size(s_item, d_item, sec, desc, s_item["field"])
223
224 check_description_in_list(s_item, d_item, sec, desc)
225
226
227 def check_subsections(src_sub, dest_sub, desc, sec):
228 for s_item in src_sub:
229 found = False
230 for d_item in dest_sub:
231 if s_item["name"] != d_item["name"]:
232 continue
233
234 found = True
235 check_descriptions(s_item, d_item, sec)
236
237 if not found:
238 print "Section \"" + sec + "\", Description \"" + desc + "\":",
239 print "Subsection \"" + s_item["name"] + "\" not found"
240 bump_taint()
241
242
243 def check_description_in_list(s_item, d_item, sec, desc):
244 if not "Description" in s_item:
245 return
246
247 if not "Description" in d_item:
248 print "Section \"" + sec + "\", Description \"" + desc + "\",",
249 print "Field \"" + s_item["field"] + "\": missing description"
250 bump_taint()
251 return
252
253 check_descriptions(s_item["Description"], d_item["Description"], sec)
254
255
256 def check_descriptions(src_desc, dest_desc, sec):
257 check_version(src_desc, dest_desc, sec, src_desc["name"])
258
259 if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
260 print "Section \"" + sec + "\":",
261 print "Description \"" + src_desc["name"] + "\"",
262 print "missing, got \"" + dest_desc["name"] + "\" instead; skipping"
263 bump_taint()
264 return
265
266 for f in src_desc:
267 if not f in dest_desc:
268 print "Section \"" + sec + "\"",
269 print "Description \"" + src_desc["name"] + "\":",
270 print "Entry \"" + f + "\" missing"
271 bump_taint()
272 continue
273
274 if f == 'Fields':
275 check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
276
277 if f == 'Subsections':
278 check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
279
280
281 def check_version(s, d, sec, desc=None):
282 if s["version_id"] > d["version_id"]:
283 print "Section \"" + sec + "\"",
284 if desc:
285 print "Description \"" + desc + "\":",
286 print "version error:", s["version_id"], ">", d["version_id"]
287 bump_taint()
288
289 if not "minimum_version_id" in d:
290 return
291
292 if s["version_id"] < d["minimum_version_id"]:
293 print "Section \"" + sec + "\"",
294 if desc:
295 print "Description \"" + desc + "\":",
296 print "minimum version error:", s["version_id"], "<",
297 print d["minimum_version_id"]
298 bump_taint()
299
300
301 def check_size(s, d, sec, desc=None, field=None):
302 if s["size"] != d["size"]:
303 print "Section \"" + sec + "\"",
304 if desc:
305 print "Description \"" + desc + "\"",
306 if field:
307 print "Field \"" + field + "\"",
308 print "size mismatch:", s["size"], ",", d["size"]
309 bump_taint()
310
311
312 def check_machine_type(s, d):
313 if s["Name"] != d["Name"]:
314 print "Warning: checking incompatible machine types:",
315 print "\"" + s["Name"] + "\", \"" + d["Name"] + "\""
316 return
317
318
319 def main():
320 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."
321
322 parser = argparse.ArgumentParser(description=help_text)
323 parser.add_argument('-s', '--src', type=file, required=True,
324 help='json dump from src qemu')
325 parser.add_argument('-d', '--dest', type=file, required=True,
326 help='json dump from dest qemu')
327 parser.add_argument('--reverse', required=False, default=False,
328 action='store_true',
329 help='reverse the direction')
330 args = parser.parse_args()
331
332 src_data = json.load(args.src)
333 dest_data = json.load(args.dest)
334 args.src.close()
335 args.dest.close()
336
337 if args.reverse:
338 temp = src_data
339 src_data = dest_data
340 dest_data = temp
341
342 for sec in src_data:
343 dest_sec = sec
344 if not dest_sec in dest_data:
345 # Either the section name got changed, or the section
346 # doesn't exist in dest.
347 dest_sec = get_changed_sec_name(sec)
348 if not dest_sec in dest_data:
349 print "Section \"" + sec + "\" does not exist in dest"
350 bump_taint()
351 continue
352
353 s = src_data[sec]
354 d = dest_data[dest_sec]
355
356 if sec == "vmschkmachine":
357 check_machine_type(s, d)
358 continue
359
360 check_version(s, d, sec)
361
362 for entry in s:
363 if not entry in d:
364 print "Section \"" + sec + "\": Entry \"" + entry + "\"",
365 print "missing"
366 bump_taint()
367 continue
368
369 if entry == "Description":
370 check_descriptions(s[entry], d[entry], sec)
371
372 return taint
373
374
375 if __name__ == '__main__':
376 sys.exit(main())