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 'e1000': ['dev', 'parent_obj'], 46 'ehci': ['dev', 'pcidev'], 47 'I440FX': ['dev', 'parent_obj'], 48 'ich9_ahci': ['card', 'parent_obj'], 49 'ioh-3240-express-root-port': ['port.br.dev', 50 'parent_obj.parent_obj.parent_obj', 51 'port.br.dev.exp.aer_log', 52 'parent_obj.parent_obj.parent_obj.exp.aer_log'], 53 'mch': ['d', 'parent_obj'], 54 'pci_bridge': ['bridge.dev', 'parent_obj', 'bridge.dev.shpc', 'shpc'], 55 'pcnet': ['pci_dev', 'parent_obj'], 56 'PIIX3': ['pci_irq_levels', 'pci_irq_levels_vmstate'], 57 'piix4_pm': ['dev', 'parent_obj', 'pci0_status', 58 'acpi_pci_hotplug.acpi_pcihp_pci_status[0x0]'], 59 'rtl8139': ['dev', 'parent_obj'], 60 'qxl': ['num_surfaces', 'ssd.num_surfaces'], 61 'usb-host': ['dev', 'parent_obj'], 62 'usb-mouse': ['usb-ptr-queue', 'HIDPointerEventQueue'], 63 'usb-tablet': ['usb-ptr-queue', 'HIDPointerEventQueue'], 64 'xhci': ['pci_dev', 'parent_obj'], 65 'xio3130-express-downstream-port': ['port.br.dev', 66 'parent_obj.parent_obj.parent_obj', 67 'port.br.dev.exp.aer_log', 68 'parent_obj.parent_obj.parent_obj.exp.aer_log'], 69 'xio3130-express-upstream-port': ['br.dev', 'parent_obj.parent_obj', 70 'br.dev.exp.aer_log', 71 'parent_obj.parent_obj.exp.aer_log'], 72 } 73 74 if not name in changed_names: 75 return False 76 77 if s_field in changed_names[name] and d_field in changed_names[name]: 78 return True 79 80 return False 81 82def get_changed_sec_name(sec): 83 # Section names can change -- see commit 292b1634 for an example. 84 changes = { 85 "ICH9 LPC": "ICH9-LPC", 86 } 87 88 for item in changes: 89 if item == sec: 90 return changes[item] 91 if changes[item] == sec: 92 return item 93 return "" 94 95def exists_in_substruct(fields, item): 96 # Some QEMU versions moved a few fields inside a substruct. This 97 # kept the on-wire format the same. This function checks if 98 # something got shifted inside a substruct. For example, the 99 # change in commit 1f42d22233b4f3d1a2933ff30e8d6a6d9ee2d08f 100 101 if not "Description" in fields: 102 return False 103 104 if not "Fields" in fields["Description"]: 105 return False 106 107 substruct_fields = fields["Description"]["Fields"] 108 109 if substruct_fields == []: 110 return False 111 112 return check_fields_match(fields["Description"]["name"], 113 substruct_fields[0]["field"], item) 114 115 116def check_fields(src_fields, dest_fields, desc, sec): 117 # This function checks for all the fields in a section. If some 118 # fields got embedded into a substruct, this function will also 119 # attempt to check inside the substruct. 120 121 d_iter = iter(dest_fields) 122 s_iter = iter(src_fields) 123 124 # Using these lists as stacks to store previous value of s_iter 125 # and d_iter, so that when time comes to exit out of a substruct, 126 # we can go back one level up and continue from where we left off. 127 128 s_iter_list = [] 129 d_iter_list = [] 130 131 advance_src = True 132 advance_dest = True 133 134 while True: 135 if advance_src: 136 try: 137 s_item = s_iter.next() 138 except StopIteration: 139 if s_iter_list == []: 140 break 141 142 s_iter = s_iter_list.pop() 143 continue 144 else: 145 # We want to avoid advancing just once -- when entering a 146 # dest substruct, or when exiting one. 147 advance_src = True 148 149 if advance_dest: 150 try: 151 d_item = d_iter.next() 152 except StopIteration: 153 if d_iter_list == []: 154 # We were not in a substruct 155 print "Section \"" + sec + "\",", 156 print "Description " + "\"" + desc + "\":", 157 print "expected field \"" + s_item["field"] + "\",", 158 print "while dest has no further fields" 159 bump_taint() 160 break 161 162 d_iter = d_iter_list.pop() 163 advance_src = False 164 continue 165 else: 166 advance_dest = True 167 168 if not check_fields_match(desc, s_item["field"], d_item["field"]): 169 # Some fields were put in substructs, keeping the 170 # on-wire format the same, but breaking static tools 171 # like this one. 172 173 # First, check if dest has a new substruct. 174 if exists_in_substruct(d_item, s_item["field"]): 175 # listiterators don't have a prev() function, so we 176 # have to store our current location, descend into the 177 # substruct, and ensure we come out as if nothing 178 # happened when the substruct is over. 179 # 180 # Essentially we're opening the substructs that got 181 # added which didn't change the wire format. 182 d_iter_list.append(d_iter) 183 substruct_fields = d_item["Description"]["Fields"] 184 d_iter = iter(substruct_fields) 185 advance_src = False 186 continue 187 188 # Next, check if src has substruct that dest removed 189 # (can happen in backward migration: 2.0 -> 1.5) 190 if exists_in_substruct(s_item, d_item["field"]): 191 s_iter_list.append(s_iter) 192 substruct_fields = s_item["Description"]["Fields"] 193 s_iter = iter(substruct_fields) 194 advance_dest = False 195 continue 196 197 print "Section \"" + sec + "\",", 198 print "Description \"" + desc + "\":", 199 print "expected field \"" + s_item["field"] + "\",", 200 print "got \"" + d_item["field"] + "\"; skipping rest" 201 bump_taint() 202 break 203 204 check_version(s_item, d_item, sec, desc) 205 206 if not "Description" in s_item: 207 # Check size of this field only if it's not a VMSTRUCT entry 208 check_size(s_item, d_item, sec, desc, s_item["field"]) 209 210 check_description_in_list(s_item, d_item, sec, desc) 211 212 213def check_subsections(src_sub, dest_sub, desc, sec): 214 for s_item in src_sub: 215 found = False 216 for d_item in dest_sub: 217 if s_item["name"] != d_item["name"]: 218 continue 219 220 found = True 221 check_descriptions(s_item, d_item, sec) 222 223 if not found: 224 print "Section \"" + sec + "\", Description \"" + desc + "\":", 225 print "Subsection \"" + s_item["name"] + "\" not found" 226 bump_taint() 227 228 229def check_description_in_list(s_item, d_item, sec, desc): 230 if not "Description" in s_item: 231 return 232 233 if not "Description" in d_item: 234 print "Section \"" + sec + "\", Description \"" + desc + "\",", 235 print "Field \"" + s_item["field"] + "\": missing description" 236 bump_taint() 237 return 238 239 check_descriptions(s_item["Description"], d_item["Description"], sec) 240 241 242def check_descriptions(src_desc, dest_desc, sec): 243 check_version(src_desc, dest_desc, sec, src_desc["name"]) 244 245 if not check_fields_match(sec, src_desc["name"], dest_desc["name"]): 246 print "Section \"" + sec + "\":", 247 print "Description \"" + src_desc["name"] + "\"", 248 print "missing, got \"" + dest_desc["name"] + "\" instead; skipping" 249 bump_taint() 250 return 251 252 for f in src_desc: 253 if not f in dest_desc: 254 print "Section \"" + sec + "\"", 255 print "Description \"" + src_desc["name"] + "\":", 256 print "Entry \"" + f + "\" missing" 257 bump_taint() 258 continue 259 260 if f == 'Fields': 261 check_fields(src_desc[f], dest_desc[f], src_desc["name"], sec) 262 263 if f == 'Subsections': 264 check_subsections(src_desc[f], dest_desc[f], src_desc["name"], sec) 265 266 267def check_version(s, d, sec, desc=None): 268 if s["version_id"] > d["version_id"]: 269 print "Section \"" + sec + "\"", 270 if desc: 271 print "Description \"" + desc + "\":", 272 print "version error:", s["version_id"], ">", d["version_id"] 273 bump_taint() 274 275 if not "minimum_version_id" in d: 276 return 277 278 if s["version_id"] < d["minimum_version_id"]: 279 print "Section \"" + sec + "\"", 280 if desc: 281 print "Description \"" + desc + "\":", 282 print "minimum version error:", s["version_id"], "<", 283 print d["minimum_version_id"] 284 bump_taint() 285 286 287def check_size(s, d, sec, desc=None, field=None): 288 if s["size"] != d["size"]: 289 print "Section \"" + sec + "\"", 290 if desc: 291 print "Description \"" + desc + "\"", 292 if field: 293 print "Field \"" + field + "\"", 294 print "size mismatch:", s["size"], ",", d["size"] 295 bump_taint() 296 297 298def check_machine_type(s, d): 299 if s["Name"] != d["Name"]: 300 print "Warning: checking incompatible machine types:", 301 print "\"" + s["Name"] + "\", \"" + d["Name"] + "\"" 302 return 303 304 305def main(): 306 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." 307 308 parser = argparse.ArgumentParser(description=help_text) 309 parser.add_argument('-s', '--src', type=file, required=True, 310 help='json dump from src qemu') 311 parser.add_argument('-d', '--dest', type=file, required=True, 312 help='json dump from dest qemu') 313 parser.add_argument('--reverse', required=False, default=False, 314 action='store_true', 315 help='reverse the direction') 316 args = parser.parse_args() 317 318 src_data = json.load(args.src) 319 dest_data = json.load(args.dest) 320 args.src.close() 321 args.dest.close() 322 323 if args.reverse: 324 temp = src_data 325 src_data = dest_data 326 dest_data = temp 327 328 for sec in src_data: 329 dest_sec = sec 330 if not dest_sec in dest_data: 331 # Either the section name got changed, or the section 332 # doesn't exist in dest. 333 dest_sec = get_changed_sec_name(sec) 334 if not dest_sec in dest_data: 335 print "Section \"" + sec + "\" does not exist in dest" 336 bump_taint() 337 continue 338 339 s = src_data[sec] 340 d = dest_data[dest_sec] 341 342 if sec == "vmschkmachine": 343 check_machine_type(s, d) 344 continue 345 346 check_version(s, d, sec) 347 348 for entry in s: 349 if not entry in d: 350 print "Section \"" + sec + "\": Entry \"" + entry + "\"", 351 print "missing" 352 bump_taint() 353 continue 354 355 if entry == "Description": 356 check_descriptions(s[entry], d[entry], sec) 357 358 return taint 359 360 361if __name__ == '__main__': 362 sys.exit(main()) 363