Add qemu 2.4.0
[kvmfornfv.git] / qemu / 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         'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
57                        'hw_cursor_y', 'vga.hw_cursor_y'],
58         'lsiscsi': ['dev', 'parent_obj'],
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',
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'],
70         'rtl8139': ['dev', 'parent_obj'],
71         'qxl': ['num_surfaces', 'ssd.num_surfaces'],
72         'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
73         'usb-host': ['dev', 'parent_obj'],
74         'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
75         'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
76         'vmware_vga': ['card', 'parent_obj'],
77         'vmware_vga_internal': ['depth', 'new_depth'],
78         'xhci': ['pci_dev', 'parent_obj'],
79         'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
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'],
84         'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
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
98 def 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 ""
110
111 def 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
132 def 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
149     unused_count = 0
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:
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
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:
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
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
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
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
275 def 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
291 def 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
304 def 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
329 def 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
349 def 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
360 def 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
367 def 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:
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
400
401         s = src_data[sec]
402         d = dest_data[dest_sec]
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
423 if __name__ == '__main__':
424     sys.exit(main())