qemu/scripts/vmstate-static-checker.py
<<
>>
Prefs
   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 = {
  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
  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 ""
 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
 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
 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:
 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
 423if __name__ == '__main__':
 424    sys.exit(main())
 425