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 22from __future__ import print_function 23import argparse 24import json 25import sys 26 27# Count the number of errors found 28taint = 0 29 30def 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 39def 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 104def 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 118def 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 139def 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 282def 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 298def 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 311def 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 336def 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 356def 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 367def 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 374def 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=argparse.FileType('r'), 379 required=True, 380 help='json dump from src qemu') 381 parser.add_argument('-d', '--dest', type=argparse.FileType('r'), 382 required=True, 383 help='json dump from dest qemu') 384 parser.add_argument('--reverse', required=False, default=False, 385 action='store_true', 386 help='reverse the direction') 387 args = parser.parse_args() 388 389 src_data = json.load(args.src) 390 dest_data = json.load(args.dest) 391 args.src.close() 392 args.dest.close() 393 394 if args.reverse: 395 temp = src_data 396 src_data = dest_data 397 dest_data = temp 398 399 for sec in src_data: 400 dest_sec = sec 401 if not dest_sec in dest_data: 402 # Either the section name got changed, or the section 403 # doesn't exist in dest. 404 dest_sec = get_changed_sec_name(sec) 405 if not dest_sec in dest_data: 406 print("Section \"" + sec + "\" does not exist in dest") 407 bump_taint() 408 continue 409 410 s = src_data[sec] 411 d = dest_data[dest_sec] 412 413 if sec == "vmschkmachine": 414 check_machine_type(s, d) 415 continue 416 417 check_version(s, d, sec) 418 419 for entry in s: 420 if not entry in d: 421 print("Section \"" + sec + "\": Entry \"" + entry + "\"", end=' ') 422 print("missing") 423 bump_taint() 424 continue 425 426 if entry == "Description": 427 check_descriptions(s[entry], d[entry], sec) 428 429 return taint 430 431 432if __name__ == '__main__': 433 sys.exit(main()) 434