1## @file
2# This file is used to generate DEPEX file for module's dependency expression
3#
4# Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
5# SPDX-License-Identifier: BSD-2-Clause-Patent
6
7## Import Modules
8#
9import sys
10import Common.LongFilePathOs as os
11import re
12import traceback
13from Common.LongFilePathSupport import OpenLongFilePath as open
14from io import BytesIO
15from struct import pack
16from Common.BuildToolError import *
17from Common.Misc import SaveFileOnChange
18from Common.Misc import GuidStructureStringToGuidString
19from Common.Misc import GuidStructureByteArrayToGuidString
20from Common.Misc import GuidStringToGuidStructureString
21from Common import EdkLogger as EdkLogger
22from Common.BuildVersion import gBUILD_VERSION
23from Common.DataType import *
24
25## Regular expression for matching "DEPENDENCY_START ... DEPENDENCY_END"
26gStartClosePattern = re.compile(".*DEPENDENCY_START(.+)DEPENDENCY_END.*", re.S)
27
28## Mapping between module type and EFI phase
29gType2Phase = {
30    SUP_MODULE_BASE              :   None,
31    SUP_MODULE_SEC               :   "PEI",
32    SUP_MODULE_PEI_CORE          :   "PEI",
33    SUP_MODULE_PEIM              :   "PEI",
34    SUP_MODULE_DXE_CORE          :   "DXE",
35    SUP_MODULE_DXE_DRIVER        :   "DXE",
36    SUP_MODULE_DXE_SMM_DRIVER    :   "DXE",
37    SUP_MODULE_DXE_RUNTIME_DRIVER:   "DXE",
38    SUP_MODULE_DXE_SAL_DRIVER    :   "DXE",
39    SUP_MODULE_UEFI_DRIVER       :   "DXE",
40    SUP_MODULE_UEFI_APPLICATION  :   "DXE",
41    SUP_MODULE_SMM_CORE          :   "DXE",
42    SUP_MODULE_MM_STANDALONE     :   "MM",
43    SUP_MODULE_MM_CORE_STANDALONE :  "MM",
44}
45
46## Convert dependency expression string into EFI internal representation
47#
48#   DependencyExpression class is used to parse dependency expression string and
49# convert it into its binary form.
50#
51class DependencyExpression:
52
53    ArchProtocols = {
54                        '665e3ff6-46cc-11d4-9a38-0090273fc14d',     #   'gEfiBdsArchProtocolGuid'
55                        '26baccb1-6f42-11d4-bce7-0080c73c8881',     #   'gEfiCpuArchProtocolGuid'
56                        '26baccb2-6f42-11d4-bce7-0080c73c8881',     #   'gEfiMetronomeArchProtocolGuid'
57                        '1da97072-bddc-4b30-99f1-72a0b56fff2a',     #   'gEfiMonotonicCounterArchProtocolGuid'
58                        '27cfac87-46cc-11d4-9a38-0090273fc14d',     #   'gEfiRealTimeClockArchProtocolGuid'
59                        '27cfac88-46cc-11d4-9a38-0090273fc14d',     #   'gEfiResetArchProtocolGuid'
60                        'b7dfb4e1-052f-449f-87be-9818fc91b733',     #   'gEfiRuntimeArchProtocolGuid'
61                        'a46423e3-4617-49f1-b9ff-d1bfa9115839',     #   'gEfiSecurityArchProtocolGuid'
62                        '26baccb3-6f42-11d4-bce7-0080c73c8881',     #   'gEfiTimerArchProtocolGuid'
63                        '6441f818-6362-4e44-b570-7dba31dd2453',     #   'gEfiVariableWriteArchProtocolGuid'
64                        '1e5668e2-8481-11d4-bcf1-0080c73c8881',     #   'gEfiVariableArchProtocolGuid'
65                        '665e3ff5-46cc-11d4-9a38-0090273fc14d'      #   'gEfiWatchdogTimerArchProtocolGuid'
66                    }
67
68    OpcodePriority = {
69        DEPEX_OPCODE_AND   :   1,
70        DEPEX_OPCODE_OR    :   1,
71        DEPEX_OPCODE_NOT   :   2,
72    }
73
74    Opcode = {
75        "PEI"   : {
76            DEPEX_OPCODE_PUSH  :   0x02,
77            DEPEX_OPCODE_AND   :   0x03,
78            DEPEX_OPCODE_OR    :   0x04,
79            DEPEX_OPCODE_NOT   :   0x05,
80            DEPEX_OPCODE_TRUE  :   0x06,
81            DEPEX_OPCODE_FALSE :   0x07,
82            DEPEX_OPCODE_END   :   0x08
83        },
84
85        "DXE"   : {
86            DEPEX_OPCODE_BEFORE:   0x00,
87            DEPEX_OPCODE_AFTER :   0x01,
88            DEPEX_OPCODE_PUSH  :   0x02,
89            DEPEX_OPCODE_AND   :   0x03,
90            DEPEX_OPCODE_OR    :   0x04,
91            DEPEX_OPCODE_NOT   :   0x05,
92            DEPEX_OPCODE_TRUE  :   0x06,
93            DEPEX_OPCODE_FALSE :   0x07,
94            DEPEX_OPCODE_END   :   0x08,
95            DEPEX_OPCODE_SOR   :   0x09
96        },
97
98        "MM"   : {
99            DEPEX_OPCODE_BEFORE:   0x00,
100            DEPEX_OPCODE_AFTER :   0x01,
101            DEPEX_OPCODE_PUSH  :   0x02,
102            DEPEX_OPCODE_AND   :   0x03,
103            DEPEX_OPCODE_OR    :   0x04,
104            DEPEX_OPCODE_NOT   :   0x05,
105            DEPEX_OPCODE_TRUE  :   0x06,
106            DEPEX_OPCODE_FALSE :   0x07,
107            DEPEX_OPCODE_END   :   0x08,
108            DEPEX_OPCODE_SOR   :   0x09
109        }
110    }
111
112    # all supported op codes and operands
113    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]
114    SupportedOperand = [DEPEX_OPCODE_TRUE, DEPEX_OPCODE_FALSE]
115
116    OpcodeWithSingleOperand = [DEPEX_OPCODE_NOT, DEPEX_OPCODE_BEFORE, DEPEX_OPCODE_AFTER]
117    OpcodeWithTwoOperand = [DEPEX_OPCODE_AND, DEPEX_OPCODE_OR]
118
119    # op code that should not be the last one
120    NonEndingOpcode = [DEPEX_OPCODE_AND, DEPEX_OPCODE_OR, DEPEX_OPCODE_NOT, DEPEX_OPCODE_SOR]
121    # op code must not present at the same time
122    ExclusiveOpcode = [DEPEX_OPCODE_BEFORE, DEPEX_OPCODE_AFTER]
123    # op code that should be the first one if it presents
124    AboveAllOpcode = [DEPEX_OPCODE_SOR, DEPEX_OPCODE_BEFORE, DEPEX_OPCODE_AFTER]
125
126    #
127    # open and close brace must be taken as individual tokens
128    #
129    TokenPattern = re.compile("(\(|\)|\{[^{}]+\{?[^{}]+\}?[ ]*\}|\w+)")
130
131    ## Constructor
132    #
133    #   @param  Expression  The list or string of dependency expression
134    #   @param  ModuleType  The type of the module using the dependency expression
135    #
136    def __init__(self, Expression, ModuleType, Optimize=False):
137        self.ModuleType = ModuleType
138        self.Phase = gType2Phase[ModuleType]
139        if isinstance(Expression, type([])):
140            self.ExpressionString = " ".join(Expression)
141            self.TokenList = Expression
142        else:
143            self.ExpressionString = Expression
144            self.GetExpressionTokenList()
145
146        self.PostfixNotation = []
147        self.OpcodeList = []
148
149        self.GetPostfixNotation()
150        self.ValidateOpcode()
151
152        EdkLogger.debug(EdkLogger.DEBUG_8, repr(self))
153        if Optimize:
154            self.Optimize()
155            EdkLogger.debug(EdkLogger.DEBUG_8, "\n    Optimized: " + repr(self))
156
157    def __str__(self):
158        return " ".join(self.TokenList)
159
160    def __repr__(self):
161        WellForm = ''
162        for Token in self.PostfixNotation:
163            if Token in self.SupportedOpcode:
164                WellForm += "\n    " + Token
165            else:
166                WellForm += ' ' + Token
167        return WellForm
168
169    ## Split the expression string into token list
170    def GetExpressionTokenList(self):
171        self.TokenList = self.TokenPattern.findall(self.ExpressionString)
172
173    ## Convert token list into postfix notation
174    def GetPostfixNotation(self):
175        Stack = []
176        LastToken = ''
177        for Token in self.TokenList:
178            if Token == "(":
179                if LastToken not in self.SupportedOpcode + ['(', '', None]:
180                    EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operator before open parentheses",
181                                    ExtraData="Near %s" % LastToken)
182                Stack.append(Token)
183            elif Token == ")":
184                if '(' not in Stack:
185                    EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: mismatched parentheses",
186                                    ExtraData=str(self))
187                elif LastToken in self.SupportedOpcode + ['', None]:
188                    EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operand before close parentheses",
189                                    ExtraData="Near %s" % LastToken)
190                while len(Stack) > 0:
191                    if Stack[-1] == '(':
192                        Stack.pop()
193                        break
194                    self.PostfixNotation.append(Stack.pop())
195            elif Token in self.OpcodePriority:
196                if Token == DEPEX_OPCODE_NOT:
197                    if LastToken not in self.SupportedOpcode + ['(', '', None]:
198                        EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operator before NOT",
199                                        ExtraData="Near %s" % LastToken)
200                elif LastToken in self.SupportedOpcode + ['(', '', None]:
201                        EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operand before " + Token,
202                                        ExtraData="Near %s" % LastToken)
203
204                while len(Stack) > 0:
205                    if Stack[-1] == "(" or self.OpcodePriority[Token] >= self.OpcodePriority[Stack[-1]]:
206                        break
207                    self.PostfixNotation.append(Stack.pop())
208                Stack.append(Token)
209                self.OpcodeList.append(Token)
210            else:
211                if Token not in self.SupportedOpcode:
212                    # not OP, take it as GUID
213                    if LastToken not in self.SupportedOpcode + ['(', '', None]:
214                        EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: missing operator before %s" % Token,
215                                        ExtraData="Near %s" % LastToken)
216                    if len(self.OpcodeList) == 0 or self.OpcodeList[-1] not in self.ExclusiveOpcode:
217                        if Token not in self.SupportedOperand:
218                            self.PostfixNotation.append(DEPEX_OPCODE_PUSH)
219                # check if OP is valid in this phase
220                elif Token in self.Opcode[self.Phase]:
221                    if Token == DEPEX_OPCODE_END:
222                        break
223                    self.OpcodeList.append(Token)
224                else:
225                    EdkLogger.error("GenDepex", PARSER_ERROR,
226                                    "Opcode=%s doesn't supported in %s stage " % (Token, self.Phase),
227                                    ExtraData=str(self))
228                self.PostfixNotation.append(Token)
229            LastToken = Token
230
231        # there should not be parentheses in Stack
232        if '(' in Stack or ')' in Stack:
233            EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid dependency expression: mismatched parentheses",
234                            ExtraData=str(self))
235        while len(Stack) > 0:
236            self.PostfixNotation.append(Stack.pop())
237        if self.PostfixNotation[-1] != DEPEX_OPCODE_END:
238            self.PostfixNotation.append(DEPEX_OPCODE_END)
239
240    ## Validate the dependency expression
241    def ValidateOpcode(self):
242        for Op in self.AboveAllOpcode:
243            if Op in self.PostfixNotation:
244                if Op != self.PostfixNotation[0]:
245                    EdkLogger.error("GenDepex", PARSER_ERROR, "%s should be the first opcode in the expression" % Op,
246                                    ExtraData=str(self))
247                if len(self.PostfixNotation) < 3:
248                    EdkLogger.error("GenDepex", PARSER_ERROR, "Missing operand for %s" % Op,
249                                    ExtraData=str(self))
250        for Op in self.ExclusiveOpcode:
251            if Op in self.OpcodeList:
252                if len(self.OpcodeList) > 1:
253                    EdkLogger.error("GenDepex", PARSER_ERROR, "%s should be the only opcode in the expression" % Op,
254                                    ExtraData=str(self))
255                if len(self.PostfixNotation) < 3:
256                    EdkLogger.error("GenDepex", PARSER_ERROR, "Missing operand for %s" % Op,
257                                    ExtraData=str(self))
258        if self.TokenList[-1] != DEPEX_OPCODE_END and self.TokenList[-1] in self.NonEndingOpcode:
259            EdkLogger.error("GenDepex", PARSER_ERROR, "Extra %s at the end of the dependency expression" % self.TokenList[-1],
260                            ExtraData=str(self))
261        if self.TokenList[-1] == DEPEX_OPCODE_END and self.TokenList[-2] in self.NonEndingOpcode:
262            EdkLogger.error("GenDepex", PARSER_ERROR, "Extra %s at the end of the dependency expression" % self.TokenList[-2],
263                            ExtraData=str(self))
264        if DEPEX_OPCODE_END in self.TokenList and DEPEX_OPCODE_END != self.TokenList[-1]:
265            EdkLogger.error("GenDepex", PARSER_ERROR, "Extra expressions after END",
266                            ExtraData=str(self))
267
268    ## Simply optimize the dependency expression by removing duplicated operands
269    def Optimize(self):
270        OpcodeSet = set(self.OpcodeList)
271        # if there are isn't one in the set, return
272        if len(OpcodeSet) != 1:
273          return
274        Op = OpcodeSet.pop()
275        #if Op isn't either OR or AND, return
276        if Op not in [DEPEX_OPCODE_AND, DEPEX_OPCODE_OR]:
277            return
278        NewOperand = []
279        AllOperand = set()
280        for Token in self.PostfixNotation:
281            if Token in self.SupportedOpcode or Token in NewOperand:
282                continue
283            AllOperand.add(Token)
284            if Token == DEPEX_OPCODE_TRUE:
285                if Op == DEPEX_OPCODE_AND:
286                    continue
287                else:
288                    NewOperand.append(Token)
289                    break
290            elif Token == DEPEX_OPCODE_FALSE:
291                if Op == DEPEX_OPCODE_OR:
292                    continue
293                else:
294                    NewOperand.append(Token)
295                    break
296            NewOperand.append(Token)
297
298        # don't generate depex if only TRUE operand left
299        if self.ModuleType == SUP_MODULE_PEIM and len(NewOperand) == 1 and NewOperand[0] == DEPEX_OPCODE_TRUE:
300            self.PostfixNotation = []
301            return
302
303        # don't generate depex if all operands are architecture protocols
304        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 \
305           Op == DEPEX_OPCODE_AND and \
306           self.ArchProtocols == set(GuidStructureStringToGuidString(Guid) for Guid in AllOperand):
307            self.PostfixNotation = []
308            return
309
310        if len(NewOperand) == 0:
311            self.TokenList = list(AllOperand)
312        else:
313            self.TokenList = []
314            while True:
315                self.TokenList.append(NewOperand.pop(0))
316                if NewOperand == []:
317                    break
318                self.TokenList.append(Op)
319        self.PostfixNotation = []
320        self.GetPostfixNotation()
321
322
323    ## Convert a GUID value in C structure format into its binary form
324    #
325    #   @param  Guid    The GUID value in C structure format
326    #
327    #   @retval array   The byte array representing the GUID value
328    #
329    def GetGuidValue(self, Guid):
330        GuidValueString = Guid.replace("{", "").replace("}", "").replace(" ", "")
331        GuidValueList = GuidValueString.split(",")
332        if len(GuidValueList) != 11 and len(GuidValueList) == 16:
333            GuidValueString = GuidStringToGuidStructureString(GuidStructureByteArrayToGuidString(Guid))
334            GuidValueString = GuidValueString.replace("{", "").replace("}", "").replace(" ", "")
335            GuidValueList = GuidValueString.split(",")
336        if len(GuidValueList) != 11:
337            EdkLogger.error("GenDepex", PARSER_ERROR, "Invalid GUID value string or opcode: %s" % Guid)
338        return pack("1I2H8B", *(int(value, 16) for value in GuidValueList))
339
340    ## Save the binary form of dependency expression in file
341    #
342    #   @param  File    The path of file. If None is given, put the data on console
343    #
344    #   @retval True    If the file doesn't exist or file is changed
345    #   @retval False   If file exists and is not changed.
346    #
347    def Generate(self, File=None):
348        Buffer = BytesIO()
349        if len(self.PostfixNotation) == 0:
350            return False
351
352        for Item in self.PostfixNotation:
353            if Item in self.Opcode[self.Phase]:
354                Buffer.write(pack("B", self.Opcode[self.Phase][Item]))
355            elif Item in self.SupportedOpcode:
356                EdkLogger.error("GenDepex", FORMAT_INVALID,
357                                "Opcode [%s] is not expected in %s phase" % (Item, self.Phase),
358                                ExtraData=self.ExpressionString)
359            else:
360                Buffer.write(self.GetGuidValue(Item))
361
362        FilePath = ""
363        FileChangeFlag = True
364        if File is None:
365            sys.stdout.write(Buffer.getvalue())
366            FilePath = "STDOUT"
367        else:
368            FileChangeFlag = SaveFileOnChange(File, Buffer.getvalue(), True)
369
370        Buffer.close()
371        return FileChangeFlag
372
373versionNumber = ("0.04" + " " + gBUILD_VERSION)
374__version__ = "%prog Version " + versionNumber
375__copyright__ = "Copyright (c) 2007-2018, Intel Corporation  All rights reserved."
376__usage__ = "%prog [options] [dependency_expression_file]"
377
378## Parse command line options
379#
380#   @retval OptionParser
381#
382def GetOptions():
383    from optparse import OptionParser
384
385    Parser = OptionParser(description=__copyright__, version=__version__, usage=__usage__)
386
387    Parser.add_option("-o", "--output", dest="OutputFile", default=None, metavar="FILE",
388                      help="Specify the name of depex file to be generated")
389    Parser.add_option("-t", "--module-type", dest="ModuleType", default=None,
390                      help="The type of module for which the dependency expression serves")
391    Parser.add_option("-e", "--dependency-expression", dest="Expression", default="",
392                      help="The string of dependency expression. If this option presents, the input file will be ignored.")
393    Parser.add_option("-m", "--optimize", dest="Optimize", default=False, action="store_true",
394                      help="Do some simple optimization on the expression.")
395    Parser.add_option("-v", "--verbose", dest="verbose", default=False, action="store_true",
396                      help="build with verbose information")
397    Parser.add_option("-d", "--debug", action="store", type="int", help="Enable debug messages at specified level.")
398    Parser.add_option("-q", "--quiet", dest="quiet", default=False, action="store_true",
399                      help="build with little information")
400
401    return Parser.parse_args()
402
403
404## Entrance method
405#
406# @retval 0     Tool was successful
407# @retval 1     Tool failed
408#
409def Main():
410    EdkLogger.Initialize()
411    Option, Input = GetOptions()
412
413    # Set log level
414    if Option.quiet:
415        EdkLogger.SetLevel(EdkLogger.QUIET)
416    elif Option.verbose:
417        EdkLogger.SetLevel(EdkLogger.VERBOSE)
418    elif Option.debug is not None:
419        EdkLogger.SetLevel(Option.debug + 1)
420    else:
421        EdkLogger.SetLevel(EdkLogger.INFO)
422
423    try:
424        if Option.ModuleType is None or Option.ModuleType not in gType2Phase:
425            EdkLogger.error("GenDepex", OPTION_MISSING, "Module type is not specified or supported")
426
427        DxsFile = ''
428        if len(Input) > 0 and Option.Expression == "":
429            DxsFile = Input[0]
430            DxsString = open(DxsFile, 'r').read().replace("\n", " ").replace("\r", " ")
431            DxsString = gStartClosePattern.sub("\\1", DxsString)
432        elif Option.Expression != "":
433            if Option.Expression[0] == '"':
434                DxsString = Option.Expression[1:-1]
435            else:
436                DxsString = Option.Expression
437        else:
438            EdkLogger.error("GenDepex", OPTION_MISSING, "No expression string or file given")
439
440        Dpx = DependencyExpression(DxsString, Option.ModuleType, Option.Optimize)
441        if Option.OutputFile is not None:
442            FileChangeFlag = Dpx.Generate(Option.OutputFile)
443            if not FileChangeFlag and DxsFile:
444                #
445                # Touch the output file if its time stamp is older than the original
446                # DXS file to avoid re-invoke this tool for the dependency check in build rule.
447                #
448                if os.stat(DxsFile)[8] > os.stat(Option.OutputFile)[8]:
449                    os.utime(Option.OutputFile, None)
450        else:
451            Dpx.Generate()
452    except BaseException as X:
453        EdkLogger.quiet("")
454        if Option is not None and Option.debug is not None:
455            EdkLogger.quiet(traceback.format_exc())
456        else:
457            EdkLogger.quiet(str(X))
458        return 1
459
460    return 0
461
462if __name__ == '__main__':
463    sys.exit(Main())
464
465