]> Git Repo - qemu.git/blob - scripts/vmstate-static-checker.py
Merge remote-tracking branch 'remotes/mst/tags/for_upstream' into staging
[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 <[email protected]>
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 from __future__ import print_function
23 import argparse
24 import json
25 import sys
26
27 # Count the number of errors found
28 taint = 0
29
30 def bump_taint():
31     global taint
32
33     # Ensure we don't wrap around or reset to 0 -- the shell only has
34     # an 8-bit return value.
35     if taint < 255:
36         taint = taint + 1
37
38
39 def check_fields_match(name, s_field, d_field):
40     if s_field == d_field:
41         return True
42
43     # Some fields changed names between qemu versions.  This list
44     # is used to whitelist such changes in each section / description.
45     changed_names = {
46         'apic': ['timer', 'timer_expiry'],
47         'e1000': ['dev', 'parent_obj'],
48         'ehci': ['dev', 'pcidev'],
49         'I440FX': ['dev', 'parent_obj'],
50         'ich9_ahci': ['card', 'parent_obj'],
51         'ich9-ahci': ['ahci', 'ich9_ahci'],
52         'ioh3420': ['PCIDevice', 'PCIEDevice'],
53         'ioh-3240-express-root-port': ['port.br.dev',
54                                        'parent_obj.parent_obj.parent_obj',
55                                        'port.br.dev.exp.aer_log',
56                                 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
57         'cirrus_vga': ['hw_cursor_x', 'vga.hw_cursor_x',
58                        'hw_cursor_y', 'vga.hw_cursor_y'],
59         'lsiscsi': ['dev', 'parent_obj'],
60         'mch': ['d', 'parent_obj'],
61         'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'],
62         'pcnet': ['pci_dev', 'parent_obj'],
63         'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'],
64         'piix4_pm': ['dev', 'parent_obj', 'pci0_status',
65                      'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]',
66                      'pm1a.sts', 'ar.pm1.evt.sts', 'pm1a.en', 'ar.pm1.evt.en',
67                      'pm1_cnt.cnt', 'ar.pm1.cnt.cnt',
68                      'tmr.timer', 'ar.tmr.timer',
69                      'tmr.overflow_time', 'ar.tmr.overflow_time',
70                      'gpe', 'ar.gpe'],
71         'rtl8139': ['dev', 'parent_obj'],
72         'qxl': ['num_surfaces', 'ssd.num_surfaces'],
73         'usb-ccid': ['abProtocolDataStructure', 'abProtocolDataStructure.data'],
74         'usb-host': ['dev', 'parent_obj'],
75         'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'],
76         'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'],
77         'vmware_vga': ['card', 'parent_obj'],
78         'vmware_vga_internal': ['depth', 'new_depth'],
79         'xhci': ['pci_dev', 'parent_obj'],
80         'x3130-upstream': ['PCIDevice', 'PCIEDevice'],
81         'xio3130-express-downstream-port': ['port.br.dev',
82                                             'parent_obj.parent_obj.parent_obj',
83                                             'port.br.dev.exp.aer_log',
84                                 'parent_obj.parent_obj.parent_obj.exp.aer_log'],
85         'xio3130-downstream': ['PCIDevice', 'PCIEDevice'],
86         'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj',
87                                           'br.dev.exp.aer_log',
88                                           'parent_obj.parent_obj.exp.aer_log'],
89         'spapr_pci': ['dma_liobn[0]', 'mig_liobn',
90                       'mem_win_addr', 'mig_mem_win_addr',
91                       'mem_win_size', 'mig_mem_win_size',
92                       'io_win_addr', 'mig_io_win_addr',
93                       'io_win_size', 'mig_io_win_size'],
94     }
95
96     if not name in changed_names:
97         return False
98
99     if s_field in changed_names[name] and d_field in changed_names[name]:
100         return True
101
102     return False
103
104 def get_changed_sec_name(sec):
105     # Section names can change -- see commit 292b1634 for an example.
106     changes = {
107         "ICH9 LPC": "ICH9-LPC",
108         "e1000-82540em": "e1000",
109     }
110
111     for item in changes:
112         if item == sec:
113             return changes[item]
114         if changes[item] == sec:
115             return item
116     return ""
117
118 def exists_in_substruct(fields, item):
119     # Some QEMU versions moved a few fields inside a substruct.  This
120     # kept the on-wire format the same.  This function checks if
121     # something got shifted inside a substruct.  For example, the
122     # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f
123
124     if not "Description" in fields:
125         return False
126
127     if not "Fields" in fields["Description"]:
128         return False
129
130     substruct_fields = fields["Description"]["Fields"]
131
132     if substruct_fields == []:
133         return False
134
135     return check_fields_match(fields["Description"]["name"],
136                               substruct_fields[0]["field"], item)
137
138
139 def check_fields(src_fields, dest_fields, desc, sec):
140     # This function checks for all the fields in a section.  If some
141     # fields got embedded into a substruct, this function will also
142     # attempt to check inside the substruct.
143
144     d_iter = iter(dest_fields)
145     s_iter = iter(src_fields)
146
147     # Using these lists as stacks to store previous value of s_iter
148     # and d_iter, so that when time comes to exit out of a substruct,
149     # we can go back one level up and continue from where we left off.
150
151     s_iter_list = []
152     d_iter_list = []
153
154     advance_src = True
155     advance_dest = True
156     unused_count = 0
157
158     while True:
159         if advance_src:
160             try:
161                 s_item = next(s_iter)
162             except StopIteration:
163                 if s_iter_list == []:
164                     break
165
166                 s_iter = s_iter_list.pop()
167                 continue
168         else:
169             if unused_count == 0:
170                 # We want to avoid advancing just once -- when entering a
171                 # dest substruct, or when exiting one.
172                 advance_src = True
173
174         if advance_dest:
175             try:
176                 d_item = next(d_iter)
177             except StopIteration:
178                 if d_iter_list == []:
179                     # We were not in a substruct
180                     print("Section \"" + sec + "\",", end=' ')
181                     print("Description " + "\"" + desc + "\":", end=' ')
182                     print("expected field \"" + s_item["field"] + "\",", end=' ')
183                     print("while dest has no further fields")
184                     bump_taint()
185                     break
186
187                 d_iter = d_iter_list.pop()
188                 advance_src = False
189                 continue
190         else:
191             if unused_count == 0:
192                 advance_dest = True
193
194         if unused_count != 0:
195             if advance_dest == False:
196                 unused_count = unused_count - s_item["size"]
197                 if unused_count == 0:
198                     advance_dest = True
199                     continue
200                 if unused_count < 0:
201                     print("Section \"" + sec + "\",", end=' ')
202                     print("Description \"" + desc + "\":", end=' ')
203                     print("unused size mismatch near \"", end=' ')
204                     print(s_item["field"] + "\"")
205                     bump_taint()
206                     break
207                 continue
208
209             if advance_src == False:
210                 unused_count = unused_count - d_item["size"]
211                 if unused_count == 0:
212                     advance_src = True
213                     continue
214                 if unused_count < 0:
215                     print("Section \"" + sec + "\",", end=' ')
216                     print("Description \"" + desc + "\":", end=' ')
217                     print("unused size mismatch near \"", end=' ')
218                     print(d_item["field"] + "\"")
219                     bump_taint()
220                     break
221                 continue
222
223         if not check_fields_match(desc, s_item["field"], d_item["field"]):
224             # Some fields were put in substructs, keeping the
225             # on-wire format the same, but breaking static tools
226             # like this one.
227
228             # First, check if dest has a new substruct.
229             if exists_in_substruct(d_item, s_item["field"]):
230                 # listiterators don't have a prev() function, so we
231                 # have to store our current location, descend into the
232                 # substruct, and ensure we come out as if nothing
233                 # happened when the substruct is over.
234                 #
235                 # Essentially we're opening the substructs that got
236                 # added which didn't change the wire format.
237                 d_iter_list.append(d_iter)
238                 substruct_fields = d_item["Description"]["Fields"]
239                 d_iter = iter(substruct_fields)
240                 advance_src = False
241                 continue
242
243             # Next, check if src has substruct that dest removed
244             # (can happen in backward migration: 2.0 -> 1.5)
245             if exists_in_substruct(s_item, d_item["field"]):
246                 s_iter_list.append(s_iter)
247                 substruct_fields = s_item["Description"]["Fields"]
248                 s_iter = iter(substruct_fields)
249                 advance_dest = False
250                 continue
251
252             if s_item["field"] == "unused" or d_item["field"] == "unused":
253                 if s_item["size"] == d_item["size"]:
254                     continue
255
256                 if d_item["field"] == "unused":
257                     advance_dest = False
258                     unused_count = d_item["size"] - s_item["size"]
259                     continue
260
261                 if s_item["field"] == "unused":
262                     advance_src = False
263                     unused_count = s_item["size"] - d_item["size"]
264                     continue
265
266             print("Section \"" + sec + "\",", end=' ')
267             print("Description \"" + desc + "\":", end=' ')
268             print("expected field \"" + s_item["field"] + "\",", end=' ')
269             print("got \"" + d_item["field"] + "\"; skipping rest")
270             bump_taint()
271             break
272
273         check_version(s_item, d_item, sec, desc)
274
275         if not "Description" in s_item:
276             # Check size of this field only if it's not a VMSTRUCT entry
277             check_size(s_item, d_item, sec, desc, s_item["field"])
278
279         check_description_in_list(s_item, d_item, sec, desc)
280
281
282 def check_subsections(src_sub, dest_sub, desc, sec):
283     for s_item in src_sub:
284         found = False
285         for d_item in dest_sub:
286             if s_item["name"] != d_item["name"]:
287                 continue
288
289             found = True
290             check_descriptions(s_item, d_item, sec)
291
292         if not found:
293             print("Section \"" + sec + "\", Description \"" + desc + "\":", end=' ')
294             print("Subsection \"" + s_item["name"] + "\" not found")
295             bump_taint()
296
297
298 def check_description_in_list(s_item, d_item, sec, desc):
299     if not "Description" in s_item:
300         return
301
302     if not "Description" in d_item:
303         print("Section \"" + sec + "\", Description \"" + desc + "\",", end=' ')
304         print("Field \"" + s_item["field"] + "\": missing description")
305         bump_taint()
306         return
307
308     check_descriptions(s_item["Description"], d_item["Description"], sec)
309
310
311 def check_descriptions(src_desc, dest_desc, sec):
312     check_version(src_desc, dest_desc, sec, src_desc["name"])
313
314     if not check_fields_match(sec, src_desc["name"], dest_desc["name"]):
315         print("Section \"" + sec + "\":", end=' ')
316         print("Description \"" + src_desc["name"] + "\"", end=' ')
317         print("missing, got \"" + dest_desc["name"] + "\" instead; skipping")
318         bump_taint()
319         return
320
321     for f in src_desc:
322         if not f in dest_desc:
323             print("Section \"" + sec + "\"", end=' ')
324             print("Description \"" + src_desc["name"] + "\":", end=' ')
325             print("Entry \"" + f + "\" missing")
326             bump_taint()
327             continue
328
329         if f == 'Fields':
330             check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
331
332         if f == 'Subsections':
333             check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
334
335
336 def check_version(s, d, sec, desc=None):
337     if s["version_id"] > d["version_id"]:
338         print("Section \"" + sec + "\"", end=' ')
339         if desc:
340             print("Description \"" + desc + "\":", end=' ')
341         print("version error:", s["version_id"], ">", d["version_id"])
342         bump_taint()
343
344     if not "minimum_version_id" in d:
345         return
346
347     if s["version_id"] < d["minimum_version_id"]:
348         print("Section \"" + sec + "\"", end=' ')
349         if desc:
350             print("Description \"" + desc + "\":", end=' ')
351             print("minimum version error:", s["version_id"], "<", end=' ')
352             print(d["minimum_version_id"])
353             bump_taint()
354
355
356 def check_size(s, d, sec, desc=None, field=None):
357     if s["size"] != d["size"]:
358         print("Section \"" + sec + "\"", end=' ')
359         if desc:
360             print("Description \"" + desc + "\"", end=' ')
361         if field:
362             print("Field \"" + field + "\"", end=' ')
363         print("size mismatch:", s["size"], ",", d["size"])
364         bump_taint()
365
366
367 def check_machine_type(s, d):
368     if s["Name"] != d["Name"]:
369         print("Warning: checking incompatible machine types:", end=' ')
370         print("\"" + s["Name"] + "\", \"" + d["Name"] + "\"")
371     return
372
373
374 def main():
375     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
377     parser = argparse.ArgumentParser(description=help_text)
378     parser.add_argument('-s', '--src', type=file, required=True,
379                         help='json dump from src qemu')
380     parser.add_argument('-d', '--dest', type=file, required=True,
381                         help='json dump from dest qemu')
382     parser.add_argument('--reverse', required=False, default=False,
383                         action='store_true',
384                         help='reverse the direction')
385     args = parser.parse_args()
386
387     src_data = json.load(args.src)
388     dest_data = json.load(args.dest)
389     args.src.close()
390     args.dest.close()
391
392     if args.reverse:
393         temp = src_data
394         src_data = dest_data
395         dest_data = temp
396
397     for sec in src_data:
398         dest_sec = sec
399         if not dest_sec in dest_data:
400             # Either the section name got changed, or the section
401             # doesn't exist in dest.
402             dest_sec = get_changed_sec_name(sec)
403             if not dest_sec in dest_data:
404                 print("Section \"" + sec + "\" does not exist in dest")
405                 bump_taint()
406                 continue
407
408         s = src_data[sec]
409         d = dest_data[dest_sec]
410
411         if sec == "vmschkmachine":
412             check_machine_type(s, d)
413             continue
414
415         check_version(s, d, sec)
416
417         for entry in s:
418             if not entry in d:
419                 print("Section \"" + sec + "\": Entry \"" + entry + "\"", end=' ')
420                 print("missing")
421                 bump_taint()
422                 continue
423
424             if entry == "Description":
425                 check_descriptions(s[entry], d[entry], sec)
426
427     return taint
428
429
430 if __name__ == '__main__':
431     sys.exit(main())
This page took 0.050197 seconds and 4 git commands to generate.