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