1## @file
2# Generate PCD table for 'Patchable In Module' type PCD with given .map file.
3#    The Patch PCD table like:
4#
5#    PCD Name    Offset in binary
6#    ========    ================
7#
8# Copyright (c) 2008 - 2018, Intel Corporation. All rights reserved.<BR>
9# SPDX-License-Identifier: BSD-2-Clause-Patent
10#
11#
12
13#======================================  External Libraries ========================================
14from __future__ import print_function
15import optparse
16import Common.LongFilePathOs as os
17import re
18import array
19
20from Common.BuildToolError import *
21import Common.EdkLogger as EdkLogger
22from Common.Misc import PeImageClass, startPatternGeneral, addressPatternGeneral, valuePatternGcc, pcdPatternGcc, secReGeneral
23from Common.BuildVersion import gBUILD_VERSION
24from Common.LongFilePathSupport import OpenLongFilePath as open
25
26# Version and Copyright
27__version_number__ = ("0.10" + " " + gBUILD_VERSION)
28__version__ = "%prog Version " + __version_number__
29__copyright__ = "Copyright (c) 2008 - 2018, Intel Corporation. All rights reserved."
30
31#======================================  Internal Libraries ========================================
32
33#============================================== Code ===============================================
34symRe = re.compile('^([\da-fA-F]+):([\da-fA-F]+) +([\.\-:\\\\\w\?@\$<>]+) +([\da-fA-F]+)', re.UNICODE)
35
36def parsePcdInfoFromMapFile(mapfilepath, efifilepath):
37    """ Parse map file to get binary patch pcd information
38    @param path    Map file absolution path
39
40    @return a list which element hold (PcdName, Offset, SectionName)
41    """
42    lines = []
43    try:
44        f = open(mapfilepath, 'r')
45        lines = f.readlines()
46        f.close()
47    except:
48        return None
49
50    if len(lines) == 0: return None
51    firstline = lines[0].strip()
52    if (firstline.startswith("Archive member included ") and
53        firstline.endswith(" file (symbol)")):
54        return _parseForGCC(lines, efifilepath)
55    if firstline.startswith("# Path:"):
56        return _parseForXcode(lines, efifilepath)
57    return _parseGeneral(lines, efifilepath)
58
59def _parseForXcode(lines, efifilepath):
60    valuePattern = re.compile('^([\da-fA-FxX]+)([\s\S]*)([_]*_gPcd_BinaryPatch_([\w]+))')
61    status = 0
62    pcds = []
63    for line in lines:
64        line = line.strip()
65        if status == 0 and line == "# Symbols:":
66            status = 1
67            continue
68        if status == 1 and len(line) != 0:
69            if '_gPcd_BinaryPatch_' in line:
70                m = valuePattern.match(line)
71                if m is not None:
72                    pcds.append((m.groups(0)[3], int(m.groups(0)[0], 16)))
73    return pcds
74
75def _parseForGCC(lines, efifilepath):
76    """ Parse map file generated by GCC linker """
77    dataPattern = re.compile('^.data._gPcd_BinaryPatch_([\w_\d]+)$')
78    status = 0
79    imageBase = -1
80    sections = []
81    bpcds = []
82    for index, line in enumerate(lines):
83        line = line.strip()
84        # status machine transection
85        if status == 0 and line == "Memory Configuration":
86            status = 1
87            continue
88        elif status == 1 and line == 'Linker script and memory map':
89            status = 2
90            continue
91        elif status ==2 and line == 'START GROUP':
92            status = 3
93            continue
94
95        # status handler
96        if status == 3:
97            m = valuePatternGcc.match(line)
98            if m is not None:
99                sections.append(m.groups(0))
100        if status == 3:
101            m = dataPattern.match(line)
102            if m is not None:
103                if lines[index + 1]:
104                    PcdName = m.groups(0)[0]
105                    m = pcdPatternGcc.match(lines[index + 1].strip())
106                    if m is not None:
107                        bpcds.append((PcdName, int(m.groups(0)[0], 16), int(sections[-1][1], 16), sections[-1][0]))
108
109    # get section information from efi file
110    efisecs = PeImageClass(efifilepath).SectionHeaderList
111    if efisecs is None or len(efisecs) == 0:
112        return None
113    #redirection
114    redirection = 0
115    for efisec in efisecs:
116        for section in sections:
117            if section[0].strip() == efisec[0].strip() and section[0].strip() == '.text':
118                redirection = int(section[1], 16) - efisec[1]
119    pcds = []
120    for pcd in bpcds:
121        for efisec in efisecs:
122            if pcd[1] >= efisec[1] and pcd[1] < efisec[1]+efisec[3]:
123                #assert efisec[0].strip() == pcd[3].strip() and efisec[1] + redirection == pcd[2], "There are some differences between map file and efi file"
124                pcds.append([pcd[0], efisec[2] + pcd[1] - efisec[1] - redirection, efisec[0]])
125    return pcds
126
127def _parseGeneral(lines, efifilepath):
128    """ For MSFT, ICC, EBC
129    @param lines    line array for map file
130
131    @return a list which element hold (PcdName, Offset, SectionName)
132    """
133    status = 0    #0 - beginning of file; 1 - PE section definition; 2 - symbol table
134    secs = []    # key = section name
135    bPcds = []
136    symPattern = re.compile('^[_]+gPcd_BinaryPatch_([\w]+)')
137
138    for line in lines:
139        line = line.strip()
140        if startPatternGeneral.match(line):
141            status = 1
142            continue
143        if addressPatternGeneral.match(line):
144            status = 2
145            continue
146        if line.startswith("entry point at"):
147            status = 3
148            continue
149        if status == 1 and len(line) != 0:
150            m = secReGeneral.match(line)
151            assert m is not None, "Fail to parse the section in map file , line is %s" % line
152            sec_no, sec_start, sec_length, sec_name, sec_class = m.groups(0)
153            secs.append([int(sec_no, 16), int(sec_start, 16), int(sec_length, 16), sec_name, sec_class])
154        if status == 2 and len(line) != 0:
155            m = symRe.match(line)
156            assert m is not None, "Fail to parse the symbol in map file, line is %s" % line
157            sec_no, sym_offset, sym_name, vir_addr = m.groups(0)
158            sec_no = int(sec_no, 16)
159            sym_offset = int(sym_offset, 16)
160            vir_addr = int(vir_addr, 16)
161            m2 = symPattern.match(sym_name)
162            if m2 is not None:
163                # fond a binary pcd entry in map file
164                for sec in secs:
165                    if sec[0] == sec_no and (sym_offset >= sec[1] and sym_offset < sec[1] + sec[2]):
166                        bPcds.append([m2.groups(0)[0], sec[3], sym_offset, vir_addr, sec_no])
167
168    if len(bPcds) == 0: return None
169
170    # get section information from efi file
171    efisecs = PeImageClass(efifilepath).SectionHeaderList
172    if efisecs is None or len(efisecs) == 0:
173        return None
174
175    pcds = []
176    for pcd in bPcds:
177        index = 0
178        for efisec in efisecs:
179            index = index + 1
180            if pcd[1].strip() == efisec[0].strip():
181                pcds.append([pcd[0], efisec[2] + pcd[2], efisec[0]])
182            elif pcd[4] == index:
183                pcds.append([pcd[0], efisec[2] + pcd[2], efisec[0]])
184    return pcds
185
186def generatePcdTable(list, pcdpath):
187    try:
188        f = open(pcdpath, 'w')
189    except:
190        pass
191
192    f.write('PCD Name                       Offset    Section Name\r\n')
193
194    for pcditem in list:
195        f.write('%-30s 0x%-08X %-6s\r\n' % (pcditem[0], pcditem[1], pcditem[2]))
196    f.close()
197
198    #print 'Success to generate Binary Patch PCD table at %s!' % pcdpath
199
200if __name__ == '__main__':
201    UsageString = "%prog -m <MapFile> -e <EfiFile> -o <OutFile>"
202    AdditionalNotes = "\nPCD table is generated in file name with .BinaryPcdTable.txt postfix"
203    parser = optparse.OptionParser(description=__copyright__, version=__version__, usage=UsageString)
204    parser.add_option('-m', '--mapfile', action='store', dest='mapfile',
205                      help='Absolute path of module map file.')
206    parser.add_option('-e', '--efifile', action='store', dest='efifile',
207                      help='Absolute path of EFI binary file.')
208    parser.add_option('-o', '--outputfile', action='store', dest='outfile',
209                      help='Absolute path of output file to store the got patchable PCD table.')
210
211    (options, args) = parser.parse_args()
212
213    if options.mapfile is None or options.efifile is None:
214        print(parser.get_usage())
215    elif os.path.exists(options.mapfile) and os.path.exists(options.efifile):
216        list = parsePcdInfoFromMapFile(options.mapfile, options.efifile)
217        if list is not None:
218            if options.outfile is not None:
219                generatePcdTable(list, options.outfile)
220            else:
221                generatePcdTable(list, options.mapfile.replace('.map', '.BinaryPcdTable.txt'))
222        else:
223            print('Fail to generate Patch PCD Table based on map file and efi file')
224    else:
225        print('Fail to generate Patch PCD Table for fail to find map file or efi file!')
226