| ## @ SplitFspBin.py | |
| # | |
| # Copyright (c) 2015 - 2022, Intel Corporation. All rights reserved.<BR> | |
| # SPDX-License-Identifier: BSD-2-Clause-Patent | |
| # | |
| ## | |
| import os | |
| import sys | |
| import uuid | |
| import copy | |
| import struct | |
| import argparse | |
| from ctypes import * | |
| from functools import reduce | |
| """ | |
| This utility supports some operations for Intel FSP 1.x/2.x image. | |
| It supports: | |
| - Display FSP 1.x/2.x information header | |
| - Split FSP 2.x image into individual FSP-T/M/S/O component | |
| - Rebase FSP 1.x/2.x components to a different base address | |
| - Generate FSP 1.x/2.x mapping C header file | |
| """ | |
| CopyRightHeaderFile = """/* | |
| * | |
| * Automatically generated file; DO NOT EDIT. | |
| * FSP mapping file | |
| * | |
| */ | |
| """ | |
| class c_uint24(Structure): | |
| """Little-Endian 24-bit Unsigned Integer""" | |
| _pack_ = 1 | |
| _fields_ = [('Data', (c_uint8 * 3))] | |
| def __init__(self, val=0): | |
| self.set_value(val) | |
| def __str__(self, indent=0): | |
| return '0x%.6x' % self.value | |
| def __int__(self): | |
| return self.get_value() | |
| def set_value(self, val): | |
| self.Data[0:3] = Val2Bytes(val, 3) | |
| def get_value(self): | |
| return Bytes2Val(self.Data[0:3]) | |
| value = property(get_value, set_value) | |
| class EFI_FIRMWARE_VOLUME_HEADER(Structure): | |
| _fields_ = [ | |
| ('ZeroVector', ARRAY(c_uint8, 16)), | |
| ('FileSystemGuid', ARRAY(c_uint8, 16)), | |
| ('FvLength', c_uint64), | |
| ('Signature', ARRAY(c_char, 4)), | |
| ('Attributes', c_uint32), | |
| ('HeaderLength', c_uint16), | |
| ('Checksum', c_uint16), | |
| ('ExtHeaderOffset', c_uint16), | |
| ('Reserved', c_uint8), | |
| ('Revision', c_uint8) | |
| ] | |
| class EFI_FIRMWARE_VOLUME_EXT_HEADER(Structure): | |
| _fields_ = [ | |
| ('FvName', ARRAY(c_uint8, 16)), | |
| ('ExtHeaderSize', c_uint32) | |
| ] | |
| class EFI_FFS_INTEGRITY_CHECK(Structure): | |
| _fields_ = [ | |
| ('Header', c_uint8), | |
| ('File', c_uint8) | |
| ] | |
| class EFI_FFS_FILE_HEADER(Structure): | |
| _fields_ = [ | |
| ('Name', ARRAY(c_uint8, 16)), | |
| ('IntegrityCheck', EFI_FFS_INTEGRITY_CHECK), | |
| ('Type', c_uint8), | |
| ('Attributes', c_uint8), | |
| ('Size', c_uint24), | |
| ('State', c_uint8) | |
| ] | |
| class EFI_COMMON_SECTION_HEADER(Structure): | |
| _fields_ = [ | |
| ('Size', c_uint24), | |
| ('Type', c_uint8) | |
| ] | |
| class FSP_COMMON_HEADER(Structure): | |
| _fields_ = [ | |
| ('Signature', ARRAY(c_char, 4)), | |
| ('HeaderLength', c_uint32) | |
| ] | |
| class FSP_INFORMATION_HEADER(Structure): | |
| _fields_ = [ | |
| ('Signature', ARRAY(c_char, 4)), | |
| ('HeaderLength', c_uint32), | |
| ('Reserved1', c_uint16), | |
| ('SpecVersion', c_uint8), | |
| ('HeaderRevision', c_uint8), | |
| ('ImageRevision', c_uint32), | |
| ('ImageId', ARRAY(c_char, 8)), | |
| ('ImageSize', c_uint32), | |
| ('ImageBase', c_uint32), | |
| ('ImageAttribute', c_uint16), | |
| ('ComponentAttribute', c_uint16), | |
| ('CfgRegionOffset', c_uint32), | |
| ('CfgRegionSize', c_uint32), | |
| ('Reserved2', c_uint32), | |
| ('TempRamInitEntryOffset', c_uint32), | |
| ('Reserved3', c_uint32), | |
| ('NotifyPhaseEntryOffset', c_uint32), | |
| ('FspMemoryInitEntryOffset', c_uint32), | |
| ('TempRamExitEntryOffset', c_uint32), | |
| ('FspSiliconInitEntryOffset', c_uint32), | |
| ('FspMultiPhaseSiInitEntryOffset', c_uint32), | |
| ('ExtendedImageRevision', c_uint16), | |
| ('Reserved4', c_uint16), | |
| ('FspMultiPhaseMemInitEntryOffset', c_uint32), | |
| ('FspSmmInitEntryOffset', c_uint32) | |
| ] | |
| class FSP_PATCH_TABLE(Structure): | |
| _fields_ = [ | |
| ('Signature', ARRAY(c_char, 4)), | |
| ('HeaderLength', c_uint16), | |
| ('HeaderRevision', c_uint8), | |
| ('Reserved', c_uint8), | |
| ('PatchEntryNum', c_uint32) | |
| ] | |
| class EFI_IMAGE_DATA_DIRECTORY(Structure): | |
| _fields_ = [ | |
| ('VirtualAddress', c_uint32), | |
| ('Size', c_uint32) | |
| ] | |
| class EFI_TE_IMAGE_HEADER(Structure): | |
| _fields_ = [ | |
| ('Signature', ARRAY(c_char, 2)), | |
| ('Machine', c_uint16), | |
| ('NumberOfSections', c_uint8), | |
| ('Subsystem', c_uint8), | |
| ('StrippedSize', c_uint16), | |
| ('AddressOfEntryPoint', c_uint32), | |
| ('BaseOfCode', c_uint32), | |
| ('ImageBase', c_uint64), | |
| ('DataDirectoryBaseReloc', EFI_IMAGE_DATA_DIRECTORY), | |
| ('DataDirectoryDebug', EFI_IMAGE_DATA_DIRECTORY) | |
| ] | |
| class EFI_IMAGE_DOS_HEADER(Structure): | |
| _fields_ = [ | |
| ('e_magic', c_uint16), | |
| ('e_cblp', c_uint16), | |
| ('e_cp', c_uint16), | |
| ('e_crlc', c_uint16), | |
| ('e_cparhdr', c_uint16), | |
| ('e_minalloc', c_uint16), | |
| ('e_maxalloc', c_uint16), | |
| ('e_ss', c_uint16), | |
| ('e_sp', c_uint16), | |
| ('e_csum', c_uint16), | |
| ('e_ip', c_uint16), | |
| ('e_cs', c_uint16), | |
| ('e_lfarlc', c_uint16), | |
| ('e_ovno', c_uint16), | |
| ('e_res', ARRAY(c_uint16, 4)), | |
| ('e_oemid', c_uint16), | |
| ('e_oeminfo', c_uint16), | |
| ('e_res2', ARRAY(c_uint16, 10)), | |
| ('e_lfanew', c_uint16) | |
| ] | |
| class EFI_IMAGE_FILE_HEADER(Structure): | |
| _fields_ = [ | |
| ('Machine', c_uint16), | |
| ('NumberOfSections', c_uint16), | |
| ('TimeDateStamp', c_uint32), | |
| ('PointerToSymbolTable', c_uint32), | |
| ('NumberOfSymbols', c_uint32), | |
| ('SizeOfOptionalHeader', c_uint16), | |
| ('Characteristics', c_uint16) | |
| ] | |
| class PE_RELOC_BLOCK_HEADER(Structure): | |
| _fields_ = [ | |
| ('PageRVA', c_uint32), | |
| ('BlockSize', c_uint32) | |
| ] | |
| class EFI_IMAGE_OPTIONAL_HEADER32(Structure): | |
| _fields_ = [ | |
| ('Magic', c_uint16), | |
| ('MajorLinkerVersion', c_uint8), | |
| ('MinorLinkerVersion', c_uint8), | |
| ('SizeOfCode', c_uint32), | |
| ('SizeOfInitializedData', c_uint32), | |
| ('SizeOfUninitializedData', c_uint32), | |
| ('AddressOfEntryPoint', c_uint32), | |
| ('BaseOfCode', c_uint32), | |
| ('BaseOfData', c_uint32), | |
| ('ImageBase', c_uint32), | |
| ('SectionAlignment', c_uint32), | |
| ('FileAlignment', c_uint32), | |
| ('MajorOperatingSystemVersion', c_uint16), | |
| ('MinorOperatingSystemVersion', c_uint16), | |
| ('MajorImageVersion', c_uint16), | |
| ('MinorImageVersion', c_uint16), | |
| ('MajorSubsystemVersion', c_uint16), | |
| ('MinorSubsystemVersion', c_uint16), | |
| ('Win32VersionValue', c_uint32), | |
| ('SizeOfImage', c_uint32), | |
| ('SizeOfHeaders', c_uint32), | |
| ('CheckSum' , c_uint32), | |
| ('Subsystem', c_uint16), | |
| ('DllCharacteristics', c_uint16), | |
| ('SizeOfStackReserve', c_uint32), | |
| ('SizeOfStackCommit' , c_uint32), | |
| ('SizeOfHeapReserve', c_uint32), | |
| ('SizeOfHeapCommit' , c_uint32), | |
| ('LoaderFlags' , c_uint32), | |
| ('NumberOfRvaAndSizes', c_uint32), | |
| ('DataDirectory', ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16)) | |
| ] | |
| class EFI_IMAGE_OPTIONAL_HEADER32_PLUS(Structure): | |
| _fields_ = [ | |
| ('Magic', c_uint16), | |
| ('MajorLinkerVersion', c_uint8), | |
| ('MinorLinkerVersion', c_uint8), | |
| ('SizeOfCode', c_uint32), | |
| ('SizeOfInitializedData', c_uint32), | |
| ('SizeOfUninitializedData', c_uint32), | |
| ('AddressOfEntryPoint', c_uint32), | |
| ('BaseOfCode', c_uint32), | |
| ('ImageBase', c_uint64), | |
| ('SectionAlignment', c_uint32), | |
| ('FileAlignment', c_uint32), | |
| ('MajorOperatingSystemVersion', c_uint16), | |
| ('MinorOperatingSystemVersion', c_uint16), | |
| ('MajorImageVersion', c_uint16), | |
| ('MinorImageVersion', c_uint16), | |
| ('MajorSubsystemVersion', c_uint16), | |
| ('MinorSubsystemVersion', c_uint16), | |
| ('Win32VersionValue', c_uint32), | |
| ('SizeOfImage', c_uint32), | |
| ('SizeOfHeaders', c_uint32), | |
| ('CheckSum' , c_uint32), | |
| ('Subsystem', c_uint16), | |
| ('DllCharacteristics', c_uint16), | |
| ('SizeOfStackReserve', c_uint64), | |
| ('SizeOfStackCommit' , c_uint64), | |
| ('SizeOfHeapReserve', c_uint64), | |
| ('SizeOfHeapCommit' , c_uint64), | |
| ('LoaderFlags' , c_uint32), | |
| ('NumberOfRvaAndSizes', c_uint32), | |
| ('DataDirectory', ARRAY(EFI_IMAGE_DATA_DIRECTORY, 16)) | |
| ] | |
| class EFI_IMAGE_OPTIONAL_HEADER(Union): | |
| _fields_ = [ | |
| ('PeOptHdr', EFI_IMAGE_OPTIONAL_HEADER32), | |
| ('PePlusOptHdr', EFI_IMAGE_OPTIONAL_HEADER32_PLUS) | |
| ] | |
| class EFI_IMAGE_NT_HEADERS32(Structure): | |
| _fields_ = [ | |
| ('Signature', c_uint32), | |
| ('FileHeader', EFI_IMAGE_FILE_HEADER), | |
| ('OptionalHeader', EFI_IMAGE_OPTIONAL_HEADER) | |
| ] | |
| class EFI_IMAGE_DIRECTORY_ENTRY: | |
| EXPORT = 0 | |
| IMPORT = 1 | |
| RESOURCE = 2 | |
| EXCEPTION = 3 | |
| SECURITY = 4 | |
| BASERELOC = 5 | |
| DEBUG = 6 | |
| COPYRIGHT = 7 | |
| GLOBALPTR = 8 | |
| TLS = 9 | |
| LOAD_CONFIG = 10 | |
| class EFI_FV_FILETYPE: | |
| ALL = 0x00 | |
| RAW = 0x01 | |
| FREEFORM = 0x02 | |
| SECURITY_CORE = 0x03 | |
| PEI_CORE = 0x04 | |
| DXE_CORE = 0x05 | |
| PEIM = 0x06 | |
| DRIVER = 0x07 | |
| COMBINED_PEIM_DRIVER = 0x08 | |
| APPLICATION = 0x09 | |
| SMM = 0x0a | |
| FIRMWARE_VOLUME_IMAGE = 0x0b | |
| COMBINED_SMM_DXE = 0x0c | |
| SMM_CORE = 0x0d | |
| OEM_MIN = 0xc0 | |
| OEM_MAX = 0xdf | |
| DEBUG_MIN = 0xe0 | |
| DEBUG_MAX = 0xef | |
| FFS_MIN = 0xf0 | |
| FFS_MAX = 0xff | |
| FFS_PAD = 0xf0 | |
| class EFI_SECTION_TYPE: | |
| """Enumeration of all valid firmware file section types.""" | |
| ALL = 0x00 | |
| COMPRESSION = 0x01 | |
| GUID_DEFINED = 0x02 | |
| DISPOSABLE = 0x03 | |
| PE32 = 0x10 | |
| PIC = 0x11 | |
| TE = 0x12 | |
| DXE_DEPEX = 0x13 | |
| VERSION = 0x14 | |
| USER_INTERFACE = 0x15 | |
| COMPATIBILITY16 = 0x16 | |
| FIRMWARE_VOLUME_IMAGE = 0x17 | |
| FREEFORM_SUBTYPE_GUID = 0x18 | |
| RAW = 0x19 | |
| PEI_DEPEX = 0x1b | |
| SMM_DEPEX = 0x1c | |
| def AlignPtr (offset, alignment = 8): | |
| return (offset + alignment - 1) & ~(alignment - 1) | |
| def Bytes2Val (bytes): | |
| return reduce(lambda x,y: (x<<8)|y, bytes[::-1] ) | |
| def Val2Bytes (value, blen): | |
| return [(value>>(i*8) & 0xff) for i in range(blen)] | |
| def IsIntegerType (val): | |
| if sys.version_info[0] < 3: | |
| if type(val) in (int, long): | |
| return True | |
| else: | |
| if type(val) is int: | |
| return True | |
| return False | |
| def IsStrType (val): | |
| if sys.version_info[0] < 3: | |
| if type(val) is str: | |
| return True | |
| else: | |
| if type(val) is bytes: | |
| return True | |
| return False | |
| def HandleNameStr (val): | |
| if sys.version_info[0] < 3: | |
| rep = "0x%X ('%s')" % (Bytes2Val (bytearray (val)), val) | |
| else: | |
| rep = "0x%X ('%s')" % (Bytes2Val (bytearray (val)), str (val, 'utf-8')) | |
| return rep | |
| def OutputStruct (obj, indent = 0, plen = 0): | |
| if indent: | |
| body = '' | |
| else: | |
| body = (' ' * indent + '<%s>:\n') % obj.__class__.__name__ | |
| if plen == 0: | |
| plen = sizeof(obj) | |
| max_key_len = 26 | |
| pstr = (' ' * (indent + 1) + '{0:<%d} = {1}\n') % max_key_len | |
| for field in obj._fields_: | |
| key = field[0] | |
| val = getattr(obj, key) | |
| rep = '' | |
| if not isinstance(val, c_uint24) and isinstance(val, Structure): | |
| body += pstr.format(key, val.__class__.__name__) | |
| body += OutputStruct (val, indent + 1) | |
| plen -= sizeof(val) | |
| else: | |
| if IsStrType (val): | |
| rep = HandleNameStr (val) | |
| elif IsIntegerType (val): | |
| if (key == 'ImageRevision'): | |
| FspImageRevisionMajor = ((val >> 24) & 0xFF) | |
| FspImageRevisionMinor = ((val >> 16) & 0xFF) | |
| FspImageRevisionRevision = ((val >> 8) & 0xFF) | |
| FspImageRevisionBuildNumber = (val & 0xFF) | |
| rep = '0x%08X' % val | |
| elif (key == 'ExtendedImageRevision'): | |
| FspImageRevisionRevision |= (val & 0xFF00) | |
| FspImageRevisionBuildNumber |= ((val << 8) & 0xFF00) | |
| rep = "0x%04X ('%02X.%02X.%04X.%04X')" % (val, FspImageRevisionMajor, FspImageRevisionMinor, FspImageRevisionRevision, FspImageRevisionBuildNumber) | |
| elif field[1] == c_uint64: | |
| rep = '0x%016X' % val | |
| elif field[1] == c_uint32: | |
| rep = '0x%08X' % val | |
| elif field[1] == c_uint16: | |
| rep = '0x%04X' % val | |
| elif field[1] == c_uint8: | |
| rep = '0x%02X' % val | |
| else: | |
| rep = '0x%X' % val | |
| elif isinstance(val, c_uint24): | |
| rep = '0x%X' % val.get_value() | |
| elif 'c_ubyte_Array' in str(type(val)): | |
| if sizeof(val) == 16: | |
| if sys.version_info[0] < 3: | |
| rep = str(bytearray(val)) | |
| else: | |
| rep = bytes(val) | |
| rep = str(uuid.UUID(bytes_le = rep)).upper() | |
| else: | |
| res = ['0x%02X'%i for i in bytearray(val)] | |
| rep = '[%s]' % (','.join(res)) | |
| else: | |
| rep = str(val) | |
| plen -= sizeof(field[1]) | |
| body += pstr.format(key, rep) | |
| if plen <= 0: | |
| break | |
| return body | |
| class Section: | |
| def __init__(self, offset, secdata): | |
| self.SecHdr = EFI_COMMON_SECTION_HEADER.from_buffer (secdata, 0) | |
| self.SecData = secdata[0:int(self.SecHdr.Size)] | |
| self.Offset = offset | |
| class FirmwareFile: | |
| def __init__(self, offset, filedata): | |
| self.FfsHdr = EFI_FFS_FILE_HEADER.from_buffer (filedata, 0) | |
| self.FfsData = filedata[0:int(self.FfsHdr.Size)] | |
| self.Offset = offset | |
| self.SecList = [] | |
| def ParseFfs(self): | |
| ffssize = len(self.FfsData) | |
| offset = sizeof(self.FfsHdr) | |
| if self.FfsHdr.Name != '\xff' * 16: | |
| while offset < (ffssize - sizeof (EFI_COMMON_SECTION_HEADER)): | |
| sechdr = EFI_COMMON_SECTION_HEADER.from_buffer (self.FfsData, offset) | |
| sec = Section (offset, self.FfsData[offset:offset + int(sechdr.Size)]) | |
| self.SecList.append(sec) | |
| offset += int(sechdr.Size) | |
| offset = AlignPtr(offset, 4) | |
| class FirmwareVolume: | |
| def __init__(self, offset, fvdata): | |
| self.FvHdr = EFI_FIRMWARE_VOLUME_HEADER.from_buffer (fvdata, 0) | |
| self.FvData = fvdata[0 : self.FvHdr.FvLength] | |
| self.Offset = offset | |
| if self.FvHdr.ExtHeaderOffset > 0: | |
| self.FvExtHdr = EFI_FIRMWARE_VOLUME_EXT_HEADER.from_buffer (self.FvData, self.FvHdr.ExtHeaderOffset) | |
| else: | |
| self.FvExtHdr = None | |
| self.FfsList = [] | |
| self.ChildFvList = [] | |
| def ParseFv(self): | |
| fvsize = len(self.FvData) | |
| if self.FvExtHdr: | |
| offset = self.FvHdr.ExtHeaderOffset + self.FvExtHdr.ExtHeaderSize | |
| else: | |
| offset = self.FvHdr.HeaderLength | |
| offset = AlignPtr(offset) | |
| while offset < (fvsize - sizeof (EFI_FFS_FILE_HEADER)): | |
| ffshdr = EFI_FFS_FILE_HEADER.from_buffer (self.FvData, offset) | |
| if (ffshdr.Name == '\xff' * 16) and (int(ffshdr.Size) == 0xFFFFFF): | |
| offset = fvsize | |
| else: | |
| ffs = FirmwareFile (offset, self.FvData[offset:offset + int(ffshdr.Size)]) | |
| # check if there is child fv | |
| childfvfound = 0 | |
| if (ffs.FfsHdr.Type == EFI_FV_FILETYPE.FIRMWARE_VOLUME_IMAGE): | |
| csoffset = offset + sizeof (EFI_FFS_FILE_HEADER) | |
| csoffset = AlignPtr(csoffset, 4) | |
| # find fv section | |
| while csoffset < (offset + int(ffs.FfsHdr.Size)): | |
| cshdr = EFI_COMMON_SECTION_HEADER.from_buffer (self.FvData, csoffset) | |
| if (cshdr.Type == EFI_SECTION_TYPE.FIRMWARE_VOLUME_IMAGE): | |
| childfvfound = 1 | |
| break | |
| else: | |
| # check next section | |
| csoffset += int(cshdr.Size) | |
| csoffset = AlignPtr(csoffset, 4) | |
| if (childfvfound): | |
| childfvoffset = csoffset + sizeof (EFI_COMMON_SECTION_HEADER) | |
| childfvhdr = EFI_FIRMWARE_VOLUME_HEADER.from_buffer (self.FvData, childfvoffset) | |
| childfv = FirmwareVolume (childfvoffset, self.FvData[childfvoffset:childfvoffset + int(childfvhdr.FvLength)]) | |
| childfv.ParseFv () | |
| self.ChildFvList.append(childfv) | |
| else: | |
| ffs.ParseFfs() | |
| self.FfsList.append(ffs) | |
| offset += int(ffshdr.Size) | |
| offset = AlignPtr(offset) | |
| class FspImage: | |
| def __init__(self, offset, fih, fihoff, patch): | |
| self.Fih = fih | |
| self.FihOffset = fihoff | |
| self.Offset = offset | |
| self.FvIdxList = [] | |
| self.Type = "XTMSIXXXOXXXXXXX"[(fih.ComponentAttribute >> 12) & 0x0F] | |
| self.PatchList = patch | |
| self.PatchList.append(fihoff + 0x1C) | |
| def AppendFv(self, FvIdx): | |
| self.FvIdxList.append(FvIdx) | |
| def Patch(self, delta, fdbin): | |
| count = 0 | |
| applied = 0 | |
| for idx, patch in enumerate(self.PatchList): | |
| ptype = (patch>>24) & 0x0F | |
| if ptype not in [0x00, 0x0F]: | |
| raise Exception('ERROR: Invalid patch type %d !' % ptype) | |
| if patch & 0x80000000: | |
| patch = self.Fih.ImageSize - (0x1000000 - (patch & 0xFFFFFF)) | |
| else: | |
| patch = patch & 0xFFFFFF | |
| if (patch < self.Fih.ImageSize) and (patch + sizeof(c_uint32) <= self.Fih.ImageSize): | |
| offset = patch + self.Offset | |
| value = Bytes2Val(fdbin[offset:offset+sizeof(c_uint32)]) | |
| value += delta | |
| fdbin[offset:offset+sizeof(c_uint32)] = Val2Bytes(value, sizeof(c_uint32)) | |
| applied += 1 | |
| count += 1 | |
| # Don't count the FSP base address patch entry appended at the end | |
| if count != 0: | |
| count -= 1 | |
| applied -= 1 | |
| return (count, applied) | |
| class FirmwareDevice: | |
| def __init__(self, offset, fdfile): | |
| self.FvList = [] | |
| self.FspList = [] | |
| self.FdFile = fdfile | |
| self.Offset = 0 | |
| hfsp = open (self.FdFile, 'rb') | |
| self.FdData = bytearray(hfsp.read()) | |
| hfsp.close() | |
| def ParseFd(self): | |
| offset = 0 | |
| fdsize = len(self.FdData) | |
| self.FvList = [] | |
| while offset < (fdsize - sizeof (EFI_FIRMWARE_VOLUME_HEADER)): | |
| fvh = EFI_FIRMWARE_VOLUME_HEADER.from_buffer (self.FdData, offset) | |
| if b'_FVH' != fvh.Signature: | |
| raise Exception("ERROR: Invalid FV header !") | |
| fv = FirmwareVolume (offset, self.FdData[offset:offset + fvh.FvLength]) | |
| fv.ParseFv () | |
| self.FvList.append(fv) | |
| offset += fv.FvHdr.FvLength | |
| def CheckFsp (self): | |
| if len(self.FspList) == 0: | |
| return | |
| fih = None | |
| for fsp in self.FspList: | |
| if not fih: | |
| fih = fsp.Fih | |
| else: | |
| newfih = fsp.Fih | |
| if (newfih.ImageId != fih.ImageId) or (newfih.ImageRevision != fih.ImageRevision): | |
| raise Exception("ERROR: Inconsistent FSP ImageId or ImageRevision detected !") | |
| def ParseFsp(self): | |
| flen = 0 | |
| for idx, fv in enumerate(self.FvList): | |
| # Check if this FV contains FSP header | |
| if flen == 0: | |
| if len(fv.FfsList) == 0: | |
| continue | |
| ffs = fv.FfsList[0] | |
| if len(ffs.SecList) == 0: | |
| continue | |
| sec = ffs.SecList[0] | |
| if sec.SecHdr.Type != EFI_SECTION_TYPE.RAW: | |
| continue | |
| fihoffset = ffs.Offset + sec.Offset + sizeof(sec.SecHdr) | |
| fspoffset = fv.Offset | |
| offset = fspoffset + fihoffset | |
| fih = FSP_INFORMATION_HEADER.from_buffer (self.FdData, offset) | |
| if b'FSPH' != fih.Signature: | |
| continue | |
| offset += fih.HeaderLength | |
| offset = AlignPtr(offset, 4) | |
| plist = [] | |
| while True: | |
| fch = FSP_COMMON_HEADER.from_buffer (self.FdData, offset) | |
| if b'FSPP' != fch.Signature: | |
| offset += fch.HeaderLength | |
| offset = AlignPtr(offset, 4) | |
| else: | |
| fspp = FSP_PATCH_TABLE.from_buffer (self.FdData, offset) | |
| offset += sizeof(fspp) | |
| pdata = (c_uint32 * fspp.PatchEntryNum).from_buffer(self.FdData, offset) | |
| plist = list(pdata) | |
| break | |
| fsp = FspImage (fspoffset, fih, fihoffset, plist) | |
| fsp.AppendFv (idx) | |
| self.FspList.append(fsp) | |
| flen = fsp.Fih.ImageSize - fv.FvHdr.FvLength | |
| else: | |
| fsp.AppendFv (idx) | |
| flen -= fv.FvHdr.FvLength | |
| if flen < 0: | |
| raise Exception("ERROR: Incorrect FV size in image !") | |
| self.CheckFsp () | |
| class PeTeImage: | |
| def __init__(self, offset, data): | |
| self.Offset = offset | |
| tehdr = EFI_TE_IMAGE_HEADER.from_buffer (data, 0) | |
| if tehdr.Signature == b'VZ': # TE image | |
| self.TeHdr = tehdr | |
| elif tehdr.Signature == b'MZ': # PE image | |
| self.TeHdr = None | |
| self.DosHdr = EFI_IMAGE_DOS_HEADER.from_buffer (data, 0) | |
| self.PeHdr = EFI_IMAGE_NT_HEADERS32.from_buffer (data, self.DosHdr.e_lfanew) | |
| if self.PeHdr.Signature != 0x4550: | |
| raise Exception("ERROR: Invalid PE32 header !") | |
| if self.PeHdr.OptionalHeader.PeOptHdr.Magic == 0x10b: # PE32 image | |
| if self.PeHdr.FileHeader.SizeOfOptionalHeader < EFI_IMAGE_OPTIONAL_HEADER32.DataDirectory.offset: | |
| raise Exception("ERROR: Unsupported PE32 image !") | |
| if self.PeHdr.OptionalHeader.PeOptHdr.NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY.BASERELOC: | |
| raise Exception("ERROR: No relocation information available !") | |
| elif self.PeHdr.OptionalHeader.PeOptHdr.Magic == 0x20b: # PE32+ image | |
| if self.PeHdr.FileHeader.SizeOfOptionalHeader < EFI_IMAGE_OPTIONAL_HEADER32_PLUS.DataDirectory.offset: | |
| raise Exception("ERROR: Unsupported PE32+ image !") | |
| if self.PeHdr.OptionalHeader.PePlusOptHdr.NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY.BASERELOC: | |
| raise Exception("ERROR: No relocation information available !") | |
| else: | |
| raise Exception("ERROR: Invalid PE32 optional header !") | |
| self.Offset = offset | |
| self.Data = data | |
| self.RelocList = [] | |
| def IsTeImage(self): | |
| return self.TeHdr is not None | |
| def ParseReloc(self): | |
| if self.IsTeImage(): | |
| rsize = self.TeHdr.DataDirectoryBaseReloc.Size | |
| roffset = sizeof(self.TeHdr) - self.TeHdr.StrippedSize + self.TeHdr.DataDirectoryBaseReloc.VirtualAddress | |
| else: | |
| # Assuming PE32 image type (self.PeHdr.OptionalHeader.PeOptHdr.Magic == 0x10b) | |
| rsize = self.PeHdr.OptionalHeader.PeOptHdr.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY.BASERELOC].Size | |
| roffset = self.PeHdr.OptionalHeader.PeOptHdr.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY.BASERELOC].VirtualAddress | |
| if self.PeHdr.OptionalHeader.PePlusOptHdr.Magic == 0x20b: # PE32+ image | |
| rsize = self.PeHdr.OptionalHeader.PePlusOptHdr.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY.BASERELOC].Size | |
| roffset = self.PeHdr.OptionalHeader.PePlusOptHdr.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY.BASERELOC].VirtualAddress | |
| alignment = 4 | |
| offset = roffset | |
| while offset < roffset + rsize: | |
| offset = AlignPtr(offset, 4) | |
| blkhdr = PE_RELOC_BLOCK_HEADER.from_buffer(self.Data, offset) | |
| offset += sizeof(blkhdr) | |
| # Read relocation type,offset pairs | |
| rlen = blkhdr.BlockSize - sizeof(PE_RELOC_BLOCK_HEADER) | |
| rnum = int (rlen/sizeof(c_uint16)) | |
| rdata = (c_uint16 * rnum).from_buffer(self.Data, offset) | |
| for each in rdata: | |
| roff = each & 0xfff | |
| rtype = each >> 12 | |
| if rtype == 0: # IMAGE_REL_BASED_ABSOLUTE: | |
| continue | |
| if ((rtype != 3) and (rtype != 10)): # IMAGE_REL_BASED_HIGHLOW and IMAGE_REL_BASED_DIR64 | |
| raise Exception("ERROR: Unsupported relocation type %d!" % rtype) | |
| # Calculate the offset of the relocation | |
| aoff = blkhdr.PageRVA + roff | |
| if self.IsTeImage(): | |
| aoff += sizeof(self.TeHdr) - self.TeHdr.StrippedSize | |
| self.RelocList.append((rtype, aoff)) | |
| offset += sizeof(rdata) | |
| def Rebase(self, delta, fdbin): | |
| count = 0 | |
| if delta == 0: | |
| return count | |
| for (rtype, roff) in self.RelocList: | |
| if rtype == 3: # IMAGE_REL_BASED_HIGHLOW | |
| offset = roff + self.Offset | |
| value = Bytes2Val(fdbin[offset:offset+sizeof(c_uint32)]) | |
| value += delta | |
| fdbin[offset:offset+sizeof(c_uint32)] = Val2Bytes(value, sizeof(c_uint32)) | |
| count += 1 | |
| elif rtype == 10: # IMAGE_REL_BASED_DIR64 | |
| offset = roff + self.Offset | |
| value = Bytes2Val(fdbin[offset:offset+sizeof(c_uint64)]) | |
| value += delta | |
| fdbin[offset:offset+sizeof(c_uint64)] = Val2Bytes(value, sizeof(c_uint64)) | |
| count += 1 | |
| else: | |
| raise Exception('ERROR: Unknown relocation type %d !' % rtype) | |
| if self.IsTeImage(): | |
| offset = self.Offset + EFI_TE_IMAGE_HEADER.ImageBase.offset | |
| size = EFI_TE_IMAGE_HEADER.ImageBase.size | |
| else: | |
| offset = self.Offset + self.DosHdr.e_lfanew | |
| offset += EFI_IMAGE_NT_HEADERS32.OptionalHeader.offset | |
| if self.PeHdr.OptionalHeader.PePlusOptHdr.Magic == 0x20b: # PE32+ image | |
| offset += EFI_IMAGE_OPTIONAL_HEADER32_PLUS.ImageBase.offset | |
| size = EFI_IMAGE_OPTIONAL_HEADER32_PLUS.ImageBase.size | |
| else: | |
| offset += EFI_IMAGE_OPTIONAL_HEADER32.ImageBase.offset | |
| size = EFI_IMAGE_OPTIONAL_HEADER32.ImageBase.size | |
| value = Bytes2Val(fdbin[offset:offset+size]) + delta | |
| fdbin[offset:offset+size] = Val2Bytes(value, size) | |
| return count | |
| def ShowFspInfo (fspfile): | |
| fd = FirmwareDevice(0, fspfile) | |
| fd.ParseFd () | |
| fd.ParseFsp () | |
| print ("\nFound the following %d Firmware Volumes in FSP binary:" % (len(fd.FvList))) | |
| for idx, fv in enumerate(fd.FvList): | |
| name = fv.FvExtHdr.FvName | |
| if not name: | |
| name = '\xff' * 16 | |
| else: | |
| if sys.version_info[0] < 3: | |
| name = str(bytearray(name)) | |
| else: | |
| name = bytes(name) | |
| guid = uuid.UUID(bytes_le = name) | |
| print ("FV%d:" % idx) | |
| print (" GUID : %s" % str(guid).upper()) | |
| print (" Offset : 0x%08X" % fv.Offset) | |
| print (" Length : 0x%08X" % fv.FvHdr.FvLength) | |
| print ("\n") | |
| for fsp in fd.FspList: | |
| fvlist = map(lambda x : 'FV%d' % x, fsp.FvIdxList) | |
| print ("FSP_%s contains %s" % (fsp.Type, ','.join(fvlist))) | |
| print ("%s" % (OutputStruct(fsp.Fih, 0, fsp.Fih.HeaderLength))) | |
| def GenFspHdr (fspfile, outdir, hfile): | |
| fd = FirmwareDevice(0, fspfile) | |
| fd.ParseFd () | |
| fd.ParseFsp () | |
| if not hfile: | |
| hfile = os.path.splitext(os.path.basename(fspfile))[0] + '.h' | |
| fspname, ext = os.path.splitext(os.path.basename(hfile)) | |
| filename = os.path.join(outdir, fspname + ext) | |
| hfsp = open(filename, 'w') | |
| hfsp.write ('%s\n\n' % CopyRightHeaderFile) | |
| firstfv = True | |
| for fsp in fd.FspList: | |
| fih = fsp.Fih | |
| if firstfv: | |
| if sys.version_info[0] < 3: | |
| hfsp.write("#define FSP_IMAGE_ID 0x%016X /* '%s' */\n" % (Bytes2Val(bytearray(fih.ImageId)), fih.ImageId)) | |
| else: | |
| hfsp.write("#define FSP_IMAGE_ID 0x%016X /* '%s' */\n" % (Bytes2Val(bytearray(fih.ImageId)), str (fih.ImageId, 'utf-8'))) | |
| hfsp.write("#define FSP_IMAGE_REV 0x%08X \n\n" % fih.ImageRevision) | |
| firstfv = False | |
| fv = fd.FvList[fsp.FvIdxList[0]] | |
| hfsp.write ('#define FSP%s_BASE 0x%08X\n' % (fsp.Type, fih.ImageBase)) | |
| hfsp.write ('#define FSP%s_OFFSET 0x%08X\n' % (fsp.Type, fv.Offset)) | |
| hfsp.write ('#define FSP%s_LENGTH 0x%08X\n\n' % (fsp.Type, fih.ImageSize)) | |
| hfsp.close() | |
| def SplitFspBin (fspfile, outdir, nametemplate): | |
| fd = FirmwareDevice(0, fspfile) | |
| fd.ParseFd () | |
| fd.ParseFsp () | |
| for fsp in fd.FspList: | |
| if fsp.Fih.HeaderRevision < 3: | |
| raise Exception("ERROR: FSP 1.x is not supported by the split command !") | |
| ftype = fsp.Type | |
| if not nametemplate: | |
| nametemplate = fspfile | |
| fspname, ext = os.path.splitext(os.path.basename(nametemplate)) | |
| filename = os.path.join(outdir, fspname + '_' + fsp.Type + ext) | |
| hfsp = open(filename, 'wb') | |
| print ("Create FSP component file '%s'" % filename) | |
| for fvidx in fsp.FvIdxList: | |
| fv = fd.FvList[fvidx] | |
| hfsp.write(fv.FvData) | |
| hfsp.close() | |
| def GetImageFromFv (fd, parentfvoffset, fv, imglist): | |
| for ffs in fv.FfsList: | |
| for sec in ffs.SecList: | |
| if sec.SecHdr.Type in [EFI_SECTION_TYPE.TE, EFI_SECTION_TYPE.PE32]: # TE or PE32 | |
| offset = fd.Offset + parentfvoffset + fv.Offset + ffs.Offset + sec.Offset + sizeof(sec.SecHdr) | |
| imglist.append ((offset, len(sec.SecData) - sizeof(sec.SecHdr))) | |
| def RebaseFspBin (FspBinary, FspComponent, FspBase, OutputDir, OutputFile): | |
| fd = FirmwareDevice(0, FspBinary) | |
| fd.ParseFd () | |
| fd.ParseFsp () | |
| numcomp = len(FspComponent) | |
| baselist = FspBase | |
| if numcomp != len(baselist): | |
| print ("ERROR: Required number of base does not match number of FSP component !") | |
| return | |
| newfspbin = fd.FdData[:] | |
| for idx, fspcomp in enumerate(FspComponent): | |
| found = False | |
| for fsp in fd.FspList: | |
| # Is this FSP 1.x single binary? | |
| if fsp.Fih.HeaderRevision < 3: | |
| found = True | |
| ftype = 'X' | |
| break | |
| ftype = fsp.Type.lower() | |
| if ftype == fspcomp: | |
| found = True | |
| break | |
| if not found: | |
| print ("ERROR: Could not find FSP_%c component to rebase !" % fspcomp.upper()) | |
| return | |
| fspbase = baselist[idx] | |
| if fspbase.startswith('0x'): | |
| newbase = int(fspbase, 16) | |
| else: | |
| newbase = int(fspbase) | |
| oldbase = fsp.Fih.ImageBase | |
| delta = newbase - oldbase | |
| print ("Rebase FSP-%c from 0x%08X to 0x%08X:" % (ftype.upper(),oldbase,newbase)) | |
| imglist = [] | |
| for fvidx in fsp.FvIdxList: | |
| fv = fd.FvList[fvidx] | |
| GetImageFromFv (fd, 0, fv, imglist) | |
| # get image from child fv | |
| for childfv in fv.ChildFvList: | |
| print ("Get image from child fv of fv%d, parent fv offset: 0x%x" % (fvidx, fv.Offset)) | |
| GetImageFromFv (fd, fv.Offset, childfv, imglist) | |
| fcount = 0 | |
| pcount = 0 | |
| for (offset, length) in imglist: | |
| img = PeTeImage(offset, fd.FdData[offset:offset + length]) | |
| img.ParseReloc() | |
| pcount += img.Rebase(delta, newfspbin) | |
| fcount += 1 | |
| print (" Patched %d entries in %d TE/PE32 images." % (pcount, fcount)) | |
| (count, applied) = fsp.Patch(delta, newfspbin) | |
| print (" Patched %d entries using FSP patch table." % applied) | |
| if count != applied: | |
| print (" %d invalid entries are ignored !" % (count - applied)) | |
| if OutputFile == '': | |
| filename = os.path.basename(FspBinary) | |
| base, ext = os.path.splitext(filename) | |
| OutputFile = base + "_%08X" % newbase + ext | |
| fspname, ext = os.path.splitext(os.path.basename(OutputFile)) | |
| filename = os.path.join(OutputDir, fspname + ext) | |
| fd = open(filename, "wb") | |
| fd.write(newfspbin) | |
| fd.close() | |
| def main (): | |
| parser = argparse.ArgumentParser() | |
| subparsers = parser.add_subparsers(title='commands', dest="which") | |
| parser_rebase = subparsers.add_parser('rebase', help='rebase a FSP into a new base address') | |
| parser_rebase.set_defaults(which='rebase') | |
| parser_rebase.add_argument('-f', '--fspbin' , dest='FspBinary', type=str, help='FSP binary file path', required = True) | |
| parser_rebase.add_argument('-c', '--fspcomp', choices=['t','m','s','o','i'], nargs='+', dest='FspComponent', type=str, help='FSP component to rebase', default = "['t']", required = True) | |
| parser_rebase.add_argument('-b', '--newbase', dest='FspBase', nargs='+', type=str, help='Rebased FSP binary file name', default = '', required = True) | |
| parser_rebase.add_argument('-o', '--outdir' , dest='OutputDir', type=str, help='Output directory path', default = '.') | |
| parser_rebase.add_argument('-n', '--outfile', dest='OutputFile', type=str, help='Rebased FSP binary file name', default = '') | |
| parser_split = subparsers.add_parser('split', help='split a FSP into multiple components') | |
| parser_split.set_defaults(which='split') | |
| parser_split.add_argument('-f', '--fspbin' , dest='FspBinary', type=str, help='FSP binary file path', required = True) | |
| parser_split.add_argument('-o', '--outdir' , dest='OutputDir', type=str, help='Output directory path', default = '.') | |
| parser_split.add_argument('-n', '--nametpl', dest='NameTemplate', type=str, help='Output name template', default = '') | |
| parser_genhdr = subparsers.add_parser('genhdr', help='generate a header file for FSP binary') | |
| parser_genhdr.set_defaults(which='genhdr') | |
| parser_genhdr.add_argument('-f', '--fspbin' , dest='FspBinary', type=str, help='FSP binary file path', required = True) | |
| parser_genhdr.add_argument('-o', '--outdir' , dest='OutputDir', type=str, help='Output directory path', default = '.') | |
| parser_genhdr.add_argument('-n', '--hfile', dest='HFileName', type=str, help='Output header file name', default = '') | |
| parser_info = subparsers.add_parser('info', help='display FSP information') | |
| parser_info.set_defaults(which='info') | |
| parser_info.add_argument('-f', '--fspbin' , dest='FspBinary', type=str, help='FSP binary file path', required = True) | |
| args = parser.parse_args() | |
| if args.which in ['rebase', 'split', 'genhdr', 'info']: | |
| if not os.path.exists(args.FspBinary): | |
| raise Exception ("ERROR: Could not locate FSP binary file '%s' !" % args.FspBinary) | |
| if hasattr(args, 'OutputDir') and not os.path.exists(args.OutputDir): | |
| raise Exception ("ERROR: Invalid output directory '%s' !" % args.OutputDir) | |
| if args.which == 'rebase': | |
| RebaseFspBin (args.FspBinary, args.FspComponent, args.FspBase, args.OutputDir, args.OutputFile) | |
| elif args.which == 'split': | |
| SplitFspBin (args.FspBinary, args.OutputDir, args.NameTemplate) | |
| elif args.which == 'genhdr': | |
| GenFspHdr (args.FspBinary, args.OutputDir, args.HFileName) | |
| elif args.which == 'info': | |
| ShowFspInfo (args.FspBinary) | |
| else: | |
| parser.print_help() | |
| return 0 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |