xref: /optee_os/scripts/symbolize.py (revision 157e6213f202ea55a2b3038a3a9ff4510035d3c6)
1733a15f2SJerome Forissier#!/usr/bin/env python
2733a15f2SJerome Forissier#
3733a15f2SJerome Forissier# Copyright (c) 2017, Linaro Limited
4733a15f2SJerome Forissier# All rights reserved.
5733a15f2SJerome Forissier#
6733a15f2SJerome Forissier# Redistribution and use in source and binary forms, with or without
7733a15f2SJerome Forissier# modification, are permitted provided that the following conditions are met:
8733a15f2SJerome Forissier#
9733a15f2SJerome Forissier# 1. Redistributions of source code must retain the above copyright notice,
10733a15f2SJerome Forissier# this list of conditions and the following disclaimer.
11733a15f2SJerome Forissier#
12733a15f2SJerome Forissier# 2. Redistributions in binary form must reproduce the above copyright notice,
13733a15f2SJerome Forissier# this list of conditions and the following disclaimer in the documentation
14733a15f2SJerome Forissier# and/or other materials provided with the distribution.
15733a15f2SJerome Forissier#
16733a15f2SJerome Forissier# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17733a15f2SJerome Forissier# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18733a15f2SJerome Forissier# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19733a15f2SJerome Forissier# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20733a15f2SJerome Forissier# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21733a15f2SJerome Forissier# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22733a15f2SJerome Forissier# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23733a15f2SJerome Forissier# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24733a15f2SJerome Forissier# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25733a15f2SJerome Forissier# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26733a15f2SJerome Forissier# POSSIBILITY OF SUCH DAMAGE.
27733a15f2SJerome Forissier#
28733a15f2SJerome Forissier
29733a15f2SJerome Forissier
30733a15f2SJerome Forissierimport argparse
31733a15f2SJerome Forissierimport glob
32*157e6213SJerome Forissierimport os
33733a15f2SJerome Forissierimport re
34733a15f2SJerome Forissierimport subprocess
35733a15f2SJerome Forissierimport sys
36733a15f2SJerome Forissier
37733a15f2SJerome ForissierTA_UUID_RE = re.compile(r'Status of TA (?P<uuid>[0-9a-f\-]+)')
38733a15f2SJerome ForissierTA_INFO_RE = re.compile(':  arch: (?P<arch>\w+)  '
39733a15f2SJerome Forissier                        'load address: (?P<load_addr>0x[0-9a-f]+)')
40733a15f2SJerome ForissierCALL_STACK_RE = re.compile('Call stack:')
41733a15f2SJerome ForissierSTACK_ADDR_RE = re.compile(r':  (?P<addr>0x[0-9a-f]+)')
42733a15f2SJerome ForissierX64_REGS_RE = re.compile(':  x0  [0-9a-f]{16} x1  [0-9a-f]{16}')
43142c5cccSJerome ForissierABORT_ADDR_RE = re.compile('-abort at address (?P<addr>0x[0-9a-f]+)')
44733a15f2SJerome Forissier
45733a15f2SJerome Forissierepilog = '''
46733a15f2SJerome ForissierThis scripts reads an OP-TEE abort message from stdin and adds debug
47733a15f2SJerome Forissierinformation ('function at file:line') next to each address in the call stack.
48733a15f2SJerome ForissierIt uses the paths provided on the command line to locate the appropriate ELF
49733a15f2SJerome Forissierbinary (tee.elf or Trusted Application) and runs arm-linux-gnueabihf-addr2line
50733a15f2SJerome Forissieror aarch64-linux-gnu-addr2line to process the addresses.
51733a15f2SJerome Forissier
52733a15f2SJerome ForissierOP-TEE abort messages are sent to the secure console. They look like the
53733a15f2SJerome Forissierfollowing:
54733a15f2SJerome Forissier
55733a15f2SJerome Forissier  ERROR:   TEE-CORE: User TA data-abort at address 0xffffdecd (alignment fault)
56733a15f2SJerome Forissier  ...
57733a15f2SJerome Forissier  ERROR:   TEE-CORE: Call stack:
58733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000549e
59733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40001f4b
60733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000273f
61733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40005da7
62733a15f2SJerome Forissier
63733a15f2SJerome ForissierInspired by a script of the same name by the Chromium project.
64733a15f2SJerome Forissier
65733a15f2SJerome ForissierSample usage:
66733a15f2SJerome Forissier
67733a15f2SJerome Forissier  $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
68733a15f2SJerome Forissier  <paste whole dump here>
69733a15f2SJerome Forissier  ^D
70733a15f2SJerome Forissier'''
71733a15f2SJerome Forissier
72733a15f2SJerome Forissierdef get_args():
73733a15f2SJerome Forissier    parser = argparse.ArgumentParser(
74733a15f2SJerome Forissier                formatter_class=argparse.RawDescriptionHelpFormatter,
75733a15f2SJerome Forissier                description='Symbolizes OP-TEE abort dumps',
76733a15f2SJerome Forissier                epilog=epilog)
77733a15f2SJerome Forissier    parser.add_argument('-d', '--dir', action='append', nargs='+',
78733a15f2SJerome Forissier        help='Search for ELF file in DIR. tee.elf is needed to decode '
79733a15f2SJerome Forissier             'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required '
80*157e6213SJerome Forissier             'if a user-mode TA has crashed. For convenience, ELF files '
81*157e6213SJerome Forissier             'may also be given.')
82733a15f2SJerome Forissier    parser.add_argument('-s', '--strip_path',
83733a15f2SJerome Forissier        help='Strip STRIP_PATH from file paths')
84733a15f2SJerome Forissier
85733a15f2SJerome Forissier    return parser.parse_args()
86733a15f2SJerome Forissier
87733a15f2SJerome Forissierclass Symbolizer(object):
88733a15f2SJerome Forissier    def __init__(self, out, dirs, strip_path):
89733a15f2SJerome Forissier        self._out = out
90733a15f2SJerome Forissier        self._dirs = dirs
91733a15f2SJerome Forissier        self._strip_path = strip_path
92733a15f2SJerome Forissier        self._addr2line = None
93733a15f2SJerome Forissier        self._bin = 'tee.elf'
94733a15f2SJerome Forissier        self.reset()
95733a15f2SJerome Forissier
96733a15f2SJerome Forissier    def get_elf(self, elf_or_uuid):
97733a15f2SJerome Forissier        if not elf_or_uuid.endswith('.elf'):
98733a15f2SJerome Forissier            elf_or_uuid += '.elf'
99733a15f2SJerome Forissier        for d in self._dirs:
100*157e6213SJerome Forissier            if d.endswith(elf_or_uuid) and os.path.isfile(d):
101*157e6213SJerome Forissier                return d
102733a15f2SJerome Forissier            elf = glob.glob(d + '/' + elf_or_uuid)
103733a15f2SJerome Forissier            if elf:
104733a15f2SJerome Forissier                return elf[0]
105733a15f2SJerome Forissier
106142c5cccSJerome Forissier    def arch_prefix(self, cmd):
107142c5cccSJerome Forissier        if self._arch == 'arm':
108142c5cccSJerome Forissier            return 'arm-linux-gnueabihf-' + cmd
109142c5cccSJerome Forissier        elif self._arch == 'aarch64':
110142c5cccSJerome Forissier            return 'aarch64-linux-gnu-' + cmd
111142c5cccSJerome Forissier        else:
112142c5cccSJerome Forissier            return ''
113142c5cccSJerome Forissier
114733a15f2SJerome Forissier    def spawn_addr2line(self):
115733a15f2SJerome Forissier        if not self._addr2line:
116733a15f2SJerome Forissier            elf = self.get_elf(self._bin)
117733a15f2SJerome Forissier            if not elf:
118733a15f2SJerome Forissier                return
119142c5cccSJerome Forissier            cmd = self.arch_prefix('addr2line')
120142c5cccSJerome Forissier            if not cmd:
121733a15f2SJerome Forissier                return
122733a15f2SJerome Forissier            self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
123733a15f2SJerome Forissier                                                stdin = subprocess.PIPE,
124733a15f2SJerome Forissier                                                stdout = subprocess.PIPE)
125733a15f2SJerome Forissier
126142c5cccSJerome Forissier    def subtract_load_addr(self, addr):
127733a15f2SJerome Forissier        offs = self._load_addr
128fd5d0622SJerome Forissier        if int(offs, 16) > int(addr, 16):
129142c5cccSJerome Forissier            return ''
130142c5cccSJerome Forissier        return '0x{:x}'.format(int(addr, 16) - int(offs, 16))
131142c5cccSJerome Forissier
132142c5cccSJerome Forissier    def resolve(self, addr):
133142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
134733a15f2SJerome Forissier        self.spawn_addr2line()
135142c5cccSJerome Forissier        if not reladdr or not self._addr2line:
136733a15f2SJerome Forissier            return '???'
137733a15f2SJerome Forissier        try:
138733a15f2SJerome Forissier            print >> self._addr2line.stdin, reladdr
139733a15f2SJerome Forissier            ret = self._addr2line.stdout.readline().rstrip('\n')
140733a15f2SJerome Forissier        except IOError:
141733a15f2SJerome Forissier            ret = '!!!'
142733a15f2SJerome Forissier        return ret
143733a15f2SJerome Forissier
144142c5cccSJerome Forissier    def symbol_plus_offset(self, addr):
145142c5cccSJerome Forissier        ret = ''
146142c5cccSJerome Forissier        prevsize = 0
147142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
148142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
149142c5cccSJerome Forissier        cmd = self.arch_prefix('nm')
150142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
151142c5cccSJerome Forissier            return ''
152142c5cccSJerome Forissier        ireladdr = int(reladdr, 0)
153142c5cccSJerome Forissier        nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
154142c5cccSJerome Forissier                               stdin = subprocess.PIPE,
155142c5cccSJerome Forissier                               stdout = subprocess.PIPE)
156142c5cccSJerome Forissier        for line in iter(nm.stdout.readline, ''):
157142c5cccSJerome Forissier            try:
158142c5cccSJerome Forissier                addr, size, _, name = line.split()
159142c5cccSJerome Forissier            except:
160142c5cccSJerome Forissier                # Size is missing
161142c5cccSJerome Forissier                addr, _, name = line.split()
162142c5cccSJerome Forissier                size = '0'
163142c5cccSJerome Forissier            iaddr = int(addr, 16)
164142c5cccSJerome Forissier            isize = int(size, 16)
165142c5cccSJerome Forissier            if iaddr == ireladdr:
166142c5cccSJerome Forissier                ret = name
167142c5cccSJerome Forissier                break
168142c5cccSJerome Forissier            if iaddr < ireladdr and iaddr + isize >= ireladdr:
169142c5cccSJerome Forissier                offs = ireladdr - iaddr
170142c5cccSJerome Forissier                ret = name + '+' + str(offs)
171142c5cccSJerome Forissier                break
172142c5cccSJerome Forissier            if iaddr > ireladdr and prevsize == 0:
173142c5cccSJerome Forissier                offs = iaddr + ireladdr
174142c5cccSJerome Forissier                ret = prevname + '+' + str(offs)
175142c5cccSJerome Forissier                break
176142c5cccSJerome Forissier            prevsize = size
177142c5cccSJerome Forissier            prevname = name
178142c5cccSJerome Forissier        nm.terminate()
179142c5cccSJerome Forissier        return ret
180142c5cccSJerome Forissier
181142c5cccSJerome Forissier    def section_plus_offset(self, addr):
182142c5cccSJerome Forissier        ret = ''
183142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
184142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
185142c5cccSJerome Forissier        cmd = self.arch_prefix('objdump')
186142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
187142c5cccSJerome Forissier            return ''
188142c5cccSJerome Forissier        iaddr = int(reladdr, 0)
189142c5cccSJerome Forissier        objdump = subprocess.Popen([cmd, '--section-headers', elf],
190142c5cccSJerome Forissier                                    stdin = subprocess.PIPE,
191142c5cccSJerome Forissier                                    stdout = subprocess.PIPE)
192142c5cccSJerome Forissier        for line in iter(objdump.stdout.readline, ''):
193142c5cccSJerome Forissier            try:
194142c5cccSJerome Forissier                idx, name, size, vma, lma, offs, algn = line.split()
195142c5cccSJerome Forissier            except:
196142c5cccSJerome Forissier                continue;
197142c5cccSJerome Forissier            ivma = int(vma, 16)
198142c5cccSJerome Forissier            isize = int(size, 16)
199142c5cccSJerome Forissier            if ivma == iaddr:
200142c5cccSJerome Forissier                ret = name
201142c5cccSJerome Forissier                break
202142c5cccSJerome Forissier            if ivma < iaddr and ivma + isize >= iaddr:
203142c5cccSJerome Forissier                offs = iaddr - ivma
204142c5cccSJerome Forissier                ret = name + '+' + str(offs)
205142c5cccSJerome Forissier                break
206142c5cccSJerome Forissier        objdump.terminate()
207142c5cccSJerome Forissier        return ret
208142c5cccSJerome Forissier
209142c5cccSJerome Forissier    def process_abort(self, line):
210142c5cccSJerome Forissier        ret = ''
211142c5cccSJerome Forissier        match = re.search(ABORT_ADDR_RE, line)
212142c5cccSJerome Forissier        addr = match.group('addr')
213142c5cccSJerome Forissier        pre = match.start('addr')
214142c5cccSJerome Forissier        post = match.end('addr')
215142c5cccSJerome Forissier        sym = self.symbol_plus_offset(addr)
216142c5cccSJerome Forissier        sec = self.section_plus_offset(addr)
217142c5cccSJerome Forissier        if sym or sec:
218142c5cccSJerome Forissier            ret += line[:pre]
219142c5cccSJerome Forissier            ret += addr
220142c5cccSJerome Forissier            if sym:
221142c5cccSJerome Forissier                ret += ' ' + sym
222142c5cccSJerome Forissier            if sec:
223142c5cccSJerome Forissier                ret += ' ' + sec
224142c5cccSJerome Forissier            ret += line[post:]
225142c5cccSJerome Forissier        return ret
226142c5cccSJerome Forissier
227733a15f2SJerome Forissier    def reset(self):
228733a15f2SJerome Forissier        self._call_stack_found = False
229733a15f2SJerome Forissier        self._load_addr = '0'
230733a15f2SJerome Forissier        if self._addr2line:
231733a15f2SJerome Forissier            self._addr2line.terminate()
232733a15f2SJerome Forissier            self._addr2line = None
233733a15f2SJerome Forissier        self._arch = 'arm'
234142c5cccSJerome Forissier        self._saved_abort_line = ''
235733a15f2SJerome Forissier
236733a15f2SJerome Forissier    def write(self, line):
237733a15f2SJerome Forissier            if self._call_stack_found:
238733a15f2SJerome Forissier                match = re.search(STACK_ADDR_RE, line)
239733a15f2SJerome Forissier                if match:
240733a15f2SJerome Forissier                    addr = match.group('addr')
241733a15f2SJerome Forissier                    pre = match.start('addr')
242733a15f2SJerome Forissier                    post = match.end('addr')
243733a15f2SJerome Forissier                    self._out.write(line[:pre])
244733a15f2SJerome Forissier                    self._out.write(addr)
245733a15f2SJerome Forissier                    res = self.resolve(addr)
246733a15f2SJerome Forissier                    if self._strip_path:
247733a15f2SJerome Forissier                        res = re.sub(re.escape(self._strip_path) + '/*', '',
248733a15f2SJerome Forissier                              res)
249733a15f2SJerome Forissier                    self._out.write(' ' + res)
250733a15f2SJerome Forissier                    self._out.write(line[post:])
251733a15f2SJerome Forissier                    return
252733a15f2SJerome Forissier                else:
253733a15f2SJerome Forissier                    self.reset()
254733a15f2SJerome Forissier            match = re.search(CALL_STACK_RE, line)
255733a15f2SJerome Forissier            if match:
256733a15f2SJerome Forissier                self._call_stack_found = True
257142c5cccSJerome Forissier                # Here is a good place to resolve the abort address because we
258142c5cccSJerome Forissier                # have all the information we need
259142c5cccSJerome Forissier                if self._saved_abort_line:
260142c5cccSJerome Forissier                    self._out.write(self.process_abort(self._saved_abort_line))
261733a15f2SJerome Forissier            match = re.search(TA_UUID_RE, line)
262733a15f2SJerome Forissier            if match:
263733a15f2SJerome Forissier                self._bin = match.group('uuid')
264733a15f2SJerome Forissier            match = re.search(TA_INFO_RE, line)
265733a15f2SJerome Forissier            if match:
266733a15f2SJerome Forissier                self._arch = match.group('arch')
267733a15f2SJerome Forissier                self._load_addr = match.group('load_addr')
268733a15f2SJerome Forissier            match = re.search(X64_REGS_RE, line)
269733a15f2SJerome Forissier            if match:
270733a15f2SJerome Forissier                # Assume _arch represents the TEE core. If we have a TA dump,
271733a15f2SJerome Forissier                # it will be overwritten later
272733a15f2SJerome Forissier                self._arch = 'aarch64'
273142c5cccSJerome Forissier            match = re.search(ABORT_ADDR_RE, line)
274142c5cccSJerome Forissier            if match:
275142c5cccSJerome Forissier                # At this point the arch and TA load address are unknown.
276142c5cccSJerome Forissier                # Save the line so We can translate the abort address later.
277142c5cccSJerome Forissier                self._saved_abort_line = line
278733a15f2SJerome Forissier            self._out.write(line)
279733a15f2SJerome Forissier
280733a15f2SJerome Forissier    def flush(self):
281733a15f2SJerome Forissier        self._out.flush()
282733a15f2SJerome Forissier
283733a15f2SJerome Forissierdef main():
284733a15f2SJerome Forissier    args = get_args()
285733a15f2SJerome Forissier    if args.dir:
286733a15f2SJerome Forissier        # Flatten list in case -d is used several times *and* with multiple
287733a15f2SJerome Forissier        # arguments
288733a15f2SJerome Forissier        args.dirs = [item for sublist in args.dir for item in sublist]
289733a15f2SJerome Forissier    else:
290733a15f2SJerome Forissier        args.dirs = []
291733a15f2SJerome Forissier    symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
292733a15f2SJerome Forissier
293733a15f2SJerome Forissier    for line in sys.stdin:
294733a15f2SJerome Forissier        symbolizer.write(line)
295733a15f2SJerome Forissier    symbolizer.flush()
296733a15f2SJerome Forissier
297733a15f2SJerome Forissierif __name__ == "__main__":
298733a15f2SJerome Forissier    main()
299