xref: /openbmc/qemu/scripts/analyze-migration.py (revision 82565fb6b36cd37a37ff0b3380cbd58f76449957)
183de0ea4SMarc-André Lureau#!/usr/bin/env python3
2b1742570SAlexander Graf#
3b1742570SAlexander Graf#  Migration Stream Analyzer
4b1742570SAlexander Graf#
5b1742570SAlexander Graf#  Copyright (c) 2015 Alexander Graf <agraf@suse.de>
6b1742570SAlexander Graf#
7b1742570SAlexander Graf# This library is free software; you can redistribute it and/or
8b1742570SAlexander Graf# modify it under the terms of the GNU Lesser General Public
9b1742570SAlexander Graf# License as published by the Free Software Foundation; either
1061f3c91aSChetan Pant# version 2.1 of the License, or (at your option) any later version.
11b1742570SAlexander Graf#
12b1742570SAlexander Graf# This library is distributed in the hope that it will be useful,
13b1742570SAlexander Graf# but WITHOUT ANY WARRANTY; without even the implied warranty of
14b1742570SAlexander Graf# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15b1742570SAlexander Graf# Lesser General Public License for more details.
16b1742570SAlexander Graf#
17b1742570SAlexander Graf# You should have received a copy of the GNU Lesser General Public
18b1742570SAlexander Graf# License along with this library; if not, see <http://www.gnu.org/licenses/>.
19b1742570SAlexander Graf
20b1742570SAlexander Grafimport json
21b1742570SAlexander Grafimport os
22b1742570SAlexander Grafimport argparse
23b1742570SAlexander Grafimport collections
2483de0ea4SMarc-André Lureauimport struct
2583de0ea4SMarc-André Lureauimport sys
2683de0ea4SMarc-André Lureau
2783de0ea4SMarc-André Lureau
28b1742570SAlexander Grafdef mkdir_p(path):
29b1742570SAlexander Graf    try:
30b1742570SAlexander Graf        os.makedirs(path)
31b1742570SAlexander Graf    except OSError:
32b1742570SAlexander Graf        pass
33b1742570SAlexander Graf
3483de0ea4SMarc-André Lureau
35b1742570SAlexander Grafclass MigrationFile(object):
36b1742570SAlexander Graf    def __init__(self, filename):
37b1742570SAlexander Graf        self.filename = filename
38b1742570SAlexander Graf        self.file = open(self.filename, "rb")
39b1742570SAlexander Graf
40b1742570SAlexander Graf    def read64(self):
41caea0327SFabiano Rosas        return int.from_bytes(self.file.read(8), byteorder='big', signed=False)
42b1742570SAlexander Graf
43b1742570SAlexander Graf    def read32(self):
44caea0327SFabiano Rosas        return int.from_bytes(self.file.read(4), byteorder='big', signed=False)
45b1742570SAlexander Graf
46b1742570SAlexander Graf    def read16(self):
47caea0327SFabiano Rosas        return int.from_bytes(self.file.read(2), byteorder='big', signed=False)
48b1742570SAlexander Graf
49b1742570SAlexander Graf    def read8(self):
5083de0ea4SMarc-André Lureau        return int.from_bytes(self.file.read(1), byteorder='big', signed=True)
51b1742570SAlexander Graf
52b1742570SAlexander Graf    def readstr(self, len = None):
5383de0ea4SMarc-André Lureau        return self.readvar(len).decode('utf-8')
54b1742570SAlexander Graf
55b1742570SAlexander Graf    def readvar(self, size = None):
56b1742570SAlexander Graf        if size is None:
57b1742570SAlexander Graf            size = self.read8()
58b1742570SAlexander Graf        if size == 0:
59b1742570SAlexander Graf            return ""
60b1742570SAlexander Graf        value = self.file.read(size)
61b1742570SAlexander Graf        if len(value) != size:
62b1742570SAlexander Graf            raise Exception("Unexpected end of %s at 0x%x" % (self.filename, self.file.tell()))
63b1742570SAlexander Graf        return value
64b1742570SAlexander Graf
65b1742570SAlexander Graf    def tell(self):
66b1742570SAlexander Graf        return self.file.tell()
67b1742570SAlexander Graf
68e3839b0cSFabiano Rosas    def seek(self, a, b):
69e3839b0cSFabiano Rosas        return self.file.seek(a, b)
70e3839b0cSFabiano Rosas
71b1742570SAlexander Graf    # The VMSD description is at the end of the file, after EOF. Look for
72b1742570SAlexander Graf    # the last NULL byte, then for the beginning brace of JSON.
73b1742570SAlexander Graf    def read_migration_debug_json(self):
74b1742570SAlexander Graf        QEMU_VM_VMDESCRIPTION = 0x06
75b1742570SAlexander Graf
76b1742570SAlexander Graf        # Remember the offset in the file when we started
77b1742570SAlexander Graf        entrypos = self.file.tell()
78b1742570SAlexander Graf
79b1742570SAlexander Graf        # Read the last 10MB
80b1742570SAlexander Graf        self.file.seek(0, os.SEEK_END)
81b1742570SAlexander Graf        endpos = self.file.tell()
82b1742570SAlexander Graf        self.file.seek(max(-endpos, -10 * 1024 * 1024), os.SEEK_END)
83b1742570SAlexander Graf        datapos = self.file.tell()
84b1742570SAlexander Graf        data = self.file.read()
85b1742570SAlexander Graf        # The full file read closed the file as well, reopen it
86b1742570SAlexander Graf        self.file = open(self.filename, "rb")
87b1742570SAlexander Graf
88b1742570SAlexander Graf        # Find the last NULL byte, then the first brace after that. This should
89b1742570SAlexander Graf        # be the beginning of our JSON data.
9013ae8cdbSMarc-André Lureau        nulpos = data.rfind(b'\0')
9113ae8cdbSMarc-André Lureau        jsonpos = data.find(b'{', nulpos)
92b1742570SAlexander Graf
93b1742570SAlexander Graf        # Check backwards from there and see whether we guessed right
94b1742570SAlexander Graf        self.file.seek(datapos + jsonpos - 5, 0)
95b1742570SAlexander Graf        if self.read8() != QEMU_VM_VMDESCRIPTION:
96b1742570SAlexander Graf            raise Exception("No Debug Migration device found")
97b1742570SAlexander Graf
98b1742570SAlexander Graf        jsonlen = self.read32()
99b1742570SAlexander Graf
100b1742570SAlexander Graf        # Seek back to where we were at the beginning
101b1742570SAlexander Graf        self.file.seek(entrypos, 0)
102b1742570SAlexander Graf
10314f9cec7SAlexey Kirillov        # explicit decode() needed for Python 3.5 compatibility
10414f9cec7SAlexey Kirillov        return data[jsonpos:jsonpos + jsonlen].decode("utf-8")
105b1742570SAlexander Graf
106b1742570SAlexander Graf    def close(self):
107b1742570SAlexander Graf        self.file.close()
108b1742570SAlexander Graf
109b1742570SAlexander Grafclass RamSection(object):
110b1742570SAlexander Graf    RAM_SAVE_FLAG_COMPRESS = 0x02
111b1742570SAlexander Graf    RAM_SAVE_FLAG_MEM_SIZE = 0x04
112b1742570SAlexander Graf    RAM_SAVE_FLAG_PAGE     = 0x08
113b1742570SAlexander Graf    RAM_SAVE_FLAG_EOS      = 0x10
114b1742570SAlexander Graf    RAM_SAVE_FLAG_CONTINUE = 0x20
115b1742570SAlexander Graf    RAM_SAVE_FLAG_XBZRLE   = 0x40
116b1742570SAlexander Graf    RAM_SAVE_FLAG_HOOK     = 0x80
117f1de3097SMarc-André Lureau    RAM_SAVE_FLAG_COMPRESS_PAGE = 0x100
118f1de3097SMarc-André Lureau    RAM_SAVE_FLAG_MULTIFD_FLUSH = 0x200
119b1742570SAlexander Graf
120b1742570SAlexander Graf    def __init__(self, file, version_id, ramargs, section_key):
121b1742570SAlexander Graf        if version_id != 4:
122b1742570SAlexander Graf            raise Exception("Unknown RAM version %d" % version_id)
123b1742570SAlexander Graf
124b1742570SAlexander Graf        self.file = file
125b1742570SAlexander Graf        self.section_key = section_key
126b1742570SAlexander Graf        self.TARGET_PAGE_SIZE = ramargs['page_size']
127b1742570SAlexander Graf        self.dump_memory = ramargs['dump_memory']
128b1742570SAlexander Graf        self.write_memory = ramargs['write_memory']
129ff40c7f0SFabiano Rosas        self.ignore_shared = ramargs['ignore_shared']
130b1742570SAlexander Graf        self.sizeinfo = collections.OrderedDict()
131b1742570SAlexander Graf        self.data = collections.OrderedDict()
132b1742570SAlexander Graf        self.data['section sizes'] = self.sizeinfo
133b1742570SAlexander Graf        self.name = ''
134b1742570SAlexander Graf        if self.write_memory:
135b1742570SAlexander Graf            self.files = { }
136b1742570SAlexander Graf        if self.dump_memory:
137b1742570SAlexander Graf            self.memory = collections.OrderedDict()
138b1742570SAlexander Graf            self.data['memory'] = self.memory
139b1742570SAlexander Graf
140b1742570SAlexander Graf    def __repr__(self):
141b1742570SAlexander Graf        return self.data.__repr__()
142b1742570SAlexander Graf
143b1742570SAlexander Graf    def __str__(self):
144b1742570SAlexander Graf        return self.data.__str__()
145b1742570SAlexander Graf
146b1742570SAlexander Graf    def getDict(self):
147b1742570SAlexander Graf        return self.data
148b1742570SAlexander Graf
149b1742570SAlexander Graf    def read(self):
150b1742570SAlexander Graf        # Read all RAM sections
151b1742570SAlexander Graf        while True:
152b1742570SAlexander Graf            addr = self.file.read64()
153b1742570SAlexander Graf            flags = addr & (self.TARGET_PAGE_SIZE - 1)
154b1742570SAlexander Graf            addr &= ~(self.TARGET_PAGE_SIZE - 1)
155b1742570SAlexander Graf
156b1742570SAlexander Graf            if flags & self.RAM_SAVE_FLAG_MEM_SIZE:
157434b8adcSPeter Xu                total_length = addr
158434b8adcSPeter Xu                while total_length > 0:
159b1742570SAlexander Graf                    namelen = self.file.read8()
160b1742570SAlexander Graf                    self.name = self.file.readstr(len = namelen)
161b1742570SAlexander Graf                    len = self.file.read64()
162434b8adcSPeter Xu                    total_length -= len
163b1742570SAlexander Graf                    self.sizeinfo[self.name] = '0x%016x' % len
164b1742570SAlexander Graf                    if self.write_memory:
165f03868bdSEduardo Habkost                        print(self.name)
166b1742570SAlexander Graf                        mkdir_p('./' + os.path.dirname(self.name))
167b1742570SAlexander Graf                        f = open('./' + self.name, "wb")
168b1742570SAlexander Graf                        f.truncate(0)
169b1742570SAlexander Graf                        f.truncate(len)
170b1742570SAlexander Graf                        self.files[self.name] = f
171ff40c7f0SFabiano Rosas                    if self.ignore_shared:
172ff40c7f0SFabiano Rosas                        mr_addr = self.file.read64()
173b1742570SAlexander Graf                flags &= ~self.RAM_SAVE_FLAG_MEM_SIZE
174b1742570SAlexander Graf
175b1742570SAlexander Graf            if flags & self.RAM_SAVE_FLAG_COMPRESS:
176b1742570SAlexander Graf                if flags & self.RAM_SAVE_FLAG_CONTINUE:
177b1742570SAlexander Graf                    flags &= ~self.RAM_SAVE_FLAG_CONTINUE
178b1742570SAlexander Graf                else:
179b1742570SAlexander Graf                    self.name = self.file.readstr()
180b1742570SAlexander Graf                fill_char = self.file.read8()
181b1742570SAlexander Graf                # The page in question is filled with fill_char now
182b1742570SAlexander Graf                if self.write_memory and fill_char != 0:
183b1742570SAlexander Graf                    self.files[self.name].seek(addr, os.SEEK_SET)
184b1742570SAlexander Graf                    self.files[self.name].write(chr(fill_char) * self.TARGET_PAGE_SIZE)
185b1742570SAlexander Graf                if self.dump_memory:
186b1742570SAlexander Graf                    self.memory['%s (0x%016x)' % (self.name, addr)] = 'Filled with 0x%02x' % fill_char
187b1742570SAlexander Graf                flags &= ~self.RAM_SAVE_FLAG_COMPRESS
188b1742570SAlexander Graf            elif flags & self.RAM_SAVE_FLAG_PAGE:
189b1742570SAlexander Graf                if flags & self.RAM_SAVE_FLAG_CONTINUE:
190b1742570SAlexander Graf                    flags &= ~self.RAM_SAVE_FLAG_CONTINUE
191b1742570SAlexander Graf                else:
192b1742570SAlexander Graf                    self.name = self.file.readstr()
193b1742570SAlexander Graf
194b1742570SAlexander Graf                if self.write_memory or self.dump_memory:
195b1742570SAlexander Graf                    data = self.file.readvar(size = self.TARGET_PAGE_SIZE)
196b1742570SAlexander Graf                else: # Just skip RAM data
197b1742570SAlexander Graf                    self.file.file.seek(self.TARGET_PAGE_SIZE, 1)
198b1742570SAlexander Graf
199b1742570SAlexander Graf                if self.write_memory:
200b1742570SAlexander Graf                    self.files[self.name].seek(addr, os.SEEK_SET)
201b1742570SAlexander Graf                    self.files[self.name].write(data)
202b1742570SAlexander Graf                if self.dump_memory:
203b1742570SAlexander Graf                    hexdata = " ".join("{0:02x}".format(ord(c)) for c in data)
204b1742570SAlexander Graf                    self.memory['%s (0x%016x)' % (self.name, addr)] = hexdata
205b1742570SAlexander Graf
206b1742570SAlexander Graf                flags &= ~self.RAM_SAVE_FLAG_PAGE
207b1742570SAlexander Graf            elif flags & self.RAM_SAVE_FLAG_XBZRLE:
208b1742570SAlexander Graf                raise Exception("XBZRLE RAM compression is not supported yet")
209b1742570SAlexander Graf            elif flags & self.RAM_SAVE_FLAG_HOOK:
210b1742570SAlexander Graf                raise Exception("RAM hooks don't make sense with files")
211f1de3097SMarc-André Lureau            if flags & self.RAM_SAVE_FLAG_MULTIFD_FLUSH:
212f1de3097SMarc-André Lureau                continue
213b1742570SAlexander Graf
214b1742570SAlexander Graf            # End of RAM section
215b1742570SAlexander Graf            if flags & self.RAM_SAVE_FLAG_EOS:
216b1742570SAlexander Graf                break
217b1742570SAlexander Graf
218b1742570SAlexander Graf            if flags != 0:
219b1742570SAlexander Graf                raise Exception("Unknown RAM flags: %x" % flags)
220b1742570SAlexander Graf
221b1742570SAlexander Graf    def __del__(self):
222b1742570SAlexander Graf        if self.write_memory:
223b1742570SAlexander Graf            for key in self.files:
224b1742570SAlexander Graf                self.files[key].close()
225b1742570SAlexander Graf
226b1742570SAlexander Graf
227b1742570SAlexander Grafclass HTABSection(object):
228b1742570SAlexander Graf    HASH_PTE_SIZE_64       = 16
229b1742570SAlexander Graf
230b1742570SAlexander Graf    def __init__(self, file, version_id, device, section_key):
231b1742570SAlexander Graf        if version_id != 1:
232b1742570SAlexander Graf            raise Exception("Unknown HTAB version %d" % version_id)
233b1742570SAlexander Graf
234b1742570SAlexander Graf        self.file = file
235b1742570SAlexander Graf        self.section_key = section_key
236b1742570SAlexander Graf
237b1742570SAlexander Graf    def read(self):
238b1742570SAlexander Graf
239b1742570SAlexander Graf        header = self.file.read32()
240b1742570SAlexander Graf
241029ff892SLaurent Vivier        if (header == -1):
242029ff892SLaurent Vivier            # "no HPT" encoding
243029ff892SLaurent Vivier            return
244029ff892SLaurent Vivier
245b1742570SAlexander Graf        if (header > 0):
246b1742570SAlexander Graf            # First section, just the hash shift
247b1742570SAlexander Graf            return
248b1742570SAlexander Graf
249b1742570SAlexander Graf        # Read until end marker
250b1742570SAlexander Graf        while True:
251b1742570SAlexander Graf            index = self.file.read32()
252b1742570SAlexander Graf            n_valid = self.file.read16()
253b1742570SAlexander Graf            n_invalid = self.file.read16()
254b1742570SAlexander Graf
255b1742570SAlexander Graf            if index == 0 and n_valid == 0 and n_invalid == 0:
256b1742570SAlexander Graf                break
257b1742570SAlexander Graf
258be7433efSGreg Kurz            self.file.readvar(n_valid * self.HASH_PTE_SIZE_64)
259b1742570SAlexander Graf
260b1742570SAlexander Graf    def getDict(self):
261b1742570SAlexander Graf        return ""
262b1742570SAlexander Graf
26396e5c9bcSMark Cave-Ayland
26481c2c9ddSThomas Huthclass S390StorageAttributes(object):
26581c2c9ddSThomas Huth    STATTR_FLAG_EOS   = 0x01
26681c2c9ddSThomas Huth    STATTR_FLAG_MORE  = 0x02
26781c2c9ddSThomas Huth    STATTR_FLAG_ERROR = 0x04
26881c2c9ddSThomas Huth    STATTR_FLAG_DONE  = 0x08
26981c2c9ddSThomas Huth
27081c2c9ddSThomas Huth    def __init__(self, file, version_id, device, section_key):
27181c2c9ddSThomas Huth        if version_id != 0:
27281c2c9ddSThomas Huth            raise Exception("Unknown storage_attributes version %d" % version_id)
27381c2c9ddSThomas Huth
27481c2c9ddSThomas Huth        self.file = file
27581c2c9ddSThomas Huth        self.section_key = section_key
27681c2c9ddSThomas Huth
27781c2c9ddSThomas Huth    def read(self):
278e3839b0cSFabiano Rosas        pos = 0
27981c2c9ddSThomas Huth        while True:
28081c2c9ddSThomas Huth            addr_flags = self.file.read64()
28181c2c9ddSThomas Huth            flags = addr_flags & 0xfff
282e3839b0cSFabiano Rosas
283e3839b0cSFabiano Rosas            if flags & self.STATTR_FLAG_DONE:
284e3839b0cSFabiano Rosas                pos = self.file.tell()
285e3839b0cSFabiano Rosas                continue
286e3839b0cSFabiano Rosas            elif flags & self.STATTR_FLAG_EOS:
28781c2c9ddSThomas Huth                return
288e3839b0cSFabiano Rosas            else:
289e3839b0cSFabiano Rosas                # No EOS came after DONE, that's OK, but rewind the
290e3839b0cSFabiano Rosas                # stream because this is not our data.
291e3839b0cSFabiano Rosas                if pos:
292e3839b0cSFabiano Rosas                    self.file.seek(pos, os.SEEK_SET)
293e3839b0cSFabiano Rosas                    return
294e3839b0cSFabiano Rosas                raise Exception("Unknown flags %x", flags)
295e3839b0cSFabiano Rosas
29681c2c9ddSThomas Huth            if (flags & self.STATTR_FLAG_ERROR):
29781c2c9ddSThomas Huth                raise Exception("Error in migration stream")
29881c2c9ddSThomas Huth            count = self.file.read64()
29981c2c9ddSThomas Huth            self.file.readvar(count)
30081c2c9ddSThomas Huth
30181c2c9ddSThomas Huth    def getDict(self):
30281c2c9ddSThomas Huth        return ""
30381c2c9ddSThomas Huth
30481c2c9ddSThomas Huth
30596e5c9bcSMark Cave-Aylandclass ConfigurationSection(object):
306c36c31c8SFabiano Rosas    def __init__(self, file, desc):
30796e5c9bcSMark Cave-Ayland        self.file = file
308c36c31c8SFabiano Rosas        self.desc = desc
30931499a9dSFabiano Rosas        self.caps = []
31031499a9dSFabiano Rosas
31131499a9dSFabiano Rosas    def parse_capabilities(self, vmsd_caps):
31231499a9dSFabiano Rosas        if not vmsd_caps:
31331499a9dSFabiano Rosas            return
31431499a9dSFabiano Rosas
31531499a9dSFabiano Rosas        ncaps = vmsd_caps.data['caps_count'].data
31631499a9dSFabiano Rosas        self.caps = vmsd_caps.data['capabilities']
31731499a9dSFabiano Rosas
31831499a9dSFabiano Rosas        if type(self.caps) != list:
31931499a9dSFabiano Rosas            self.caps = [self.caps]
32031499a9dSFabiano Rosas
32131499a9dSFabiano Rosas        if len(self.caps) != ncaps:
32231499a9dSFabiano Rosas            raise Exception("Number of capabilities doesn't match "
32331499a9dSFabiano Rosas                            "caps_count field")
32431499a9dSFabiano Rosas
32531499a9dSFabiano Rosas    def has_capability(self, cap):
32631499a9dSFabiano Rosas        return any([str(c) == cap for c in self.caps])
32796e5c9bcSMark Cave-Ayland
32896e5c9bcSMark Cave-Ayland    def read(self):
329c36c31c8SFabiano Rosas        if self.desc:
330c36c31c8SFabiano Rosas            version_id = self.desc['version']
331c36c31c8SFabiano Rosas            section = VMSDSection(self.file, version_id, self.desc,
332c36c31c8SFabiano Rosas                                  'configuration')
333c36c31c8SFabiano Rosas            section.read()
33431499a9dSFabiano Rosas            self.parse_capabilities(
33531499a9dSFabiano Rosas                section.data.get("configuration/capabilities"))
336c36c31c8SFabiano Rosas        else:
337c36c31c8SFabiano Rosas            # backward compatibility for older streams that don't have
338c36c31c8SFabiano Rosas            # the configuration section in the json
33996e5c9bcSMark Cave-Ayland            name_len = self.file.read32()
34096e5c9bcSMark Cave-Ayland            name = self.file.readstr(len = name_len)
34196e5c9bcSMark Cave-Ayland
342b1742570SAlexander Grafclass VMSDFieldGeneric(object):
343b1742570SAlexander Graf    def __init__(self, desc, file):
344b1742570SAlexander Graf        self.file = file
345b1742570SAlexander Graf        self.desc = desc
346b1742570SAlexander Graf        self.data = ""
347b1742570SAlexander Graf
348b1742570SAlexander Graf    def __repr__(self):
349b1742570SAlexander Graf        return str(self.__str__())
350b1742570SAlexander Graf
351b1742570SAlexander Graf    def __str__(self):
35283de0ea4SMarc-André Lureau        return " ".join("{0:02x}".format(c) for c in self.data)
353b1742570SAlexander Graf
354b1742570SAlexander Graf    def getDict(self):
355b1742570SAlexander Graf        return self.__str__()
356b1742570SAlexander Graf
357b1742570SAlexander Graf    def read(self):
358b1742570SAlexander Graf        size = int(self.desc['size'])
359b1742570SAlexander Graf        self.data = self.file.readvar(size)
360b1742570SAlexander Graf        return self.data
361b1742570SAlexander Graf
36231499a9dSFabiano Rosasclass VMSDFieldCap(object):
36331499a9dSFabiano Rosas    def __init__(self, desc, file):
36431499a9dSFabiano Rosas        self.file = file
36531499a9dSFabiano Rosas        self.desc = desc
36631499a9dSFabiano Rosas        self.data = ""
36731499a9dSFabiano Rosas
36831499a9dSFabiano Rosas    def __repr__(self):
36931499a9dSFabiano Rosas        return self.data
37031499a9dSFabiano Rosas
37131499a9dSFabiano Rosas    def __str__(self):
37231499a9dSFabiano Rosas        return self.data
37331499a9dSFabiano Rosas
37431499a9dSFabiano Rosas    def read(self):
37531499a9dSFabiano Rosas        len = self.file.read8()
37631499a9dSFabiano Rosas        self.data = self.file.readstr(len)
37731499a9dSFabiano Rosas
37831499a9dSFabiano Rosas
379b1742570SAlexander Grafclass VMSDFieldInt(VMSDFieldGeneric):
380b1742570SAlexander Graf    def __init__(self, desc, file):
381b1742570SAlexander Graf        super(VMSDFieldInt, self).__init__(desc, file)
382b1742570SAlexander Graf        self.size = int(desc['size'])
383b1742570SAlexander Graf        self.format = '0x%%0%dx' % (self.size * 2)
384b1742570SAlexander Graf        self.sdtype = '>i%d' % self.size
385b1742570SAlexander Graf        self.udtype = '>u%d' % self.size
386b1742570SAlexander Graf
387b1742570SAlexander Graf    def __repr__(self):
388b1742570SAlexander Graf        if self.data < 0:
389b1742570SAlexander Graf            return ('%s (%d)' % ((self.format % self.udata), self.data))
390b1742570SAlexander Graf        else:
391b1742570SAlexander Graf            return self.format % self.data
392b1742570SAlexander Graf
393b1742570SAlexander Graf    def __str__(self):
394b1742570SAlexander Graf        return self.__repr__()
395b1742570SAlexander Graf
396b1742570SAlexander Graf    def getDict(self):
397b1742570SAlexander Graf        return self.__str__()
398b1742570SAlexander Graf
399b1742570SAlexander Graf    def read(self):
400b1742570SAlexander Graf        super(VMSDFieldInt, self).read()
40183de0ea4SMarc-André Lureau        self.sdata = int.from_bytes(self.data, byteorder='big', signed=True)
40283de0ea4SMarc-André Lureau        self.udata = int.from_bytes(self.data, byteorder='big', signed=False)
403b1742570SAlexander Graf        self.data = self.sdata
404b1742570SAlexander Graf        return self.data
405b1742570SAlexander Graf
406b1742570SAlexander Grafclass VMSDFieldUInt(VMSDFieldInt):
407b1742570SAlexander Graf    def __init__(self, desc, file):
408b1742570SAlexander Graf        super(VMSDFieldUInt, self).__init__(desc, file)
409b1742570SAlexander Graf
410b1742570SAlexander Graf    def read(self):
411b1742570SAlexander Graf        super(VMSDFieldUInt, self).read()
412b1742570SAlexander Graf        self.data = self.udata
413b1742570SAlexander Graf        return self.data
414b1742570SAlexander Graf
415b1742570SAlexander Grafclass VMSDFieldIntLE(VMSDFieldInt):
416b1742570SAlexander Graf    def __init__(self, desc, file):
417b1742570SAlexander Graf        super(VMSDFieldIntLE, self).__init__(desc, file)
418b1742570SAlexander Graf        self.dtype = '<i%d' % self.size
419b1742570SAlexander Graf
4203ba6e116SFabiano Rosasclass VMSDFieldNull(VMSDFieldGeneric):
4213ba6e116SFabiano Rosas    NULL_PTR_MARKER = b'0'
4223ba6e116SFabiano Rosas
4233ba6e116SFabiano Rosas    def __init__(self, desc, file):
4243ba6e116SFabiano Rosas        super(VMSDFieldNull, self).__init__(desc, file)
4253ba6e116SFabiano Rosas
4263ba6e116SFabiano Rosas    def __repr__(self):
4273ba6e116SFabiano Rosas        # A NULL pointer is encoded in the stream as a '0' to
4283ba6e116SFabiano Rosas        # disambiguate from a mere 0x0 value and avoid consumers
4293ba6e116SFabiano Rosas        # trying to follow the NULL pointer. Displaying '0', 0x30 or
4303ba6e116SFabiano Rosas        # 0x0 when analyzing the JSON debug stream could become
4313ba6e116SFabiano Rosas        # confusing, so use an explicit term instead.
4323ba6e116SFabiano Rosas        return "nullptr"
4333ba6e116SFabiano Rosas
4343ba6e116SFabiano Rosas    def __str__(self):
4353ba6e116SFabiano Rosas        return self.__repr__()
4363ba6e116SFabiano Rosas
4373ba6e116SFabiano Rosas    def read(self):
4383ba6e116SFabiano Rosas        super(VMSDFieldNull, self).read()
4393ba6e116SFabiano Rosas        assert(self.data == self.NULL_PTR_MARKER)
4403ba6e116SFabiano Rosas        return self.data
4413ba6e116SFabiano Rosas
442b1742570SAlexander Grafclass VMSDFieldBool(VMSDFieldGeneric):
443b1742570SAlexander Graf    def __init__(self, desc, file):
444b1742570SAlexander Graf        super(VMSDFieldBool, self).__init__(desc, file)
445b1742570SAlexander Graf
446b1742570SAlexander Graf    def __repr__(self):
447b1742570SAlexander Graf        return self.data.__repr__()
448b1742570SAlexander Graf
449b1742570SAlexander Graf    def __str__(self):
450b1742570SAlexander Graf        return self.data.__str__()
451b1742570SAlexander Graf
452b1742570SAlexander Graf    def getDict(self):
453b1742570SAlexander Graf        return self.data
454b1742570SAlexander Graf
455b1742570SAlexander Graf    def read(self):
456b1742570SAlexander Graf        super(VMSDFieldBool, self).read()
457b1742570SAlexander Graf        if self.data[0] == 0:
458b1742570SAlexander Graf            self.data = False
459b1742570SAlexander Graf        else:
460b1742570SAlexander Graf            self.data = True
461b1742570SAlexander Graf        return self.data
462b1742570SAlexander Graf
463b1742570SAlexander Grafclass VMSDFieldStruct(VMSDFieldGeneric):
464b1742570SAlexander Graf    QEMU_VM_SUBSECTION    = 0x05
465b1742570SAlexander Graf
466b1742570SAlexander Graf    def __init__(self, desc, file):
467b1742570SAlexander Graf        super(VMSDFieldStruct, self).__init__(desc, file)
468b1742570SAlexander Graf        self.data = collections.OrderedDict()
469b1742570SAlexander Graf
470ea3b8215SFabiano Rosas        if 'fields' not in self.desc['struct']:
471ea3b8215SFabiano Rosas            raise Exception("No fields in struct. VMSD:\n%s" % self.desc)
472ea3b8215SFabiano Rosas
473b1742570SAlexander Graf        # When we see compressed array elements, unfold them here
474b1742570SAlexander Graf        new_fields = []
475b1742570SAlexander Graf        for field in self.desc['struct']['fields']:
476b1742570SAlexander Graf            if not 'array_len' in field:
477b1742570SAlexander Graf                new_fields.append(field)
478b1742570SAlexander Graf                continue
479b1742570SAlexander Graf            array_len = field.pop('array_len')
480b1742570SAlexander Graf            field['index'] = 0
481b1742570SAlexander Graf            new_fields.append(field)
48283de0ea4SMarc-André Lureau            for i in range(1, array_len):
483b1742570SAlexander Graf                c = field.copy()
484b1742570SAlexander Graf                c['index'] = i
485b1742570SAlexander Graf                new_fields.append(c)
486b1742570SAlexander Graf
487b1742570SAlexander Graf        self.desc['struct']['fields'] = new_fields
488b1742570SAlexander Graf
489b1742570SAlexander Graf    def __repr__(self):
490b1742570SAlexander Graf        return self.data.__repr__()
491b1742570SAlexander Graf
492b1742570SAlexander Graf    def __str__(self):
493b1742570SAlexander Graf        return self.data.__str__()
494b1742570SAlexander Graf
495b1742570SAlexander Graf    def read(self):
496b1742570SAlexander Graf        for field in self.desc['struct']['fields']:
497b1742570SAlexander Graf            try:
498b1742570SAlexander Graf                reader = vmsd_field_readers[field['type']]
499b1742570SAlexander Graf            except:
500b1742570SAlexander Graf                reader = VMSDFieldGeneric
501b1742570SAlexander Graf
502b1742570SAlexander Graf            field['data'] = reader(field, self.file)
503b1742570SAlexander Graf            field['data'].read()
504b1742570SAlexander Graf
505*82565fb6SFabiano Rosas            fname = field['name']
506*82565fb6SFabiano Rosas            fdata = field['data']
507*82565fb6SFabiano Rosas
508*82565fb6SFabiano Rosas            # The field could be:
509*82565fb6SFabiano Rosas            # i) a single data entry, e.g. uint64
510*82565fb6SFabiano Rosas            # ii) an array, indicated by it containing the 'index' key
511*82565fb6SFabiano Rosas            #
512*82565fb6SFabiano Rosas            # However, the overall data after parsing the whole
513*82565fb6SFabiano Rosas            # stream, could be a mix of arrays and single data fields,
514*82565fb6SFabiano Rosas            # all sharing the same field name due to how QEMU breaks
515*82565fb6SFabiano Rosas            # up arrays with NULL pointers into multiple compressed
516*82565fb6SFabiano Rosas            # array segments.
517*82565fb6SFabiano Rosas            if fname not in self.data:
518*82565fb6SFabiano Rosas                self.data[fname] = fdata
519*82565fb6SFabiano Rosas            elif type(self.data[fname]) == list:
520*82565fb6SFabiano Rosas                self.data[fname].append(fdata)
521b1742570SAlexander Graf            else:
522*82565fb6SFabiano Rosas                tmp = self.data[fname]
523*82565fb6SFabiano Rosas                self.data[fname] = [tmp, fdata]
524b1742570SAlexander Graf
525b1742570SAlexander Graf        if 'subsections' in self.desc['struct']:
526b1742570SAlexander Graf            for subsection in self.desc['struct']['subsections']:
527b1742570SAlexander Graf                if self.file.read8() != self.QEMU_VM_SUBSECTION:
528b1742570SAlexander Graf                    raise Exception("Subsection %s not found at offset %x" % ( subsection['vmsd_name'], self.file.tell()))
529b1742570SAlexander Graf                name = self.file.readstr()
530b1742570SAlexander Graf                version_id = self.file.read32()
531ea3b8215SFabiano Rosas
532ea3b8215SFabiano Rosas                if not subsection:
533ea3b8215SFabiano Rosas                    raise Exception("Empty description for subsection: %s" % name)
534ea3b8215SFabiano Rosas
535b1742570SAlexander Graf                self.data[name] = VMSDSection(self.file, version_id, subsection, (name, 0))
536b1742570SAlexander Graf                self.data[name].read()
537b1742570SAlexander Graf
538b1742570SAlexander Graf    def getDictItem(self, value):
539b1742570SAlexander Graf       # Strings would fall into the array category, treat
540b1742570SAlexander Graf       # them specially
541b1742570SAlexander Graf       if value.__class__ is ''.__class__:
542b1742570SAlexander Graf           return value
543b1742570SAlexander Graf
544b1742570SAlexander Graf       try:
545b1742570SAlexander Graf           return self.getDictOrderedDict(value)
546b1742570SAlexander Graf       except:
547b1742570SAlexander Graf           try:
548b1742570SAlexander Graf               return self.getDictArray(value)
549b1742570SAlexander Graf           except:
550b1742570SAlexander Graf               try:
551b1742570SAlexander Graf                   return value.getDict()
552b1742570SAlexander Graf               except:
553b1742570SAlexander Graf                   return value
554b1742570SAlexander Graf
555b1742570SAlexander Graf    def getDictArray(self, array):
556b1742570SAlexander Graf        r = []
557b1742570SAlexander Graf        for value in array:
558b1742570SAlexander Graf           r.append(self.getDictItem(value))
559b1742570SAlexander Graf        return r
560b1742570SAlexander Graf
561b1742570SAlexander Graf    def getDictOrderedDict(self, dict):
562b1742570SAlexander Graf        r = collections.OrderedDict()
563b1742570SAlexander Graf        for (key, value) in dict.items():
564b1742570SAlexander Graf            r[key] = self.getDictItem(value)
565b1742570SAlexander Graf        return r
566b1742570SAlexander Graf
567b1742570SAlexander Graf    def getDict(self):
568b1742570SAlexander Graf        return self.getDictOrderedDict(self.data)
569b1742570SAlexander Graf
570b1742570SAlexander Grafvmsd_field_readers = {
571b1742570SAlexander Graf    "bool" : VMSDFieldBool,
572b1742570SAlexander Graf    "int8" : VMSDFieldInt,
573b1742570SAlexander Graf    "int16" : VMSDFieldInt,
574b1742570SAlexander Graf    "int32" : VMSDFieldInt,
575b1742570SAlexander Graf    "int32 equal" : VMSDFieldInt,
576b1742570SAlexander Graf    "int32 le" : VMSDFieldIntLE,
577b1742570SAlexander Graf    "int64" : VMSDFieldInt,
578b1742570SAlexander Graf    "uint8" : VMSDFieldUInt,
579b1742570SAlexander Graf    "uint16" : VMSDFieldUInt,
580b1742570SAlexander Graf    "uint32" : VMSDFieldUInt,
581b1742570SAlexander Graf    "uint32 equal" : VMSDFieldUInt,
582b1742570SAlexander Graf    "uint64" : VMSDFieldUInt,
583b1742570SAlexander Graf    "int64 equal" : VMSDFieldInt,
584b1742570SAlexander Graf    "uint8 equal" : VMSDFieldInt,
585b1742570SAlexander Graf    "uint16 equal" : VMSDFieldInt,
586b1742570SAlexander Graf    "float64" : VMSDFieldGeneric,
587b1742570SAlexander Graf    "timer" : VMSDFieldGeneric,
588b1742570SAlexander Graf    "buffer" : VMSDFieldGeneric,
589b1742570SAlexander Graf    "unused_buffer" : VMSDFieldGeneric,
590b1742570SAlexander Graf    "bitmap" : VMSDFieldGeneric,
591b1742570SAlexander Graf    "struct" : VMSDFieldStruct,
59231499a9dSFabiano Rosas    "capability": VMSDFieldCap,
5933ba6e116SFabiano Rosas    "nullptr": VMSDFieldNull,
594b1742570SAlexander Graf    "unknown" : VMSDFieldGeneric,
595b1742570SAlexander Graf}
596b1742570SAlexander Graf
597b1742570SAlexander Grafclass VMSDSection(VMSDFieldStruct):
598b1742570SAlexander Graf    def __init__(self, file, version_id, device, section_key):
599b1742570SAlexander Graf        self.file = file
600b1742570SAlexander Graf        self.data = ""
601b1742570SAlexander Graf        self.vmsd_name = ""
602b1742570SAlexander Graf        self.section_key = section_key
603b1742570SAlexander Graf        desc = device
604b1742570SAlexander Graf        if 'vmsd_name' in device:
605b1742570SAlexander Graf            self.vmsd_name = device['vmsd_name']
606b1742570SAlexander Graf
607b1742570SAlexander Graf        # A section really is nothing but a FieldStruct :)
608b1742570SAlexander Graf        super(VMSDSection, self).__init__({ 'struct' : desc }, file)
609b1742570SAlexander Graf
610b1742570SAlexander Graf###############################################################################
611b1742570SAlexander Graf
612b1742570SAlexander Grafclass MigrationDump(object):
613b1742570SAlexander Graf    QEMU_VM_FILE_MAGIC    = 0x5145564d
614b1742570SAlexander Graf    QEMU_VM_FILE_VERSION  = 0x00000003
615b1742570SAlexander Graf    QEMU_VM_EOF           = 0x00
616b1742570SAlexander Graf    QEMU_VM_SECTION_START = 0x01
617b1742570SAlexander Graf    QEMU_VM_SECTION_PART  = 0x02
618b1742570SAlexander Graf    QEMU_VM_SECTION_END   = 0x03
619b1742570SAlexander Graf    QEMU_VM_SECTION_FULL  = 0x04
620b1742570SAlexander Graf    QEMU_VM_SUBSECTION    = 0x05
621b1742570SAlexander Graf    QEMU_VM_VMDESCRIPTION = 0x06
62296e5c9bcSMark Cave-Ayland    QEMU_VM_CONFIGURATION = 0x07
62373d9a796SDr. David Alan Gilbert    QEMU_VM_SECTION_FOOTER= 0x7e
624b1742570SAlexander Graf
625b1742570SAlexander Graf    def __init__(self, filename):
62681c2c9ddSThomas Huth        self.section_classes = {
62781c2c9ddSThomas Huth            ( 'ram', 0 ) : [ RamSection, None ],
62881c2c9ddSThomas Huth            ( 's390-storage_attributes', 0 ) : [ S390StorageAttributes, None],
62981c2c9ddSThomas Huth            ( 'spapr/htab', 0) : ( HTABSection, None )
63081c2c9ddSThomas Huth        }
631b1742570SAlexander Graf        self.filename = filename
632b1742570SAlexander Graf        self.vmsd_desc = None
633ea3b8215SFabiano Rosas        self.vmsd_json = ""
634b1742570SAlexander Graf
635ea3b8215SFabiano Rosas    def read(self, desc_only = False, dump_memory = False,
636ea3b8215SFabiano Rosas             write_memory = False):
637b1742570SAlexander Graf        # Read in the whole file
638b1742570SAlexander Graf        file = MigrationFile(self.filename)
639ea3b8215SFabiano Rosas        self.vmsd_json = file.read_migration_debug_json()
640b1742570SAlexander Graf
641b1742570SAlexander Graf        # File magic
642b1742570SAlexander Graf        data = file.read32()
643b1742570SAlexander Graf        if data != self.QEMU_VM_FILE_MAGIC:
644b1742570SAlexander Graf            raise Exception("Invalid file magic %x" % data)
645b1742570SAlexander Graf
646b1742570SAlexander Graf        # Version (has to be v3)
647b1742570SAlexander Graf        data = file.read32()
648b1742570SAlexander Graf        if data != self.QEMU_VM_FILE_VERSION:
649b1742570SAlexander Graf            raise Exception("Invalid version number %d" % data)
650b1742570SAlexander Graf
651b1742570SAlexander Graf        self.load_vmsd_json(file)
652b1742570SAlexander Graf
653b1742570SAlexander Graf        # Read sections
654b1742570SAlexander Graf        self.sections = collections.OrderedDict()
655b1742570SAlexander Graf
656b1742570SAlexander Graf        if desc_only:
657b1742570SAlexander Graf            return
658b1742570SAlexander Graf
659b1742570SAlexander Graf        ramargs = {}
660b1742570SAlexander Graf        ramargs['page_size'] = self.vmsd_desc['page_size']
661b1742570SAlexander Graf        ramargs['dump_memory'] = dump_memory
662b1742570SAlexander Graf        ramargs['write_memory'] = write_memory
663ff40c7f0SFabiano Rosas        ramargs['ignore_shared'] = False
664b1742570SAlexander Graf        self.section_classes[('ram',0)][1] = ramargs
665b1742570SAlexander Graf
666b1742570SAlexander Graf        while True:
667b1742570SAlexander Graf            section_type = file.read8()
668b1742570SAlexander Graf            if section_type == self.QEMU_VM_EOF:
669b1742570SAlexander Graf                break
67096e5c9bcSMark Cave-Ayland            elif section_type == self.QEMU_VM_CONFIGURATION:
671c36c31c8SFabiano Rosas                config_desc = self.vmsd_desc.get('configuration')
672c36c31c8SFabiano Rosas                section = ConfigurationSection(file, config_desc)
67396e5c9bcSMark Cave-Ayland                section.read()
674ff40c7f0SFabiano Rosas                ramargs['ignore_shared'] = section.has_capability('x-ignore-shared')
675b1742570SAlexander Graf            elif section_type == self.QEMU_VM_SECTION_START or section_type == self.QEMU_VM_SECTION_FULL:
676b1742570SAlexander Graf                section_id = file.read32()
677b1742570SAlexander Graf                name = file.readstr()
678b1742570SAlexander Graf                instance_id = file.read32()
679b1742570SAlexander Graf                version_id = file.read32()
680b1742570SAlexander Graf                section_key = (name, instance_id)
681b1742570SAlexander Graf                classdesc = self.section_classes[section_key]
682b1742570SAlexander Graf                section = classdesc[0](file, version_id, classdesc[1], section_key)
683b1742570SAlexander Graf                self.sections[section_id] = section
684b1742570SAlexander Graf                section.read()
685b1742570SAlexander Graf            elif section_type == self.QEMU_VM_SECTION_PART or section_type == self.QEMU_VM_SECTION_END:
686b1742570SAlexander Graf                section_id = file.read32()
687b1742570SAlexander Graf                self.sections[section_id].read()
68873d9a796SDr. David Alan Gilbert            elif section_type == self.QEMU_VM_SECTION_FOOTER:
68973d9a796SDr. David Alan Gilbert                read_section_id = file.read32()
69073d9a796SDr. David Alan Gilbert                if read_section_id != section_id:
69173d9a796SDr. David Alan Gilbert                    raise Exception("Mismatched section footer: %x vs %x" % (read_section_id, section_id))
692b1742570SAlexander Graf            else:
693b1742570SAlexander Graf                raise Exception("Unknown section type: %d" % section_type)
694b1742570SAlexander Graf        file.close()
695b1742570SAlexander Graf
696b1742570SAlexander Graf    def load_vmsd_json(self, file):
697ea3b8215SFabiano Rosas        self.vmsd_desc = json.loads(self.vmsd_json,
698ea3b8215SFabiano Rosas                                    object_pairs_hook=collections.OrderedDict)
699b1742570SAlexander Graf        for device in self.vmsd_desc['devices']:
700ea3b8215SFabiano Rosas            if 'fields' not in device:
701ea3b8215SFabiano Rosas                raise Exception("vmstate for device %s has no fields" % device['name'])
702b1742570SAlexander Graf            key = (device['name'], device['instance_id'])
703b1742570SAlexander Graf            value = ( VMSDSection, device )
704b1742570SAlexander Graf            self.section_classes[key] = value
705b1742570SAlexander Graf
706b1742570SAlexander Graf    def getDict(self):
707b1742570SAlexander Graf        r = collections.OrderedDict()
708b1742570SAlexander Graf        for (key, value) in self.sections.items():
709b1742570SAlexander Graf           key = "%s (%d)" % ( value.section_key[0], key )
710b1742570SAlexander Graf           r[key] = value.getDict()
711b1742570SAlexander Graf        return r
712b1742570SAlexander Graf
713b1742570SAlexander Graf###############################################################################
714b1742570SAlexander Graf
715b1742570SAlexander Grafclass JSONEncoder(json.JSONEncoder):
716b1742570SAlexander Graf    def default(self, o):
717b1742570SAlexander Graf        if isinstance(o, VMSDFieldGeneric):
718b1742570SAlexander Graf            return str(o)
719b1742570SAlexander Graf        return json.JSONEncoder.default(self, o)
720b1742570SAlexander Graf
721b1742570SAlexander Grafparser = argparse.ArgumentParser()
722b1742570SAlexander Grafparser.add_argument("-f", "--file", help='migration dump to read from', required=True)
723b1742570SAlexander Grafparser.add_argument("-m", "--memory", help='dump RAM contents as well', action='store_true')
724b1742570SAlexander Grafparser.add_argument("-d", "--dump", help='what to dump ("state" or "desc")', default='state')
725b1742570SAlexander Grafparser.add_argument("-x", "--extract", help='extract contents into individual files', action='store_true')
726b1742570SAlexander Grafargs = parser.parse_args()
727b1742570SAlexander Graf
728b1742570SAlexander Grafjsonenc = JSONEncoder(indent=4, separators=(',', ': '))
729b1742570SAlexander Graf
730ea3b8215SFabiano Rosasif not any([args.extract, args.dump == "state", args.dump == "desc"]):
731ea3b8215SFabiano Rosas    raise Exception("Please specify either -x, -d state or -d desc")
732ea3b8215SFabiano Rosas
733ea3b8215SFabiano Rosastry:
734b1742570SAlexander Graf    dump = MigrationDump(args.file)
735b1742570SAlexander Graf
736ea3b8215SFabiano Rosas    if args.extract:
737b1742570SAlexander Graf        dump.read(desc_only = True)
738ea3b8215SFabiano Rosas
739f03868bdSEduardo Habkost        print("desc.json")
7402c92be50SLaurent Vivier        f = open("desc.json", "w")
741b1742570SAlexander Graf        f.truncate()
742b1742570SAlexander Graf        f.write(jsonenc.encode(dump.vmsd_desc))
743b1742570SAlexander Graf        f.close()
744b1742570SAlexander Graf
745b1742570SAlexander Graf        dump.read(write_memory = True)
746b1742570SAlexander Graf        dict = dump.getDict()
747f03868bdSEduardo Habkost        print("state.json")
7482c92be50SLaurent Vivier        f = open("state.json", "w")
749b1742570SAlexander Graf        f.truncate()
750b1742570SAlexander Graf        f.write(jsonenc.encode(dict))
751b1742570SAlexander Graf        f.close()
752b1742570SAlexander Graf    elif args.dump == "state":
753b1742570SAlexander Graf        dump.read(dump_memory = args.memory)
754b1742570SAlexander Graf        dict = dump.getDict()
755f03868bdSEduardo Habkost        print(jsonenc.encode(dict))
756b1742570SAlexander Graf    elif args.dump == "desc":
757b1742570SAlexander Graf        dump.read(desc_only = True)
758f03868bdSEduardo Habkost        print(jsonenc.encode(dump.vmsd_desc))
759ea3b8215SFabiano Rosasexcept Exception:
760ea3b8215SFabiano Rosas    raise Exception("Full JSON dump:\n%s", dump.vmsd_json)
761