include/ui/console.h: Delete is_surface_bgr()
[qemu/ar7.git] / scripts / vmstate-static-checker.py
blob539ead62b498202fc40b42bff05f133469ed4bb8
1 #!/usr/bin/env python3
3 # Compares vmstate information stored in JSON format, obtained from
4 # the -dump-vmstate QEMU command.
6 # Copyright 2014 Amit Shah <amit.shah@redhat.com>
7 # Copyright 2014 Red Hat, Inc.
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.
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.
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/>.
22 import argparse
23 import json
24 import sys
26 # Count the number of errors found
27 taint = 0
29 def bump_taint():
30     global taint
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
38 def check_fields_match(name, s_field, d_field):
39     if s_field == d_field:
40         return True
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         'spapr_pci': ['dma_liobn[0]', 'mig_liobn',
89                       'mem_win_addr', 'mig_mem_win_addr',
90                       'mem_win_size', 'mig_mem_win_size',
91                       'io_win_addr', 'mig_io_win_addr',
92                       'io_win_size', 'mig_io_win_size'],
93     }
95     if not name in changed_names:
96         return False
98     if s_field in changed_names[name] and d_field in changed_names[name]:
99         return True
101     return False
103 def get_changed_sec_name(sec):
104     # Section names can change -- see commit 292b1634 for an example.
105     changes = {
106         "ICH9 LPC": "ICH9-LPC",
107         "e1000-82540em": "e1000",
108     }
110     for item in changes:
111         if item == sec:
112             return changes[item]
113         if changes[item] == sec:
114             return item
115     return ""
117 def exists_in_substruct(fields, item):
118     # Some QEMU versions moved a few fields inside a substruct.  This
119     # kept the on-wire format the same.  This function checks if
120     # something got shifted inside a substruct.  For example, the
121     # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
123     if not "Description" in fields:
124         return False
126     if not "Fields" in fields["Description"]:
127         return False
129     substruct_fields = fields["Description"]["Fields"]
131     if substruct_fields == []:
132         return False
134     return check_fields_match(fields["Description"]["name"],
135                               substruct_fields[0]["field"], item)
138 def check_fields(src_fields, dest_fields, desc, sec):
139     # This function checks for all the fields in a section.  If some
140     # fields got embedded into a substruct, this function will also
141     # attempt to check inside the substruct.
143     d_iter = iter(dest_fields)
144     s_iter = iter(src_fields)
146     # Using these lists as stacks to store previous value of s_iter
147     # and d_iter, so that when time comes to exit out of a substruct,
148     # we can go back one level up and continue from where we left off.
150     s_iter_list = []
151     d_iter_list = []
153     advance_src = True
154     advance_dest = True
155     unused_count = 0
157     while True:
158         if advance_src:
159             try:
160                 s_item = next(s_iter)
161             except StopIteration:
162                 if s_iter_list == []:
163                     break
165                 s_iter = s_iter_list.pop()
166                 continue
167         else:
168             if unused_count == 0:
169                 # We want to avoid advancing just once -- when entering a
170                 # dest substruct, or when exiting one.
171                 advance_src = True
173         if advance_dest:
174             try:
175                 d_item = next(d_iter)
176             except StopIteration:
177                 if d_iter_list == []:
178                     # We were not in a substruct
179                     print("Section \"" + sec + "\",", end=' ')
180                     print("Description " + "\"" + desc + "\":", end=' ')
181                     print("expected field \"" + s_item["field"] + "\",", end=' ')
182                     print("while dest has no further fields")
183                     bump_taint()
184                     break
186                 d_iter = d_iter_list.pop()
187                 advance_src = False
188                 continue
189         else:
190             if unused_count == 0:
191                 advance_dest = True
193         if unused_count != 0:
194             if advance_dest == False:
195                 unused_count = unused_count - s_item["size"]
196                 if unused_count == 0:
197                     advance_dest = True
198                     continue
199                 if unused_count < 0:
200                     print("Section \"" + sec + "\",", end=' ')
201                     print("Description \"" + desc + "\":", end=' ')
202                     print("unused size mismatch near \"", end=' ')
203                     print(s_item["field"] + "\"")
204                     bump_taint()
205                     break
206                 continue
208             if advance_src == False:
209                 unused_count = unused_count - d_item["size"]
210                 if unused_count == 0:
211                     advance_src = True
212                     continue
213                 if unused_count < 0:
214                     print("Section \"" + sec + "\",", end=' ')
215                     print("Description \"" + desc + "\":", end=' ')
216                     print("unused size mismatch near \"", end=' ')
217                     print(d_item["field"] + "\"")
218                     bump_taint()
219                     break
220                 continue
222         if not check_fields_match(desc, s_item["field"], d_item["field"]):
223             # Some fields were put in substructs, keeping the
224             # on-wire format the same, but breaking static tools
225             # like this one.
227             # First, check if dest has a new substruct.
228             if exists_in_substruct(d_item, s_item["field"]):
229                 # listiterators don't have a prev() function, so we
230                 # have to store our current location, descend into the
231                 # substruct, and ensure we come out as if nothing
232                 # happened when the substruct is over.
233                 #
234                 # Essentially we're opening the substructs that got
235                 # added which didn't change the wire format.
236                 d_iter_list.append(d_iter)
237                 substruct_fields = d_item["Description"]["Fields"]
238                 d_iter = iter(substruct_fields)
239                 advance_src = False
240                 continue
242             # Next, check if src has substruct that dest removed
243             # (can happen in backward migration: 2.0 -> 1.5)
244             if exists_in_substruct(s_item, d_item["field"]):
245                 s_iter_list.append(s_iter)
246                 substruct_fields = s_item["Description"]["Fields"]
247                 s_iter = iter(substruct_fields)
248                 advance_dest = False
249                 continue
251             if s_item["field"] == "unused" or d_item["field"] == "unused":
252                 if s_item["size"] == d_item["size"]:
253                     continue
255                 if d_item["field"] == "unused":
256                     advance_dest = False
257                     unused_count = d_item["size"] - s_item["size"]
258                     continue
260                 if s_item["field"] == "unused":
261                     advance_src = False
262                     unused_count = s_item["size"] - d_item["size"]
263                     continue
265             print("Section \"" + sec + "\",", end=' ')
266             print("Description \"" + desc + "\":", end=' ')
267             print("expected field \"" + s_item["field"] + "\",", end=' ')
268             print("got \"" + d_item["field"] + "\"; skipping rest")
269             bump_taint()
270             break
272         check_version(s_item, d_item, sec, desc)
274         if not "Description" in s_item:
275             # Check size of this field only if it's not a VMSTRUCT entry
276             check_size(s_item, d_item, sec, desc, s_item["field"])
278         check_description_in_list(s_item, d_item, sec, desc)
281 def check_subsections(src_sub, dest_sub, desc, sec):
282     for s_item in src_sub:
283         found = False
284         for d_item in dest_sub:
285             if s_item["name"] != d_item["name"]:
286                 continue
288             found = True
289             check_descriptions(s_item, d_item, sec)
291         if not found:
292             print("Section \"" + sec + "\", Description \"" + desc + "\":", end=' ')
293             print("Subsection \"" + s_item["name"] + "\" not found")
294             bump_taint()
297 def check_description_in_list(s_item, d_item, sec, desc):
298     if not "Description" in s_item:
299         return
301     if not "Description" in d_item:
302         print("Section \"" + sec + "\", Description \"" + desc + "\",", end=' ')
303         print("Field \"" + s_item["field"] + "\": missing description")
304         bump_taint()
305         return
307     check_descriptions(s_item["Description"], d_item["Description"], sec)
310 def check_descriptions(src_desc, dest_desc, sec):
311     check_version(src_desc, dest_desc, sec, src_desc["name"])
313     if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
314         print("Section \"" + sec + "\":", end=' ')
315         print("Description \"" + src_desc["name"] + "\"", end=' ')
316         print("missing, got \"" + dest_desc["name"] + "\" instead; skipping")
317         bump_taint()
318         return
320     for f in src_desc:
321         if not f in dest_desc:
322             print("Section \"" + sec + "\"", end=' ')
323             print("Description \"" + src_desc["name"] + "\":", end=' ')
324             print("Entry \"" + f + "\" missing")
325             bump_taint()
326             continue
328         if f == 'Fields':
329             check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
331         if f == 'Subsections':
332             check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
335 def check_version(s, d, sec, desc=None):
336     if s["version_id"] > d["version_id"]:
337         print("Section \"" + sec + "\"", end=' ')
338         if desc:
339             print("Description \"" + desc + "\":", end=' ')
340         print("version error:", s["version_id"], ">", d["version_id"])
341         bump_taint()
343     if not "minimum_version_id" in d:
344         return
346     if s["version_id"] < d["minimum_version_id"]:
347         print("Section \"" + sec + "\"", end=' ')
348         if desc:
349             print("Description \"" + desc + "\":", end=' ')
350             print("minimum version error:", s["version_id"], "<", end=' ')
351             print(d["minimum_version_id"])
352             bump_taint()
355 def check_size(s, d, sec, desc=None, field=None):
356     if s["size"] != d["size"]:
357         print("Section \"" + sec + "\"", end=' ')
358         if desc:
359             print("Description \"" + desc + "\"", end=' ')
360         if field:
361             print("Field \"" + field + "\"", end=' ')
362         print("size mismatch:", s["size"], ",", d["size"])
363         bump_taint()
366 def check_machine_type(s, d):
367     if s["Name"] != d["Name"]:
368         print("Warning: checking incompatible machine types:", end=' ')
369         print("\"" + s["Name"] + "\", \"" + d["Name"] + "\"")
370     return
373 def main():
374     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."
376     parser = argparse.ArgumentParser(description=help_text)
377     parser.add_argument('-s', '--src', type=argparse.FileType('r'),
378                         required=True,
379                         help='json dump from src qemu')
380     parser.add_argument('-d', '--dest', type=argparse.FileType('r'),
381                         required=True,
382                         help='json dump from dest qemu')
383     parser.add_argument('--reverse', required=False, default=False,
384                         action='store_true',
385                         help='reverse the direction')
386     args = parser.parse_args()
388     src_data = json.load(args.src)
389     dest_data = json.load(args.dest)
390     args.src.close()
391     args.dest.close()
393     if args.reverse:
394         temp = src_data
395         src_data = dest_data
396         dest_data = temp
398     for sec in src_data:
399         dest_sec = sec
400         if not dest_sec in dest_data:
401             # Either the section name got changed, or the section
402             # doesn't exist in dest.
403             dest_sec = get_changed_sec_name(sec)
404             if not dest_sec in dest_data:
405                 print("Section \"" + sec + "\" does not exist in dest")
406                 bump_taint()
407                 continue
409         s = src_data[sec]
410         d = dest_data[dest_sec]
412         if sec == "vmschkmachine":
413             check_machine_type(s, d)
414             continue
416         check_version(s, d, sec)
418         for entry in s:
419             if not entry in d:
420                 print("Section \"" + sec + "\": Entry \"" + entry + "\"", end=' ')
421                 print("missing")
422                 bump_taint()
423                 continue
425             if entry == "Description":
426                 check_descriptions(s[entry], d[entry], sec)
428     return taint
431 if __name__ == '__main__':
432     sys.exit(main())