• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
22
23
24class LsdumpError(Exception):
25    """The exception raised by ParseLsdumpFile."""
26    pass
27
28
29class AttrDict(dict):
30    """A dictionary with attribute accessors."""
31
32    def __getattr__(self, key):
33        """Returns self[key]."""
34        try:
35            return self[key]
36        except KeyError:
37            raise AttributeError('Failed to get attribute: %s' % key)
38
39    def __setattr__(self, key, value):
40        """Assigns value to self[key]."""
41        self[key] = value
42
43
44def _OpenFileOrGzipped(file_name):
45    """Opens a file that is either in gzip or uncompressed format.
46
47    If file_name ends with '.gz' then return gzip.open(file_name, 'rb'),
48    else return open(file_name, 'rb').
49
50    Args:
51        file_name: The file name to open.
52
53    Returns:
54        A file object.
55
56    Raises:
57        IOError if fails to open.
58    """
59    if file_name.endswith('.gz'):
60        return gzip.open(file_name, 'rb')
61    return open(file_name, 'rb')
62
63
64def _ConsumeOffset(tok, beg=0):
65    """Consumes a <offset-number> in a thunk symbol."""
66    pos = tok.find('_', beg) + 1
67    return tok[:pos], tok[pos:]
68
69
70def _ConsumeCallOffset(tok):
71    """Consumes a <call-offset> in a thunk symbol."""
72    if tok[:1] == 'h':
73        lhs, rhs = _ConsumeOffset(tok, 1)
74    elif tok[:1] == 'v':
75        lhs, rhs = _ConsumeOffset(tok, 1)
76        lhs2, rhs = _ConsumeOffset(rhs)
77        if lhs and lhs2:
78            lhs = lhs + lhs2
79        else:
80            lhs, rhs = '', tok
81    else:
82        lhs, rhs = '', tok
83    return lhs, rhs
84
85
86def _FindThunkTarget(name):
87    """Finds thunk symbol's target function.
88
89    <thunk-symbol> ::= _ZT <call-offset> <base-encoding>
90                     | _ZTc <call-offset> <call-offset> <base-encoding>
91    <call-offset>  ::= h <nv-offset>
92                     | v <v-offset>
93    <nv-offset>    ::= <offset-number> _
94    <v-offset>     ::= <offset-number> _ <offset-number> _
95
96    Args:
97        name: A string, the symbol name to resolve.
98
99    Returns:
100        A string, symbol name of the nominal target function.
101        The input symbol name if it is not a thunk symbol.
102    """
103    if name.startswith('_ZTh') or name.startswith('_ZTv'):
104        lhs, rhs = _ConsumeCallOffset(name[len('_ZT'):])
105        if lhs:
106            return '_Z' + rhs
107    if name.startswith('_ZTc'):
108        lhs, rhs = _ConsumeCallOffset(name[len('_ZTc'):])
109        lhs2, rhs = _ConsumeCallOffset(rhs)
110        if lhs and lhs2:
111            return '_Z' + rhs
112    return name
113
114
115def _FilterElfFunctions(lsdump):
116    """Finds exported functions and thunks in lsdump.
117
118    Args:
119        lsdump: An AttrDict object containing the lsdump.
120
121    Yields:
122        The AttrDict objects in lsdump.elf_functions.
123    """
124    functions = {function.linker_set_key for function in lsdump.functions}
125
126    for elf_function in lsdump.elf_functions:
127        if elf_function.name in functions:
128            yield elf_function
129        elif _FindThunkTarget(elf_function.name) in functions:
130            yield elf_function
131
132
133def _FilterElfObjects(lsdump):
134    """Finds exported variables, type info, and vtables in lsdump.
135
136    Args:
137        lsdump: An AttrDict object containing the lsdump.
138
139    Yields:
140        The AttrDict objects in lsdump.elf_objects.
141    """
142    global_vars = {global_var.linker_set_key
143                   for global_var in lsdump.global_vars}
144    record_names = {record_type.unique_id[len('_ZTS'):]
145                    for record_type in lsdump.record_types}
146
147    for elf_object in lsdump.elf_objects:
148        name = elf_object.name
149        if name in global_vars:
150            yield elf_object
151        elif (name[:len('_ZTS')] in {'_ZTV', '_ZTT', '_ZTI', '_ZTS'} and
152                name[len('_ZTS'):] in record_names):
153            yield elf_object
154
155
156def _ParseSymbolsFromLsdump(lsdump, output_dump):
157    """Parses symbols from an lsdump.
158
159    Args:
160        lsdump: An AttrDict object containing the lsdump.
161        output_dump: An AttrDict object containing the output.
162    """
163    output_dump.elf_functions = list(_FilterElfFunctions(lsdump))
164    output_dump.elf_objects = list(_FilterElfObjects(lsdump))
165
166
167def _ParseVtablesFromLsdump(lsdump, output_dump):
168    """Parses vtables from an lsdump.
169
170    Args:
171        lsdump: An AttrDict object containing the lsdump.
172        output_dump: An AttrDict object containing the output.
173    """
174    vtable_symbols = {elf_object.name for elf_object in lsdump.elf_objects
175                      if elf_object.name.startswith('_ZTV')}
176
177    output_dump.record_types = []
178    for lsdump_record_type in lsdump.record_types:
179        type_symbol = lsdump_record_type.unique_id
180        vtable_symbol = '_ZTV' + type_symbol[len('_ZTS'):]
181        if vtable_symbol not in vtable_symbols:
182            continue
183        record_type = AttrDict()
184        record_type.unique_id = lsdump_record_type.unique_id
185        record_type.vtable_components = lsdump_record_type.vtable_components
186        output_dump.record_types.append(record_type)
187
188
189def ParseLsdumpFile(input_path, output_path):
190    """Converts an lsdump file to a dump file for the ABI test.
191
192    Args:
193        input_path: The path to the (gzipped) lsdump file.
194        output_path: The path to the output dump file.
195
196    Raises:
197        LsdumpError if fails to create the dump file.
198    """
199    try:
200        with _OpenFileOrGzipped(input_path) as lsdump_file:
201            lsdump = json.load(lsdump_file, object_hook=AttrDict)
202    except (IOError, ValueError) as e:
203        raise LsdumpError(e)
204
205    try:
206        output_dump = AttrDict()
207        _ParseVtablesFromLsdump(lsdump, output_dump)
208        _ParseSymbolsFromLsdump(lsdump, output_dump)
209    except AttributeError as e:
210        raise LsdumpError(e)
211
212    abs_output_path = os.path.abspath(output_path)
213    abs_output_dir = os.path.dirname(abs_output_path)
214
215    try:
216        if abs_output_dir and not os.path.exists(abs_output_dir):
217            os.makedirs(abs_output_dir)
218        with open(output_path, 'wb') as output_file:
219            json.dump(output_dump, output_file,
220                      indent=1, separators=(',', ':'))
221    except (IOError, OSError) as e:
222        raise LsdumpError(e)
223
224
225def main():
226    arg_parser = argparse.ArgumentParser(
227        description='This script converts an lsdump file to a dump file for '
228                    'the ABI test in VTS.')
229    arg_parser.add_argument('input_path',
230                            help='input lsdump file path.')
231    arg_parser.add_argument('output_path',
232                            help='output dump file path.')
233    args = arg_parser.parse_args()
234
235    try:
236        ParseLsdumpFile(args.input_path, args.output_path)
237    except LsdumpError as e:
238        print(e)
239        exit(1)
240
241
242if __name__ == '__main__':
243    main()
244