## @file # This file is used to generate DEPEX file for module's dependency expression # # Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.
# SPDX-License-Identifier: BSD-2-Clause-Patent ## Import Modules # import sys import Common.LongFilePathOs as os import re import traceback from Common.LongFilePathSupport import OpenLongFilePath as open from io import BytesIO from struct import pack from Common.BuildToolError import * from Common.Misc import SaveFileOnChange from Common.Misc import GuidStructureStringToGuidString from Common.Misc import GuidStructureByteArrayToGuidString from Common.Misc import GuidStringToGuidStructureString from Common import EdkLogger as EdkLogger from Common.BuildVersion import gBUILD_VERSION from Common.DataType import * ## Regular expression for matching "DEPENDENCY_START ... DEPENDENCY_END" gStartClosePattern = re.compile(".*DEPENDENCY_START(.+)DEPENDENCY_END.*", re.S) ## Mapping between module type and EFI phase gType2Phase = { SUP_MODULE_BASE : None, SUP_MODULE_SEC : "PEI", SUP_MODULE_PEI_CORE : "PEI", SUP_MODULE_PEIM : "PEI", SUP_MODULE_DXE_CORE : "DXE", SUP_MODULE_DXE_DRIVER : "DXE", SUP_MODULE_DXE_SMM_DRIVER : "DXE", SUP_MODULE_DXE_RUNTIME_DRIVER: "DXE", SUP_MODULE_DXE_SAL_DRIVER : "DXE", SUP_MODULE_UEFI_DRIVER : "DXE", SUP_MODULE_UEFI_APPLICATION : "DXE", SUP_MODULE_SMM_CORE : "DXE", SUP_MODULE_MM_STANDALONE : "MM", SUP_MODULE_MM_CORE_STANDALONE : "MM", } ## Convert dependency expression string into EFI internal representation # # DependencyExpression class is used to parse dependency expression string and # convert it into its binary form. # class DependencyExpression: ArchProtocols = { '665e3ff6-46cc-11d4-9a38-0090273fc14d', # 'gEfiBdsArchProtocolGuid' '26baccb1-6f42-11d4-bce7-0080c73c8881', # 'gEfiCpuArchProtocolGuid' '26baccb2-6f42-11d4-bce7-0080c73c8881', # 'gEfiMetronomeArchProtocolGuid' '1da97072-bddc-4b30-99f1-72a0b56fff2a', # 'gEfiMonotonicCounterArchProtocolGuid' '27cfac87-46cc-11d4-9a38-0090273fc14d', # 'gEfiRealTimeClockArchProtocolGuid' '27cfac88-46cc-11d4-9a38-0090273fc14d', # 'gEfiResetArchProtocolGuid' 'b7dfb4e1-052f-449f-87be-9818fc91b733', # 'gEfiRuntimeArchProtocolGuid' 'a46423e3-4617-49f1-b9ff-d1bfa9115839', # 'gEfiSecurityArchProtocolGuid' '26baccb3-6f42-11d4-bce7-0080c73c8881', # 'gEfiTimerArchProtocolGuid' '6441f818-6362-4e44-b570-7dba31dd2453', # 'gEfiVariableWriteArchProtocolGuid' '1e5668e2-8481-11d4-bcf1-0080c73c8881', # 'gEfiVariableArchProtocolGuid' '665e3ff5-46cc-11d4-9a38-0090273fc14d' # 'gEfiWatchdogTimerArchProtocolGuid' } OpcodePriority = { DEPEX_OPCODE_AND : 1, DEPEX_OPCODE_OR : 1, DEPEX_OPCODE_NOT : 2, } Opcode = { "PEI" : { DEPEX_OPCODE_PUSH : 0x02, DEPEX_OPCODE_AND : 0x03, DEPEX_OPCODE_OR : 0x04, DEPEX_OPCODE_NOT : 0x05, DEPEX_OPCODE_TRUE : 0x06, DEPEX_OPCODE_FALSE : 0x07, DEPEX_OPCODE_END : 0x08 }, "DXE" : { DEPEX_OPCODE_BEFORE: 0x00, DEPEX_OPCODE_AFTER : 0x01, DEPEX_OPCODE_PUSH : 0x02, DEPEX_OPCODE_AND : 0x03, DEPEX_OPCODE_OR : 0x04, DEPEX_OPCODE_NOT : 0x05, DEPEX_OPCODE_TRUE : 0x06, DEPEX_OPCODE_FALSE : 0x07, DEPEX_OPCODE_END : 0x08, DEPEX_OPCODE_SOR : 0x09 }, "MM" : { DEPEX_OPCODE_BEFORE: 0x00, DEPEX_OPCODE_AFTER : 0x01, DEPEX_OPCODE_PUSH : 0x02, DEPEX_OPCODE_AND : 0x03, DEPEX_OPCODE_OR : 0x04, DEPEX_OPCODE_NOT : 0x05, DEPEX_OPCODE_TRUE : 0x06, DEPEX_OPCODE_FALSE : 0x07, DEPEX_OPCODE_END : 0x08, DEPEX_OPCODE_SOR : 0x09 } } # all supported op codes and operands SupportedOpcode = [DEPEX_OPCODE_BEFORE, DEPEX_OPCODE_AFTER, DEPEX_OPCODE_PUSH, DEPEX_OPCODE_AND, DEPEX_OPCODE_OR, DEPEX_OPCODE_NOT, DEPEX_OPCODE_END, DEPEX_OPCODE_SOR] SupportedOperand = [DEPEX_OPCODE_TRUE, DEPEX_OPCODE_FALSE] OpcodeWithSingleOperand = [DEPEX_OPCODE_NOT, DEPEX_OPCODE_BEFORE, DEPEX_OPCODE_AFTER] OpcodeWithTwoOperand = [DEPEX_OPCODE_AND, DEPEX_OPCODE_OR] # op code that should not be the last one NonEndingOpcode = [DEPEX_OPCODE_AND, DEPEX_OPCODE_OR, DEPEX_OPCODE_NOT, DEPEX_OPCODE_SOR] # op code must not present at the same time ExclusiveOpcode = [DEPEX_OPCODE_BEFORE, DEPEX_OPCODE_AFTER] # op code that should be the first one if it presents AboveAllOpcode = [DEPEX_OPCODE_SOR, DEPEX_OPCODE_BEFORE, DEPEX_OPCODE_AFTER] # # open and close brace must be taken as individual tokens # TokenPattern = re.compile("(\(|\)|\{[^{}]+\{?[^{}]+\}?[ ]*\}|\w+)") ## Constructor # # @param Expression The list or string of dependency expression # @param ModuleType The type of the module using the dependency expression # def __init__(self, Expression, ModuleType, Optimize=False): self.ModuleType = ModuleType self.Phase = gType2Phase[ModuleType] if isinstance(Expression, type([])): self.ExpressionString = " ".join(Expression) self.TokenList = Expression else: self.ExpressionString = Expression self.GetExpressionTokenList() self.PostfixNotation = [] self.OpcodeList = [] self.GetPostfixNotation() self.ValidateOpcode() EdkLogger.debug(EdkLogger.DEBUG_8, repr(self)) if Optimize: self.Optimize() EdkLogger.debug(EdkLogger.DEBUG_8, "\n Optimized: " + repr(self)) def __str__(self): return " ".join(self.TokenList) def __repr__(self): WellForm = '' for Token in self.PostfixNotation: if Token in self.SupportedOpcode: WellForm += "\n " + Token else: WellForm += ' ' + Token return WellForm ## Split the expression string into token list def GetExpressionTokenList(self): self.TokenList = self.TokenPattern.findall(self.ExpressionString) ## Convert token list into postfix notation def GetPostfixNotation(self): Stack = [] LastToken = '' for Token in self.TokenList: if Token == "(": if LastToken not in self.SupportedOpcode + ['(', '', None]: EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operator before open parentheses", ExtraData="Near %s" % LastToken) Stack.append(Token) elif Token == ")": if '(' not in Stack: EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: mismatched parentheses", ExtraData=str(self)) elif LastToken in self.SupportedOpcode + ['', None]: EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operand before close parentheses", ExtraData="Near %s" % LastToken) while len(Stack) > 0: if Stack[-1] == '(': Stack.pop() break self.PostfixNotation.append(Stack.pop()) elif Token in self.OpcodePriority: if Token == DEPEX_OPCODE_NOT: if LastToken not in self.SupportedOpcode + ['(', '', None]: EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operator before NOT", ExtraData="Near %s" % LastToken) elif LastToken in self.SupportedOpcode + ['(', '', None]: EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operand before " + Token, ExtraData="Near %s" % LastToken) while len(Stack) > 0: if Stack[-1] == "(" or self.OpcodePriority[Token] >= self.OpcodePriority[Stack[-1]]: break self.PostfixNotation.append(Stack.pop()) Stack.append(Token) self.OpcodeList.append(Token) else: if Token not in self.SupportedOpcode: # not OP, take it as GUID if LastToken not in self.SupportedOpcode + ['(', '', None]: EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operator before %s" % Token, ExtraData="Near %s" % LastToken) if len(self.OpcodeList) == 0 or self.OpcodeList[-1] not in self.ExclusiveOpcode: if Token not in self.SupportedOperand: self.PostfixNotation.append(DEPEX_OPCODE_PUSH) # check if OP is valid in this phase elif Token in self.Opcode[self.Phase]: if Token == DEPEX_OPCODE_END: break self.OpcodeList.append(Token) else: EdkLogger.error("GenDepex", PARSER_ERROR, "Opcode=%s doesn't supported in %s stage " % (Token, self.Phase), ExtraData=str(self)) self.PostfixNotation.append(Token) LastToken = Token # there should not be parentheses in Stack if '(' in Stack or ')' in Stack: EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: mismatched parentheses", ExtraData=str(self)) while len(Stack) > 0: self.PostfixNotation.append(Stack.pop()) if self.PostfixNotation[-1] != DEPEX_OPCODE_END: self.PostfixNotation.append(DEPEX_OPCODE_END) ## Validate the dependency expression def ValidateOpcode(self): for Op in self.AboveAllOpcode: if Op in self.PostfixNotation: if Op != self.PostfixNotation[0]: EdkLogger.error("GenDepex", PARSER_ERROR, "%s should be the first opcode in the expression" % Op, ExtraData=str(self)) if len(self.PostfixNotation) < 3: EdkLogger.error("GenDepex", PARSER_ERROR, "Missing operand for %s" % Op, ExtraData=str(self)) for Op in self.ExclusiveOpcode: if Op in self.OpcodeList: if len(self.OpcodeList) > 1: EdkLogger.error("GenDepex", PARSER_ERROR, "%s should be the only opcode in the expression" % Op, ExtraData=str(self)) if len(self.PostfixNotation) < 3: EdkLogger.error("GenDepex", PARSER_ERROR, "Missing operand for %s" % Op, ExtraData=str(self)) if self.TokenList[-1] != DEPEX_OPCODE_END and self.TokenList[-1] in self.NonEndingOpcode: EdkLogger.error("GenDepex", PARSER_ERROR, "Extra %s at the end of the dependency expression" % self.TokenList[-1], ExtraData=str(self)) if self.TokenList[-1] == DEPEX_OPCODE_END and self.TokenList[-2] in self.NonEndingOpcode: EdkLogger.error("GenDepex", PARSER_ERROR, "Extra %s at the end of the dependency expression" % self.TokenList[-2], ExtraData=str(self)) if DEPEX_OPCODE_END in self.TokenList and DEPEX_OPCODE_END != self.TokenList[-1]: EdkLogger.error("GenDepex", PARSER_ERROR, "Extra expressions after END", ExtraData=str(self)) ## Simply optimize the dependency expression by removing duplicated operands def Optimize(self): OpcodeSet = set(self.OpcodeList) # if there are isn't one in the set, return if len(OpcodeSet) != 1: return Op = OpcodeSet.pop() #if Op isn't either OR or AND, return if Op not in [DEPEX_OPCODE_AND, DEPEX_OPCODE_OR]: return NewOperand = [] AllOperand = set() for Token in self.PostfixNotation: if Token in self.SupportedOpcode or Token in NewOperand: continue AllOperand.add(Token) if Token == DEPEX_OPCODE_TRUE: if Op == DEPEX_OPCODE_AND: continue else: NewOperand.append(Token) break elif Token == DEPEX_OPCODE_FALSE: if Op == DEPEX_OPCODE_OR: continue else: NewOperand.append(Token) break NewOperand.append(Token) # don't generate depex if only TRUE operand left if self.ModuleType == SUP_MODULE_PEIM and len(NewOperand) == 1 and NewOperand[0] == DEPEX_OPCODE_TRUE: self.PostfixNotation = [] return # don't generate depex if all operands are architecture protocols if self.ModuleType in [SUP_MODULE_UEFI_DRIVER, SUP_MODULE_DXE_DRIVER, SUP_MODULE_DXE_RUNTIME_DRIVER, SUP_MODULE_DXE_SAL_DRIVER, SUP_MODULE_DXE_SMM_DRIVER, SUP_MODULE_MM_STANDALONE] and \ Op == DEPEX_OPCODE_AND and \ self.ArchProtocols == set(GuidStructureStringToGuidString(Guid) for Guid in AllOperand): self.PostfixNotation = [] return if len(NewOperand) == 0: self.TokenList = list(AllOperand) else: self.TokenList = [] while True: self.TokenList.append(NewOperand.pop(0)) if NewOperand == []: break self.TokenList.append(Op) self.PostfixNotation = [] self.GetPostfixNotation() ## Convert a GUID value in C structure format into its binary form # # @param Guid The GUID value in C structure format # # @retval array The byte array representing the GUID value # def GetGuidValue(self, Guid): GuidValueString = Guid.replace("{", "").replace("}", "").replace(" ", "") GuidValueList = GuidValueString.split(",") if len(GuidValueList) != 11 and len(GuidValueList) == 16: GuidValueString = GuidStringToGuidStructureString(GuidStructureByteArrayToGuidString(Guid)) GuidValueString = GuidValueString.replace("{", "").replace("}", "").replace(" ", "") GuidValueList = GuidValueString.split(",") if len(GuidValueList) != 11: EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid GUID value string or opcode: %s" % Guid) return pack("1I2H8B", *(int(value, 16) for value in GuidValueList)) ## Save the binary form of dependency expression in file # # @param File The path of file. If None is given, put the data on console # # @retval True If the file doesn't exist or file is changed # @retval False If file exists and is not changed. # def Generate(self, File=None): Buffer = BytesIO() if len(self.PostfixNotation) == 0: return False for Item in self.PostfixNotation: if Item in self.Opcode[self.Phase]: Buffer.write(pack("B", self.Opcode[self.Phase][Item])) elif Item in self.SupportedOpcode: EdkLogger.error("GenDepex", FORMAT_INVALID, "Opcode [%s] is not expected in %s phase" % (Item, self.Phase), ExtraData=self.ExpressionString) else: Buffer.write(self.GetGuidValue(Item)) FilePath = "" FileChangeFlag = True if File is None: sys.stdout.write(Buffer.getvalue()) FilePath = "STDOUT" else: FileChangeFlag = SaveFileOnChange(File, Buffer.getvalue(), True) Buffer.close() return FileChangeFlag versionNumber = ("0.04" + " " + gBUILD_VERSION) __version__ = "%prog Version " + versionNumber __copyright__ = "Copyright (c) 2007-2018, Intel Corporation All rights reserved." __usage__ = "%prog [options] [dependency_expression_file]" ## Parse command line options # # @retval OptionParser # def GetOptions(): from optparse import OptionParser Parser = OptionParser(description=__copyright__, version=__version__, usage=__usage__) Parser.add_option("-o", "--output", dest="OutputFile", default=None, metavar="FILE", help="Specify the name of depex file to be generated") Parser.add_option("-t", "--module-type", dest="ModuleType", default=None, help="The type of module for which the dependency expression serves") Parser.add_option("-e", "--dependency-expression", dest="Expression", default="", help="The string of dependency expression. If this option presents, the input file will be ignored.") Parser.add_option("-m", "--optimize", dest="Optimize", default=False, action="store_true", help="Do some simple optimization on the expression.") Parser.add_option("-v", "--verbose", dest="verbose", default=False, action="store_true", help="build with verbose information") Parser.add_option("-d", "--debug", action="store", type="int", help="Enable debug messages at specified level.") Parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true", help="build with little information") return Parser.parse_args() ## Entrance method # # @retval 0 Tool was successful # @retval 1 Tool failed # def Main(): EdkLogger.Initialize() Option, Input = GetOptions() # Set log level if Option.quiet: EdkLogger.SetLevel(EdkLogger.QUIET) elif Option.verbose: EdkLogger.SetLevel(EdkLogger.VERBOSE) elif Option.debug is not None: EdkLogger.SetLevel(Option.debug + 1) else: EdkLogger.SetLevel(EdkLogger.INFO) try: if Option.ModuleType is None or Option.ModuleType not in gType2Phase: EdkLogger.error("GenDepex", OPTION_MISSING, "Module type is not specified or supported") DxsFile = '' if len(Input) > 0 and Option.Expression == "": DxsFile = Input[0] DxsString = open(DxsFile, 'r').read().replace("\n", " ").replace("\r", " ") DxsString = gStartClosePattern.sub("\\1", DxsString) elif Option.Expression != "": if Option.Expression[0] == '"': DxsString = Option.Expression[1:-1] else: DxsString = Option.Expression else: EdkLogger.error("GenDepex", OPTION_MISSING, "No expression string or file given") Dpx = DependencyExpression(DxsString, Option.ModuleType, Option.Optimize) if Option.OutputFile is not None: FileChangeFlag = Dpx.Generate(Option.OutputFile) if not FileChangeFlag and DxsFile: # # Touch the output file if its time stamp is older than the original # DXS file to avoid re-invoke this tool for the dependency check in build rule. # if os.stat(DxsFile)[8] > os.stat(Option.OutputFile)[8]: os.utime(Option.OutputFile, None) else: Dpx.Generate() except BaseException as X: EdkLogger.quiet("") if Option is not None and Option.debug is not None: EdkLogger.quiet(traceback.format_exc()) else: EdkLogger.quiet(str(X)) return 1 return 0 if __name__ == '__main__': sys.exit(Main())