vmstate-static-checker.py 16 KB

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