mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 02:30:03 +01:00
These aren't runable scripts, so shouldn't have shebangs. Signed-off-by: Andrew Cooper <andrew.cooper3@citrix.com> Acked-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
529 lines
17 KiB
Python
529 lines
17 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Libxc Migration v2 streams
|
|
|
|
Record structures as per docs/specs/libxc-migration-stream.pandoc, and
|
|
verification routines.
|
|
"""
|
|
|
|
import sys
|
|
|
|
from struct import calcsize, unpack
|
|
|
|
from xen.migration.verify import StreamError, RecordError, VerifyBase
|
|
|
|
# Image Header
|
|
IHDR_FORMAT = "!QIIHHI"
|
|
|
|
IHDR_MARKER = 0xffffffffffffffff
|
|
IHDR_IDENT = 0x58454E46 # "XENF" in ASCII
|
|
|
|
IHDR_OPT_BIT_ENDIAN = 0
|
|
IHDR_OPT_LE = (0 << IHDR_OPT_BIT_ENDIAN)
|
|
IHDR_OPT_BE = (1 << IHDR_OPT_BIT_ENDIAN)
|
|
|
|
IHDR_OPT_RESZ_MASK = 0xfffe
|
|
|
|
# Domain Header
|
|
DHDR_FORMAT = "IHHII"
|
|
|
|
DHDR_TYPE_x86_pv = 0x00000001
|
|
DHDR_TYPE_x86_hvm = 0x00000002
|
|
|
|
dhdr_type_to_str = {
|
|
DHDR_TYPE_x86_pv : "x86 PV",
|
|
DHDR_TYPE_x86_hvm : "x86 HVM",
|
|
}
|
|
|
|
# Records
|
|
RH_FORMAT = "II"
|
|
|
|
REC_TYPE_end = 0x00000000
|
|
REC_TYPE_page_data = 0x00000001
|
|
REC_TYPE_x86_pv_info = 0x00000002
|
|
REC_TYPE_x86_pv_p2m_frames = 0x00000003
|
|
REC_TYPE_x86_pv_vcpu_basic = 0x00000004
|
|
REC_TYPE_x86_pv_vcpu_extended = 0x00000005
|
|
REC_TYPE_x86_pv_vcpu_xsave = 0x00000006
|
|
REC_TYPE_shared_info = 0x00000007
|
|
REC_TYPE_tsc_info = 0x00000008
|
|
REC_TYPE_hvm_context = 0x00000009
|
|
REC_TYPE_hvm_params = 0x0000000a
|
|
REC_TYPE_toolstack = 0x0000000b
|
|
REC_TYPE_x86_pv_vcpu_msrs = 0x0000000c
|
|
REC_TYPE_verify = 0x0000000d
|
|
REC_TYPE_checkpoint = 0x0000000e
|
|
REC_TYPE_checkpoint_dirty_pfn_list = 0x0000000f
|
|
REC_TYPE_static_data_end = 0x00000010
|
|
REC_TYPE_x86_cpuid_policy = 0x00000011
|
|
REC_TYPE_x86_msr_policy = 0x00000012
|
|
|
|
rec_type_to_str = {
|
|
REC_TYPE_end : "End",
|
|
REC_TYPE_page_data : "Page data",
|
|
REC_TYPE_x86_pv_info : "x86 PV info",
|
|
REC_TYPE_x86_pv_p2m_frames : "x86 PV P2M frames",
|
|
REC_TYPE_x86_pv_vcpu_basic : "x86 PV vcpu basic",
|
|
REC_TYPE_x86_pv_vcpu_extended : "x86 PV vcpu extended",
|
|
REC_TYPE_x86_pv_vcpu_xsave : "x86 PV vcpu xsave",
|
|
REC_TYPE_shared_info : "Shared info",
|
|
REC_TYPE_tsc_info : "TSC info",
|
|
REC_TYPE_hvm_context : "HVM context",
|
|
REC_TYPE_hvm_params : "HVM params",
|
|
REC_TYPE_toolstack : "Toolstack",
|
|
REC_TYPE_x86_pv_vcpu_msrs : "x86 PV vcpu msrs",
|
|
REC_TYPE_verify : "Verify",
|
|
REC_TYPE_checkpoint : "Checkpoint",
|
|
REC_TYPE_checkpoint_dirty_pfn_list : "Checkpoint dirty pfn list",
|
|
REC_TYPE_static_data_end : "Static data end",
|
|
REC_TYPE_x86_cpuid_policy : "x86 CPUID policy",
|
|
REC_TYPE_x86_msr_policy : "x86 MSR policy",
|
|
}
|
|
|
|
# page_data
|
|
PAGE_DATA_FORMAT = "II"
|
|
PAGE_DATA_PFN_MASK = (1 << 52) - 1
|
|
PAGE_DATA_PFN_RESZ_MASK = ((1 << 60) - 1) & ~((1 << 52) - 1)
|
|
|
|
# flags from xen/public/domctl.h: XEN_DOMCTL_PFINFO_* shifted by 32 bits
|
|
PAGE_DATA_TYPE_SHIFT = 60
|
|
PAGE_DATA_TYPE_LTABTYPE_MASK = (0x7 << PAGE_DATA_TYPE_SHIFT)
|
|
PAGE_DATA_TYPE_LTAB_MASK = (0xf << PAGE_DATA_TYPE_SHIFT)
|
|
PAGE_DATA_TYPE_LPINTAB = (0x8 << PAGE_DATA_TYPE_SHIFT) # Pinned pagetable
|
|
|
|
PAGE_DATA_TYPE_NOTAB = (0x0 << PAGE_DATA_TYPE_SHIFT) # Regular page
|
|
PAGE_DATA_TYPE_L1TAB = (0x1 << PAGE_DATA_TYPE_SHIFT) # L1 pagetable
|
|
PAGE_DATA_TYPE_L2TAB = (0x2 << PAGE_DATA_TYPE_SHIFT) # L2 pagetable
|
|
PAGE_DATA_TYPE_L3TAB = (0x3 << PAGE_DATA_TYPE_SHIFT) # L3 pagetable
|
|
PAGE_DATA_TYPE_L4TAB = (0x4 << PAGE_DATA_TYPE_SHIFT) # L4 pagetable
|
|
PAGE_DATA_TYPE_BROKEN = (0xd << PAGE_DATA_TYPE_SHIFT) # Broken
|
|
PAGE_DATA_TYPE_XALLOC = (0xe << PAGE_DATA_TYPE_SHIFT) # Allocate-only
|
|
PAGE_DATA_TYPE_XTAB = (0xf << PAGE_DATA_TYPE_SHIFT) # Invalid
|
|
|
|
# x86_pv_info
|
|
X86_PV_INFO_FORMAT = "BBHI"
|
|
|
|
X86_PV_P2M_FRAMES_FORMAT = "II"
|
|
|
|
# x86_pv_vcpu_{basic,extended,xsave,msrs}
|
|
X86_PV_VCPU_HDR_FORMAT = "II"
|
|
|
|
# x86_tsc_info
|
|
X86_TSC_INFO_FORMAT = "IIQII"
|
|
|
|
# hvm_params
|
|
HVM_PARAMS_ENTRY_FORMAT = "QQ"
|
|
HVM_PARAMS_FORMAT = "II"
|
|
|
|
# x86_cpuid_policy => xen_cpuid_leaf_t[]
|
|
X86_CPUID_POLICY_FORMAT = "IIIIII"
|
|
|
|
# x86_msr_policy => xen_msr_entry_t[]
|
|
X86_MSR_POLICY_FORMAT = "QII"
|
|
|
|
class VerifyLibxc(VerifyBase):
|
|
""" Verify a Libxc v2 (or later) stream """
|
|
|
|
def __init__(self, info, read):
|
|
VerifyBase.__init__(self, info, read)
|
|
|
|
self.version = 0
|
|
self.squashed_pagedata_records = 0
|
|
|
|
|
|
def verify(self):
|
|
""" Verity a libxc stream """
|
|
|
|
self.verify_ihdr()
|
|
self.verify_dhdr()
|
|
|
|
while self.verify_record() != REC_TYPE_end:
|
|
pass
|
|
|
|
|
|
def verify_ihdr(self):
|
|
""" Verify an Image Header """
|
|
marker, ident, version, options, res1, res2 = \
|
|
self.unpack_exact(IHDR_FORMAT)
|
|
|
|
if marker != IHDR_MARKER:
|
|
raise StreamError("Bad image marker: Expected 0x%x, got 0x%x" %
|
|
(IHDR_MARKER, marker))
|
|
|
|
if ident != IHDR_IDENT:
|
|
raise StreamError("Bad image id: Expected 0x%x, got 0x%x" %
|
|
(IHDR_IDENT, ident))
|
|
|
|
if not (2 <= version <= 3):
|
|
raise StreamError(
|
|
"Unknown image version: Expected 2 <= ver <= 3, got %d" %
|
|
(version, ))
|
|
|
|
self.version = version
|
|
|
|
if options & IHDR_OPT_RESZ_MASK:
|
|
raise StreamError("Reserved bits set in image options field: 0x%x" %
|
|
(options & IHDR_OPT_RESZ_MASK))
|
|
|
|
if res1 != 0 or res2 != 0:
|
|
raise StreamError(
|
|
"Reserved bits set in image header: 0x%04x:0x%08x" %
|
|
(res1, res2))
|
|
|
|
if ( (sys.byteorder == "little") and
|
|
((options & IHDR_OPT_BIT_ENDIAN) != IHDR_OPT_LE) ):
|
|
raise StreamError(
|
|
"Stream is not native endianess - unable to validate")
|
|
|
|
endian = ["little", "big"][options & IHDR_OPT_LE]
|
|
self.info("Libxc Image Header: Version %d, %s endian" %
|
|
(version, endian))
|
|
|
|
|
|
def verify_dhdr(self):
|
|
""" Verify a domain header """
|
|
|
|
gtype, page_shift, res1, major, minor = \
|
|
self.unpack_exact(DHDR_FORMAT)
|
|
|
|
if gtype not in dhdr_type_to_str:
|
|
raise StreamError("Unrecognised domain type 0x%x" % (gtype, ))
|
|
|
|
if res1 != 0:
|
|
raise StreamError("Reserved bits set in domain header 0x%04x" %
|
|
(res1, ))
|
|
|
|
if page_shift != 12:
|
|
raise StreamError("Page shift expected to be 12. Got %d" %
|
|
(page_shift, ))
|
|
|
|
if major == 0:
|
|
self.info("Domain Header: legacy converted %s" %
|
|
(dhdr_type_to_str[gtype], ))
|
|
else:
|
|
self.info("Domain Header: %s from Xen %d.%d" %
|
|
(dhdr_type_to_str[gtype], major, minor))
|
|
|
|
|
|
def verify_record(self):
|
|
""" Verify an individual record """
|
|
|
|
rtype, length = self.unpack_exact(RH_FORMAT)
|
|
|
|
if rtype not in rec_type_to_str:
|
|
raise StreamError("Unrecognised record type 0x%x" % (rtype, ))
|
|
|
|
contentsz = (length + 7) & ~7
|
|
content = self.rdexact(contentsz)
|
|
|
|
if rtype != REC_TYPE_page_data:
|
|
|
|
if self.squashed_pagedata_records > 0:
|
|
self.info("Squashed %d Page Data records together" %
|
|
(self.squashed_pagedata_records, ))
|
|
self.squashed_pagedata_records = 0
|
|
|
|
self.info("Libxc Record: %s, length %d" %
|
|
(rec_type_to_str[rtype], length))
|
|
|
|
else:
|
|
self.squashed_pagedata_records += 1
|
|
|
|
padding = content[length:]
|
|
if padding != b"\x00" * len(padding):
|
|
raise StreamError("Padding containing non0 bytes found")
|
|
|
|
if rtype not in record_verifiers:
|
|
raise RuntimeError(
|
|
"No verification function for libxc record '%s'" %
|
|
rec_type_to_str[rtype])
|
|
else:
|
|
record_verifiers[rtype](self, content[:length])
|
|
|
|
return rtype
|
|
|
|
|
|
def verify_record_end(self, content):
|
|
""" End record """
|
|
|
|
if len(content) != 0:
|
|
raise RecordError("End record with non-zero length")
|
|
|
|
|
|
def verify_record_page_data(self, content):
|
|
""" Page Data record """
|
|
minsz = calcsize(PAGE_DATA_FORMAT)
|
|
|
|
if len(content) <= minsz:
|
|
raise RecordError(
|
|
"PAGE_DATA record must be at least %d bytes long" % (minsz, ))
|
|
|
|
count, res1 = unpack(PAGE_DATA_FORMAT, content[:minsz])
|
|
|
|
if res1 != 0:
|
|
raise StreamError(
|
|
"Reserved bits set in PAGE_DATA record 0x%04x" % (res1, ))
|
|
|
|
pfnsz = count * 8
|
|
if (len(content) - minsz) < pfnsz:
|
|
raise RecordError(
|
|
"PAGE_DATA record must contain a pfn record for each count")
|
|
|
|
pfns = list(unpack("=%dQ" % (count, ), content[minsz:minsz + pfnsz]))
|
|
|
|
nr_pages = 0
|
|
for idx, pfn in enumerate(pfns):
|
|
|
|
if pfn & PAGE_DATA_PFN_RESZ_MASK:
|
|
raise RecordError("Reserved bits set in pfn[%d]: 0x%016x" %
|
|
(idx, pfn & PAGE_DATA_PFN_RESZ_MASK))
|
|
|
|
if pfn >> PAGE_DATA_TYPE_SHIFT in (5, 6, 7, 8):
|
|
raise RecordError("Invalid type value in pfn[%d]: 0x%016x" %
|
|
(idx, pfn & PAGE_DATA_TYPE_LTAB_MASK))
|
|
|
|
# We expect page data for each normal page or pagetable
|
|
if PAGE_DATA_TYPE_NOTAB <= (pfn & PAGE_DATA_TYPE_LTABTYPE_MASK) \
|
|
<= PAGE_DATA_TYPE_L4TAB:
|
|
nr_pages += 1
|
|
|
|
pagesz = nr_pages * 4096
|
|
if len(content) != minsz + pfnsz + pagesz:
|
|
raise RecordError("Expected %u + %u + %u, got %u" %
|
|
(minsz, pfnsz, pagesz, len(content)))
|
|
|
|
|
|
def verify_record_x86_pv_info(self, content):
|
|
""" x86 PV Info record """
|
|
|
|
expectedsz = calcsize(X86_PV_INFO_FORMAT)
|
|
if len(content) != expectedsz:
|
|
raise RecordError("x86_pv_info: expected length of %d, got %d" %
|
|
(expectedsz, len(content)))
|
|
|
|
width, levels, res1, res2 = unpack(X86_PV_INFO_FORMAT, content)
|
|
|
|
if width not in (4, 8):
|
|
raise RecordError("Expected width of 4 or 8, got %d" % (width, ))
|
|
|
|
if levels not in (3, 4):
|
|
raise RecordError("Expected levels of 3 or 4, got %d" % (levels, ))
|
|
|
|
if res1 != 0 or res2 != 0:
|
|
raise StreamError(
|
|
"Reserved bits set in X86_PV_INFO: 0x%04x 0x%08x" %
|
|
(res1, res2))
|
|
|
|
bitness = {4:32, 8:64}[width]
|
|
self.info(" %sbit guest, %d levels of pagetables" % (bitness, levels))
|
|
|
|
|
|
def verify_record_x86_pv_p2m_frames(self, content):
|
|
""" x86 PV p2m frames record """
|
|
|
|
if len(content) < 8:
|
|
raise RecordError("x86_pv_p2m_frames: record length must be at"
|
|
" least 8 bytes long")
|
|
|
|
if len(content) % 8 != 0:
|
|
raise RecordError("Length expected to be a multiple of 8, not %d" %
|
|
(len(content), ))
|
|
|
|
start, end = unpack("=II", content[:8])
|
|
self.info(" Start pfn 0x%x, End 0x%x" % (start, end))
|
|
|
|
|
|
def verify_record_x86_pv_vcpu_generic(self, content, name):
|
|
""" Generic for all REC_TYPE_x86_pv_vcpu_{basic,extended,xsave,msrs} """
|
|
minsz = calcsize(X86_PV_VCPU_HDR_FORMAT)
|
|
|
|
if len(content) < minsz:
|
|
raise RecordError(
|
|
"X86_PV_VCPU_%s record length must be at least %d bytes long" %
|
|
(name, minsz))
|
|
|
|
if len(content) == minsz:
|
|
self.info("Warning: X86_PV_VCPU_%s record with zero content" %
|
|
(name, ))
|
|
|
|
vcpuid, res1 = unpack(X86_PV_VCPU_HDR_FORMAT, content[:minsz])
|
|
|
|
if res1 != 0:
|
|
raise StreamError(
|
|
"Reserved bits set in x86_pv_vcpu_%s record 0x%04x" %
|
|
(name, res1))
|
|
|
|
self.info(" vcpu%d %s context, %d bytes" %
|
|
(vcpuid, name, len(content) - minsz))
|
|
|
|
|
|
def verify_record_shared_info(self, content):
|
|
""" shared info record """
|
|
|
|
contentsz = len(content)
|
|
if contentsz != 4096:
|
|
raise RecordError("Length expected to be 4906 bytes, not %d" %
|
|
(contentsz, ))
|
|
|
|
|
|
def verify_record_tsc_info(self, content):
|
|
""" tsc info record """
|
|
|
|
sz = calcsize(X86_TSC_INFO_FORMAT)
|
|
|
|
if len(content) != sz:
|
|
raise RecordError("Length should be %u bytes" % (sz, ))
|
|
|
|
mode, khz, nsec, incarn, res1 = unpack(X86_TSC_INFO_FORMAT, content)
|
|
|
|
if res1 != 0:
|
|
raise StreamError("Reserved bits set in X86_TSC_INFO: 0x%08x" %
|
|
(res1, ))
|
|
|
|
self.info(" Mode %u, %u kHz, %u ns, incarnation %d" %
|
|
(mode, khz, nsec, incarn))
|
|
|
|
|
|
def verify_record_hvm_context(self, content):
|
|
""" hvm context record """
|
|
|
|
if len(content) == 0:
|
|
raise RecordError("Zero length HVM context")
|
|
|
|
|
|
def verify_record_hvm_params(self, content):
|
|
""" hvm params record """
|
|
|
|
sz = calcsize(HVM_PARAMS_FORMAT)
|
|
|
|
if len(content) < sz:
|
|
raise RecordError("Length should be at least %u bytes" % (sz, ))
|
|
|
|
count, rsvd = unpack(HVM_PARAMS_FORMAT, content[:sz])
|
|
|
|
if rsvd != 0:
|
|
raise RecordError("Reserved field not zero (0x%04x)" % (rsvd, ))
|
|
|
|
if count == 0:
|
|
self.info("Warning: HVM_PARAMS record with zero content")
|
|
|
|
sz += count * calcsize(HVM_PARAMS_ENTRY_FORMAT)
|
|
|
|
if len(content) != sz:
|
|
raise RecordError("Length should be %u bytes" % (sz, ))
|
|
|
|
|
|
def verify_record_toolstack(self, _):
|
|
""" toolstack record """
|
|
raise DeprecationWarning("Found Toolstack record in stream")
|
|
|
|
|
|
def verify_record_verify(self, content):
|
|
""" verify record """
|
|
|
|
if len(content) != 0:
|
|
raise RecordError("Verify record with non-zero length")
|
|
|
|
|
|
def verify_record_checkpoint(self, content):
|
|
""" checkpoint record """
|
|
|
|
if len(content) != 0:
|
|
raise RecordError("Checkpoint record with non-zero length")
|
|
|
|
|
|
def verify_record_checkpoint_dirty_pfn_list(self, content):
|
|
""" checkpoint dirty pfn list """
|
|
raise RecordError("Found checkpoint dirty pfn list record in stream")
|
|
|
|
|
|
def verify_record_static_data_end(self, content):
|
|
""" static data end record """
|
|
|
|
if len(content) != 0:
|
|
raise RecordError("End record with non-zero length")
|
|
|
|
if self.version < 3:
|
|
raise RecordError("Static data end record found in v2 stream")
|
|
|
|
|
|
def verify_record_x86_cpuid_policy(self, content):
|
|
""" x86 CPUID policy record """
|
|
|
|
if self.version < 3:
|
|
raise RecordError("x86 CPUID policy record found in v2 stream")
|
|
|
|
sz = calcsize(X86_CPUID_POLICY_FORMAT)
|
|
contentsz = len(content)
|
|
|
|
if contentsz < sz or (contentsz % sz) != 0:
|
|
raise RecordError("Record length %u, expected multiple of %u" %
|
|
(contentsz, sz))
|
|
|
|
|
|
def verify_record_x86_msr_policy(self, content):
|
|
""" x86 MSR policy record """
|
|
|
|
if self.version < 3:
|
|
raise RecordError("x86 MSR policy record found in v2 stream")
|
|
|
|
sz = calcsize(X86_MSR_POLICY_FORMAT)
|
|
contentsz = len(content)
|
|
|
|
if contentsz < sz or (contentsz % sz) != 0:
|
|
raise RecordError("Record length %u, expected multiple of %u" %
|
|
(contentsz, sz))
|
|
|
|
|
|
record_verifiers = {
|
|
REC_TYPE_end:
|
|
VerifyLibxc.verify_record_end,
|
|
REC_TYPE_page_data:
|
|
VerifyLibxc.verify_record_page_data,
|
|
|
|
REC_TYPE_x86_pv_info:
|
|
VerifyLibxc.verify_record_x86_pv_info,
|
|
REC_TYPE_x86_pv_p2m_frames:
|
|
VerifyLibxc.verify_record_x86_pv_p2m_frames,
|
|
|
|
REC_TYPE_x86_pv_vcpu_basic:
|
|
lambda s, x:
|
|
VerifyLibxc.verify_record_x86_pv_vcpu_generic(s, x, "basic"),
|
|
REC_TYPE_x86_pv_vcpu_extended:
|
|
lambda s, x:
|
|
VerifyLibxc.verify_record_x86_pv_vcpu_generic(s, x, "extended"),
|
|
REC_TYPE_x86_pv_vcpu_xsave:
|
|
lambda s, x:
|
|
VerifyLibxc.verify_record_x86_pv_vcpu_generic(s, x, "xsave"),
|
|
REC_TYPE_x86_pv_vcpu_msrs:
|
|
lambda s, x:
|
|
VerifyLibxc.verify_record_x86_pv_vcpu_generic(s, x, "msrs"),
|
|
|
|
REC_TYPE_shared_info:
|
|
VerifyLibxc.verify_record_shared_info,
|
|
REC_TYPE_tsc_info:
|
|
VerifyLibxc.verify_record_tsc_info,
|
|
|
|
REC_TYPE_hvm_context:
|
|
VerifyLibxc.verify_record_hvm_context,
|
|
REC_TYPE_hvm_params:
|
|
VerifyLibxc.verify_record_hvm_params,
|
|
REC_TYPE_toolstack:
|
|
VerifyLibxc.verify_record_toolstack,
|
|
REC_TYPE_verify:
|
|
VerifyLibxc.verify_record_verify,
|
|
REC_TYPE_checkpoint:
|
|
VerifyLibxc.verify_record_checkpoint,
|
|
REC_TYPE_checkpoint_dirty_pfn_list:
|
|
VerifyLibxc.verify_record_checkpoint_dirty_pfn_list,
|
|
|
|
REC_TYPE_static_data_end:
|
|
VerifyLibxc.verify_record_static_data_end,
|
|
|
|
REC_TYPE_x86_cpuid_policy:
|
|
VerifyLibxc.verify_record_x86_cpuid_policy,
|
|
REC_TYPE_x86_msr_policy:
|
|
VerifyLibxc.verify_record_x86_msr_policy,
|
|
}
|