1##
2# Generate symbal for memory profile info.
3#
4# This tool depends on DIA2Dump.exe (VS) or nm (gcc) to parse debug entry.
5#
6# Copyright (c) 2016 - 2018, Intel Corporation. All rights reserved.<BR>
7# SPDX-License-Identifier: BSD-2-Clause-Patent
8#
9##
10
11from __future__ import print_function
12import os
13import re
14import sys
15from optparse import OptionParser
16
17versionNumber = "1.1"
18__copyright__ = "Copyright (c) 2016 - 2018, Intel Corporation. All rights reserved."
19
20class Symbols:
21    def __init__(self):
22        self.listLineAddress = []
23        self.pdbName = ""
24        # Cache for function
25        self.functionName = ""
26        # Cache for line
27        self.sourceName = ""
28
29
30    def getSymbol (self, rva):
31        index = 0
32        lineName  = 0
33        sourceName = "??"
34        while index + 1 < self.lineCount :
35            if self.listLineAddress[index][0] <= rva and self.listLineAddress[index + 1][0] > rva :
36                offset = rva - self.listLineAddress[index][0]
37                functionName = self.listLineAddress[index][1]
38                lineName = self.listLineAddress[index][2]
39                sourceName = self.listLineAddress[index][3]
40                if lineName == 0 :
41                  return " (" + self.listLineAddress[index][1] + "() - " + ")"
42                else :
43                  return " (" + self.listLineAddress[index][1] + "() - " + sourceName + ":" + str(lineName) + ")"
44            index += 1
45
46        return " (unknown)"
47
48    def parse_debug_file(self, driverName, pdbName):
49        if cmp (pdbName, "") == 0 :
50            return
51        self.pdbName = pdbName;
52
53        try:
54            nmCommand = "nm"
55            nmLineOption = "-l"
56            print("parsing (debug) - " + pdbName)
57            os.system ('%s %s %s > nmDump.line.log' % (nmCommand, nmLineOption, pdbName))
58        except :
59            print('ERROR: nm command not available.  Please verify PATH')
60            return
61
62        #
63        # parse line
64        #
65        linefile = open("nmDump.line.log")
66        reportLines = linefile.readlines()
67        linefile.close()
68
69        # 000113ca T AllocatePool  c:\home\edk-ii\MdePkg\Library\UefiMemoryAllocationLib\MemoryAllocationLib.c:399
70        patchLineFileMatchString = "([0-9a-fA-F]*)\s+[T|D|t|d]\s+(\w+)\s*((?:[a-zA-Z]:)?[\w+\-./_a-zA-Z0-9\\\\]*):?([0-9]*)"
71
72        for reportLine in reportLines:
73            #print "check - " + reportLine
74            match = re.match(patchLineFileMatchString, reportLine)
75            if match is not None:
76                #print "match - " + reportLine[:-1]
77                #print "0 - " + match.group(0)
78                #print "1 - " + match.group(1)
79                #print "2 - " + match.group(2)
80                #print "3 - " + match.group(3)
81                #print "4 - " + match.group(4)
82
83                rva = int (match.group(1), 16)
84                functionName = match.group(2)
85                sourceName = match.group(3)
86                if cmp (match.group(4), "") != 0 :
87                    lineName = int (match.group(4))
88                else :
89                    lineName = 0
90                self.listLineAddress.append ([rva, functionName, lineName, sourceName])
91
92        self.lineCount = len (self.listLineAddress)
93
94        self.listLineAddress = sorted(self.listLineAddress, key=lambda symbolAddress:symbolAddress[0])
95
96        #for key in self.listLineAddress :
97            #print "rva - " + "%x"%(key[0]) + ", func - " + key[1] + ", line - " + str(key[2]) + ", source - " + key[3]
98
99    def parse_pdb_file(self, driverName, pdbName):
100        if cmp (pdbName, "") == 0 :
101            return
102        self.pdbName = pdbName;
103
104        try:
105            #DIA2DumpCommand = "\"C:\\Program Files (x86)\Microsoft Visual Studio 14.0\\DIA SDK\\Samples\\DIA2Dump\\x64\\Debug\\Dia2Dump.exe\""
106            DIA2DumpCommand = "Dia2Dump.exe"
107            #DIA2SymbolOption = "-p"
108            DIA2LinesOption = "-l"
109            print("parsing (pdb) - " + pdbName)
110            #os.system ('%s %s %s > DIA2Dump.symbol.log' % (DIA2DumpCommand, DIA2SymbolOption, pdbName))
111            os.system ('%s %s %s > DIA2Dump.line.log' % (DIA2DumpCommand, DIA2LinesOption, pdbName))
112        except :
113            print('ERROR: DIA2Dump command not available.  Please verify PATH')
114            return
115
116        #
117        # parse line
118        #
119        linefile = open("DIA2Dump.line.log")
120        reportLines = linefile.readlines()
121        linefile.close()
122
123        #   ** GetDebugPrintErrorLevel
124        #  line 32 at [0000C790][0001:0000B790], len = 0x3  c:\home\edk-ii\mdepkg\library\basedebugprinterrorlevellib\basedebugprinterrorlevellib.c (MD5: 687C0AE564079D35D56ED5D84A6164CC)
125        #  line 36 at [0000C793][0001:0000B793], len = 0x5
126        #  line 37 at [0000C798][0001:0000B798], len = 0x2
127
128        patchLineFileMatchString = "\s+line ([0-9]+) at \[([0-9a-fA-F]{8})\]\[[0-9a-fA-F]{4}\:[0-9a-fA-F]{8}\], len = 0x[0-9a-fA-F]+\s*([\w+\-\:./_a-zA-Z0-9\\\\]*)\s*"
129        patchLineFileMatchStringFunc = "\*\*\s+(\w+)\s*"
130
131        for reportLine in reportLines:
132            #print "check line - " + reportLine
133            match = re.match(patchLineFileMatchString, reportLine)
134            if match is not None:
135                #print "match - " + reportLine[:-1]
136                #print "0 - " + match.group(0)
137                #print "1 - " + match.group(1)
138                #print "2 - " + match.group(2)
139                if cmp (match.group(3), "") != 0 :
140                    self.sourceName = match.group(3)
141                sourceName = self.sourceName
142                functionName = self.functionName
143
144                rva = int (match.group(2), 16)
145                lineName = int (match.group(1))
146                self.listLineAddress.append ([rva, functionName, lineName, sourceName])
147            else :
148                match = re.match(patchLineFileMatchStringFunc, reportLine)
149                if match is not None:
150                    self.functionName = match.group(1)
151
152        self.lineCount = len (self.listLineAddress)
153        self.listLineAddress = sorted(self.listLineAddress, key=lambda symbolAddress:symbolAddress[0])
154
155        #for key in self.listLineAddress :
156            #print "rva - " + "%x"%(key[0]) + ", func - " + key[1] + ", line - " + str(key[2]) + ", source - " + key[3]
157
158class SymbolsFile:
159    def __init__(self):
160        self.symbolsTable = {}
161
162symbolsFile = ""
163
164driverName = ""
165rvaName = ""
166symbolName = ""
167
168def getSymbolName(driverName, rva):
169    global symbolsFile
170
171    #print "driverName - " + driverName
172
173    try :
174        symbolList = symbolsFile.symbolsTable[driverName]
175        if symbolList is not None:
176            return symbolList.getSymbol (rva)
177        else:
178            return " (???)"
179    except Exception:
180        return " (???)"
181
182def processLine(newline):
183    global driverName
184    global rvaName
185
186    driverPrefixLen = len("Driver - ")
187    # get driver name
188    if cmp(newline[0:driverPrefixLen], "Driver - ") == 0 :
189        driverlineList = newline.split(" ")
190        driverName = driverlineList[2]
191        #print "Checking : ", driverName
192
193        # EDKII application output
194        pdbMatchString = "Driver - \w* \(Usage - 0x[0-9a-fA-F]+\) \(Pdb - ([:\-.\w\\\\/]*)\)\s*"
195        pdbName = ""
196        match = re.match(pdbMatchString, newline)
197        if match is not None:
198            #print "match - " + newline
199            #print "0 - " + match.group(0)
200            #print "1 - " + match.group(1)
201            pdbName = match.group(1)
202            #print "PDB - " + pdbName
203
204        symbolsFile.symbolsTable[driverName] = Symbols()
205
206        if cmp (pdbName[-3:], "pdb") == 0 :
207            symbolsFile.symbolsTable[driverName].parse_pdb_file (driverName, pdbName)
208        else :
209            symbolsFile.symbolsTable[driverName].parse_debug_file (driverName, pdbName)
210
211    elif cmp(newline, "") == 0 :
212        driverName = ""
213
214    # check entry line
215    if newline.find ("<==") != -1 :
216        entry_list = newline.split(" ")
217        rvaName = entry_list[4]
218        #print "rva : ", rvaName
219        symbolName = getSymbolName (driverName, int(rvaName, 16))
220    else :
221        rvaName = ""
222        symbolName = ""
223
224    if cmp(rvaName, "") == 0 :
225        return newline
226    else :
227        return newline + symbolName
228
229def myOptionParser():
230    usage = "%prog [--version] [-h] [--help] [-i inputfile [-o outputfile]]"
231    Parser = OptionParser(usage=usage, description=__copyright__, version="%prog " + str(versionNumber))
232    Parser.add_option("-i", "--inputfile", dest="inputfilename", type="string", help="The input memory profile info file output from MemoryProfileInfo application in MdeModulePkg")
233    Parser.add_option("-o", "--outputfile", dest="outputfilename", type="string", help="The output memory profile info file with symbol, MemoryProfileInfoSymbol.txt will be used if it is not specified")
234
235    (Options, args) = Parser.parse_args()
236    if Options.inputfilename is None:
237        Parser.error("no input file specified")
238    if Options.outputfilename is None:
239        Options.outputfilename = "MemoryProfileInfoSymbol.txt"
240    return Options
241
242def main():
243    global symbolsFile
244    global Options
245    Options = myOptionParser()
246
247    symbolsFile = SymbolsFile()
248
249    try :
250        file = open(Options.inputfilename)
251    except Exception:
252        print("fail to open " + Options.inputfilename)
253        return 1
254    try :
255        newfile = open(Options.outputfilename, "w")
256    except Exception:
257        print("fail to open " + Options.outputfilename)
258        return 1
259
260    try:
261        while True:
262            line = file.readline()
263            if not line:
264                break
265            newline = line[:-1]
266
267            newline = processLine(newline)
268
269            newfile.write(newline)
270            newfile.write("\n")
271    finally:
272        file.close()
273        newfile.close()
274
275if __name__ == '__main__':
276    sys.exit(main())
277