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 "e1000-82540em": "e1000", 103 } 104 105 for item in changes: 106 if item == sec: 107 return changes[item] 108 if changes[item] == sec: 109 return item 110 return "" 111 112def exists_in_substruct(fields, item): 113 # Some QEMU versions moved a few fields inside a substruct. This 114 # kept the on-wire format the same. This function checks if 115 # something got shifted inside a substruct. For example, the 116 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f 117 118 if not "Description" in fields: 119 return False 120 121 if not "Fields" in fields["Description"]: 122 return False 123 124 substruct_fields = fields["Description"]["Fields"] 125 126 if substruct_fields == []: 127 return False 128 129 return check_fields_match(fields["Description"]["name"], 130 substruct_fields[0]["field"], item) 131 132 133def check_fields(src_fields, dest_fields, desc, sec): 134 # This function checks for all the fields in a section. If some 135 # fields got embedded into a substruct, this function will also 136 # attempt to check inside the substruct. 137 138 d_iter = iter(dest_fields) 139 s_iter = iter(src_fields) 140 141 # Using these lists as stacks to store previous value of s_iter 142 # and d_iter, so that when time comes to exit out of a substruct, 143 # we can go back one level up and continue from where we left off. 144 145 s_iter_list = [] 146 d_iter_list = [] 147 148 advance_src = True 149 advance_dest = True 150 unused_count = 0 151 152 while True: 153 if advance_src: 154 try: 155 s_item = s_iter.next() 156 except StopIteration: 157 if s_iter_list == []: 158 break 159 160 s_iter = s_iter_list.pop() 161 continue 162 else: 163 if unused_count == 0: 164 # We want to avoid advancing just once -- when entering a 165 # dest substruct, or when exiting one. 166 advance_src = True 167 168 if advance_dest: 169 try: 170 d_item = d_iter.next() 171 except StopIteration: 172 if d_iter_list == []: 173 # We were not in a substruct 174 print "Section \"" + sec + "\",", 175 print "Description " + "\"" + desc + "\":", 176 print "expected field \"" + s_item["field"] + "\",", 177 print "while dest has no further fields" 178 bump_taint() 179 break 180 181 d_iter = d_iter_list.pop() 182 advance_src = False 183 continue 184 else: 185 if unused_count == 0: 186 advance_dest = True 187 188 if unused_count != 0: 189 if advance_dest == False: 190 unused_count = unused_count - s_item["size"] 191 if unused_count == 0: 192 advance_dest = True 193 continue 194 if unused_count < 0: 195 print "Section \"" + sec + "\",", 196 print "Description \"" + desc + "\":", 197 print "unused size mismatch near \"", 198 print s_item["field"] + "\"" 199 bump_taint() 200 break 201 continue 202 203 if advance_src == False: 204 unused_count = unused_count - d_item["size"] 205 if unused_count == 0: 206 advance_src = True 207 continue 208 if unused_count < 0: 209 print "Section \"" + sec + "\",", 210 print "Description \"" + desc + "\":", 211 print "unused size mismatch near \"", 212 print d_item["field"] + "\"" 213 bump_taint() 214 break 215 continue 216 217 if not check_fields_match(desc, s_item["field"], d_item["field"]): 218 # Some fields were put in substructs, keeping the 219 # on-wire format the same, but breaking static tools 220 # like this one. 221 222 # First, check if dest has a new substruct. 223 if exists_in_substruct(d_item, s_item["field"]): 224 # listiterators don't have a prev() function, so we 225 # have to store our current location, descend into the 226 # substruct, and ensure we come out as if nothing 227 # happened when the substruct is over. 228 # 229 # Essentially we're opening the substructs that got 230 # added which didn't change the wire format. 231 d_iter_list.append(d_iter) 232 substruct_fields = d_item["Description"]["Fields"] 233 d_iter = iter(substruct_fields) 234 advance_src = False 235 continue 236 237 # Next, check if src has substruct that dest removed 238 # (can happen in backward migration: 2.0 -> 1.5) 239 if exists_in_substruct(s_item, d_item["field"]): 240 s_iter_list.append(s_iter) 241 substruct_fields = s_item["Description"]["Fields"] 242 s_iter = iter(substruct_fields) 243 advance_dest = False 244 continue 245 246 if s_item["field"] == "unused" or d_item["field"] == "unused": 247 if s_item["size"] == d_item["size"]: 248 continue 249 250 if d_item["field"] == "unused": 251 advance_dest = False 252 unused_count = d_item["size"] - s_item["size"] 253 continue 254 255 if s_item["field"] == "unused": 256 advance_src = False 257 unused_count = s_item["size"] - d_item["size"] 258 continue 259 260 print "Section \"" + sec + "\",", 261 print "Description \"" + desc + "\":", 262 print "expected field \"" + s_item["field"] + "\",", 263 print "got \"" + d_item["field"] + "\"; skipping rest" 264 bump_taint() 265 break 266 267 check_version(s_item, d_item, sec, desc) 268 269 if not "Description" in s_item: 270 # Check size of this field only if it's not a VMSTRUCT entry 271 check_size(s_item, d_item, sec, desc, s_item["field"]) 272 273 check_description_in_list(s_item, d_item, sec, desc) 274 275 276def check_subsections(src_sub, dest_sub, desc, sec): 277 for s_item in src_sub: 278 found = False 279 for d_item in dest_sub: 280 if s_item["name"] != d_item["name"]: 281 continue 282 283 found = True 284 check_descriptions(s_item, d_item, sec) 285 286 if not found: 287 print "Section \"" + sec + "\", Description \"" + desc + "\":", 288 print "Subsection \"" + s_item["name"] + "\" not found" 289 bump_taint() 290 291 292def check_description_in_list(s_item, d_item, sec, desc): 293 if not "Description" in s_item: 294 return 295 296 if not "Description" in d_item: 297 print "Section \"" + sec + "\", Description \"" + desc + "\",", 298 print "Field \"" + s_item["field"] + "\": missing description" 299 bump_taint() 300 return 301 302 check_descriptions(s_item["Description"], d_item["Description"], sec) 303 304 305def check_descriptions(src_desc, dest_desc, sec): 306 check_version(src_desc, dest_desc, sec, src_desc["name"]) 307 308 if not check_fields_match(sec, src_desc["name"], dest_desc["name"]): 309 print "Section \"" + sec + "\":", 310 print "Description \"" + src_desc["name"] + "\"", 311 print "missing, got \"" + dest_desc["name"] + "\" instead; skipping" 312 bump_taint() 313 return 314 315 for f in src_desc: 316 if not f in dest_desc: 317 print "Section \"" + sec + "\"", 318 print "Description \"" + src_desc["name"] + "\":", 319 print "Entry \"" + f + "\" missing" 320 bump_taint() 321 continue 322 323 if f == 'Fields': 324 check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec) 325 326 if f == 'Subsections': 327 check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec) 328 329 330def check_version(s, d, sec, desc=None): 331 if s["version_id"] > d["version_id"]: 332 print "Section \"" + sec + "\"", 333 if desc: 334 print "Description \"" + desc + "\":", 335 print "version error:", s["version_id"], ">", d["version_id"] 336 bump_taint() 337 338 if not "minimum_version_id" in d: 339 return 340 341 if s["version_id"] < d["minimum_version_id"]: 342 print "Section \"" + sec + "\"", 343 if desc: 344 print "Description \"" + desc + "\":", 345 print "minimum version error:", s["version_id"], "<", 346 print d["minimum_version_id"] 347 bump_taint() 348 349 350def check_size(s, d, sec, desc=None, field=None): 351 if s["size"] != d["size"]: 352 print "Section \"" + sec + "\"", 353 if desc: 354 print "Description \"" + desc + "\"", 355 if field: 356 print "Field \"" + field + "\"", 357 print "size mismatch:", s["size"], ",", d["size"] 358 bump_taint() 359 360 361def check_machine_type(s, d): 362 if s["Name"] != d["Name"]: 363 print "Warning: checking incompatible machine types:", 364 print "\"" + s["Name"] + "\", \"" + d["Name"] + "\"" 365 return 366 367 368def main(): 369 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." 370 371 parser = argparse.ArgumentParser(description=help_text) 372 parser.add_argument('-s', '--src', type=file, required=True, 373 help='json dump from src qemu') 374 parser.add_argument('-d', '--dest', type=file, required=True, 375 help='json dump from dest qemu') 376 parser.add_argument('--reverse', required=False, default=False, 377 action='store_true', 378 help='reverse the direction') 379 args = parser.parse_args() 380 381 src_data = json.load(args.src) 382 dest_data = json.load(args.dest) 383 args.src.close() 384 args.dest.close() 385 386 if args.reverse: 387 temp = src_data 388 src_data = dest_data 389 dest_data = temp 390 391 for sec in src_data: 392 dest_sec = sec 393 if not dest_sec in dest_data: 394 # Either the section name got changed, or the section 395 # doesn't exist in dest. 396 dest_sec = get_changed_sec_name(sec) 397 if not dest_sec in dest_data: 398 print "Section \"" + sec + "\" does not exist in dest" 399 bump_taint() 400 continue 401 402 s = src_data[sec] 403 d = dest_data[dest_sec] 404 405 if sec == "vmschkmachine": 406 check_machine_type(s, d) 407 continue 408 409 check_version(s, d, sec) 410 411 for entry in s: 412 if not entry in d: 413 print "Section \"" + sec + "\": Entry \"" + entry + "\"", 414 print "missing" 415 bump_taint() 416 continue 417 418 if entry == "Description": 419 check_descriptions(s[entry], d[entry], sec) 420 421 return taint 422 423 424if __name__ == '__main__': 425 sys.exit(main()) 426