1#!/usr/bin/env python
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17
18import argparse
19import gzip
20import json
21import os
22import zipfile
23
24
25class LsdumpError(Exception):
26    """The exception raised by ParseLsdumpFile."""
27    pass
28
29
30class AttrDict(dict):
31    """A dictionary with attribute accessors."""
32
33    def __getattr__(self, key):
34        """Returns self[key]."""
35        try:
36            return self[key]
37        except KeyError:
38            raise AttributeError('Failed to get attribute: %s' % key)
39
40    def __setattr__(self, key, value):
41        """Assigns value to self[key]."""
42        self[key] = value
43
44
45def _GetTypeSymbol(record_type):
46    """Gets the mangled name of a record type.
47
48    Before Android R, unique_id was mangled name starting with "_ZTS".
49    linker_set_key was unmangled.
50    Since Android R, unique_id has been removed, and linker_set_key has
51    been changed to mangled name starting with "_ZTI".
52
53    Args:
54        record_type: An AttrDict, a record type in lsdump.
55
56    Returns:
57        A string, the mangled name starting with "_ZTI".
58    """
59    if "unique_id" in record_type:
60        return record_type["unique_id"].replace("_ZTS", "_ZTI", 1)
61    return record_type["linker_set_key"]
62
63
64def _OpenFileOrGzipped(file_name):
65    """Opens a file that is either in gzip or uncompressed format.
66
67    If file_name ends with '.gz' then return gzip.open(file_name, 'rb'),
68    else return open(file_name, 'rb').
69
70    Args:
71        file_name: The file name to open.
72
73    Returns:
74        A file object.
75
76    Raises:
77        IOError if fails to open.
78    """
79    if file_name.endswith('.gz'):
80        return gzip.open(file_name, 'rb')
81    return open(file_name, 'rb')
82
83
84def _ConsumeOffset(tok, beg=0):
85    """Consumes a <offset-number> in a thunk symbol."""
86    pos = tok.find('_', beg) + 1
87    return tok[:pos], tok[pos:]
88
89
90def _ConsumeCallOffset(tok):
91    """Consumes a <call-offset> in a thunk symbol."""
92    if tok[:1] == 'h':
93        lhs, rhs = _ConsumeOffset(tok, 1)
94    elif tok[:1] == 'v':
95        lhs, rhs = _ConsumeOffset(tok, 1)
96        lhs2, rhs = _ConsumeOffset(rhs)
97        if lhs and lhs2:
98            lhs = lhs + lhs2
99        else:
100            lhs, rhs = '', tok
101    else:
102        lhs, rhs = '', tok
103    return lhs, rhs
104
105
106def _FindThunkTarget(name):
107    """Finds thunk symbol's target function.
108
109    <thunk-symbol> ::= _ZT <call-offset> <base-encoding>
110                     | _ZTc <call-offset> <call-offset> <base-encoding>
111    <call-offset>  ::= h <nv-offset>
112                     | v <v-offset>
113    <nv-offset>    ::= <offset-number> _
114    <v-offset>     ::= <offset-number> _ <offset-number> _
115
116    Args:
117        name: A string, the symbol name to resolve.
118
119    Returns:
120        A string, symbol name of the nominal target function.
121        The input symbol name if it is not a thunk symbol.
122    """
123    if name.startswith('_ZTh') or name.startswith('_ZTv'):
124        lhs, rhs = _ConsumeCallOffset(name[len('_ZT'):])
125        if lhs:
126            return '_Z' + rhs
127    if name.startswith('_ZTc'):
128        lhs, rhs = _ConsumeCallOffset(name[len('_ZTc'):])
129        lhs2, rhs = _ConsumeCallOffset(rhs)
130        if lhs and lhs2:
131            return '_Z' + rhs
132    return name
133
134
135def _FilterElfFunctions(lsdump):
136    """Finds exported functions and thunks in lsdump.
137
138    Args:
139        lsdump: An AttrDict object containing the lsdump.
140
141    Yields:
142        The AttrDict objects in lsdump.elf_functions.
143    """
144    functions = {function.linker_set_key for function in lsdump.functions}
145
146    for elf_function in lsdump.elf_functions:
147        if elf_function.name in functions:
148            yield elf_function
149        elif _FindThunkTarget(elf_function.name) in functions:
150            yield elf_function
151
152
153def _FilterElfObjects(lsdump):
154    """Finds exported variables, type info, and vtables in lsdump.
155
156    Args:
157        lsdump: An AttrDict object containing the lsdump.
158
159    Yields:
160        The AttrDict objects in lsdump.elf_objects.
161    """
162    global_vars = {global_var.linker_set_key
163                   for global_var in lsdump.global_vars}
164    record_names = {_GetTypeSymbol(record_type)[len('_ZTI'):]
165                    for record_type in lsdump.record_types}
166
167    for elf_object in lsdump.elf_objects:
168        name = elf_object.name
169        if name in global_vars:
170            yield elf_object
171        elif (name[:len('_ZTI')] in {'_ZTV', '_ZTT', '_ZTI', '_ZTS'} and
172                name[len('_ZTI'):] in record_names):
173            yield elf_object
174
175
176def _ParseSymbolsFromLsdump(lsdump, output_dump):
177    """Parses symbols from an lsdump.
178
179    Args:
180        lsdump: An AttrDict object containing the lsdump.
181        output_dump: An AttrDict object containing the output.
182    """
183    output_dump.elf_functions = list(_FilterElfFunctions(lsdump))
184    output_dump.elf_objects = list(_FilterElfObjects(lsdump))
185
186
187def _ParseVtablesFromLsdump(lsdump, output_dump):
188    """Parses vtables from an lsdump.
189
190    Args:
191        lsdump: An AttrDict object containing the lsdump.
192        output_dump: An AttrDict object containing the output.
193    """
194    vtable_symbols = {elf_object.name for elf_object in lsdump.elf_objects
195                      if elf_object.name.startswith('_ZTV')}
196
197    output_dump.record_types = []
198    for lsdump_record_type in lsdump.record_types:
199        type_symbol = _GetTypeSymbol(lsdump_record_type)
200        vtable_symbol = '_ZTV' + type_symbol[len('_ZTI'):]
201        if vtable_symbol not in vtable_symbols:
202            continue
203        record_type = AttrDict()
204        record_type.linker_set_key = type_symbol
205        record_type.vtable_components = lsdump_record_type.vtable_components
206        output_dump.record_types.append(record_type)
207
208
209def _ParseLsdumpFileObject(lsdump_file):
210    """Converts an lsdump file to a dump object.
211
212    Args:
213        lsdump_file: The file object of the input lsdump.
214
215    Returns:
216        The AttrDict object converted from the lsdump file.
217
218    Raises:
219        LsdumpError if fails to create the dump file.
220    """
221    try:
222        lsdump = json.load(lsdump_file, object_hook=AttrDict)
223    except ValueError:
224        raise LsdumpError(e)
225
226    try:
227        output_dump = AttrDict()
228        _ParseVtablesFromLsdump(lsdump, output_dump)
229        _ParseSymbolsFromLsdump(lsdump, output_dump)
230        return output_dump
231    except AttributeError as e:
232        raise LsdumpError(e)
233
234
235def _ParseLsdumpZipFile(lsdump_zip, output_zip):
236    """Converts zipped lsdump files to the dump files for ABI test."""
237    for name in lsdump_zip.namelist():
238        if name.endswith(".lsdump"):
239            with lsdump_zip.open(name, mode="r") as lsdump_file:
240                output_dump = _ParseLsdumpFileObject(lsdump_file)
241            output_str = json.dumps(output_dump, indent=1,
242                                    separators=(',', ':'))
243            output_zip.writestr(name, output_str)
244
245
246def ParseLsdumpFile(input_path, output_path):
247    """Converts an lsdump file to a dump file for the ABI test.
248
249    Args:
250        input_path: The path to the (gzipped) lsdump file.
251        output_path: The path to the output dump file.
252
253    Raises:
254        LsdumpError if fails to create the dump file.
255    """
256    abs_output_path = os.path.abspath(output_path)
257    abs_output_dir = os.path.dirname(abs_output_path)
258
259    try:
260        if abs_output_dir and not os.path.exists(abs_output_dir):
261            os.makedirs(abs_output_dir)
262
263        with _OpenFileOrGzipped(input_path) as lsdump_file:
264            output_dump = _ParseLsdumpFileObject(lsdump_file)
265        with open(output_path, 'wb') as output_file:
266            json.dump(output_dump, output_file,
267                      indent=1, separators=(',', ':'))
268    except (IOError, OSError) as e:
269        raise LsdumpError(e)
270
271
272def main():
273    arg_parser = argparse.ArgumentParser(
274        description='This script converts an lsdump file to a dump file for '
275                    'the ABI test in VTS.')
276    arg_parser.add_argument('input_path',
277                            help='input lsdump file path.')
278    arg_parser.add_argument('output_path',
279                            help='output dump file path.')
280    args = arg_parser.parse_args()
281
282    # TODO(b/150663999): Remove this when the build system is able to process
283    #                    the large file group.
284    if zipfile.is_zipfile(args.input_path):
285        with zipfile.ZipFile(args.input_path, mode='r') as input_zip:
286            # The zip will be added to a Python package. It is not necessary
287            # to reduce the file size.
288            with zipfile.ZipFile(args.output_path, mode='w',
289                                 compression=zipfile.ZIP_STORED) as output_zip:
290                _ParseLsdumpZipFile(input_zip, output_zip)
291        exit(0)
292
293    try:
294        ParseLsdumpFile(args.input_path, args.output_path)
295    except LsdumpError as e:
296        print(e)
297        exit(1)
298
299
300if __name__ == '__main__':
301    main()
302