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