qemu/scripts/vmstate-static-checker.py
<<
>>
Prefs
   1#!/usr/bin/env python3
   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        '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    }
  94
  95    if not name in changed_names:
  96        return False
  97
  98    if s_field in changed_names[name] and d_field in changed_names[name]:
  99        return True
 100
 101    return False
 102
 103def 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    }
 109
 110    for item in changes:
 111        if item == sec:
 112            return changes[item]
 113        if changes[item] == sec:
 114            return item
 115    return ""
 116
 117def 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
 122
 123    if not "Description" in fields:
 124        return False
 125
 126    if not "Fields" in fields["Description"]:
 127        return False
 128
 129    substruct_fields = fields["Description"]["Fields"]
 130
 131    if substruct_fields == []:
 132        return False
 133
 134    return check_fields_match(fields["Description"]["name"],
 135                              substruct_fields[0]["field"], item)
 136
 137
 138def 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.
 142
 143    d_iter = iter(dest_fields)
 144    s_iter = iter(src_fields)
 145
 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.
 149
 150    s_iter_list = []
 151    d_iter_list = []
 152
 153    advance_src = True
 154    advance_dest = True
 155    unused_count = 0
 156
 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
 164
 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
 172
 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
 185
 186                d_iter = d_iter_list.pop()
 187                advance_src = False
 188                continue
 189        else:
 190            if unused_count == 0:
 191                advance_dest = True
 192
 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
 207
 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
 221
 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.
 226
 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
 241
 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
 250
 251            if s_item["field"] == "unused" or d_item["field"] == "unused":
 252                if s_item["size"] == d_item["size"]:
 253                    continue
 254
 255                if d_item["field"] == "unused":
 256                    advance_dest = False
 257                    unused_count = d_item["size"] - s_item["size"]
 258                    continue
 259
 260                if s_item["field"] == "unused":
 261                    advance_src = False
 262                    unused_count = s_item["size"] - d_item["size"]
 263                    continue
 264
 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
 271
 272        check_version(s_item, d_item, sec, desc)
 273
 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"])
 277
 278        check_description_in_list(s_item, d_item, sec, desc)
 279
 280
 281def 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
 287
 288            found = True
 289            check_descriptions(s_item, d_item, sec)
 290
 291        if not found:
 292            print("Section \"" + sec + "\", Description \"" + desc + "\":", end=' ')
 293            print("Subsection \"" + s_item["name"] + "\" not found")
 294            bump_taint()
 295
 296
 297def check_description_in_list(s_item, d_item, sec, desc):
 298    if not "Description" in s_item:
 299        return
 300
 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
 306
 307    check_descriptions(s_item["Description"], d_item["Description"], sec)
 308
 309
 310def check_descriptions(src_desc, dest_desc, sec):
 311    check_version(src_desc, dest_desc, sec, src_desc["name"])
 312
 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
 319
 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
 327
 328        if f == 'Fields':
 329            check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec)
 330
 331        if f == 'Subsections':
 332            check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec)
 333
 334
 335def 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()
 342
 343    if not "minimum_version_id" in d:
 344        return
 345
 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()
 353
 354
 355def 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()
 364
 365
 366def 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
 371
 372
 373def 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."
 375
 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()
 387
 388    src_data = json.load(args.src)
 389    dest_data = json.load(args.dest)
 390    args.src.close()
 391    args.dest.close()
 392
 393    if args.reverse:
 394        temp = src_data
 395        src_data = dest_data
 396        dest_data = temp
 397
 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
 408
 409        s = src_data[sec]
 410        d = dest_data[dest_sec]
 411
 412        if sec == "vmschkmachine":
 413            check_machine_type(s, d)
 414            continue
 415
 416        check_version(s, d, sec)
 417
 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
 424
 425            if entry == "Description":
 426                check_descriptions(s[entry], d[entry], sec)
 427
 428    return taint
 429
 430
 431if __name__ == '__main__':
 432    sys.exit(main())
 433