2
0

vmstate-static-checker.py 16 KB

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