xref: /optee_os/scripts/symbolize.py (revision 142c5ccc097709eefe00d2075a6b3385c43af439)
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
32733a15f2SJerome Forissierimport re
33733a15f2SJerome Forissierimport subprocess
34733a15f2SJerome Forissierimport sys
35733a15f2SJerome Forissier
36733a15f2SJerome ForissierTA_UUID_RE = re.compile(r'Status of TA (?P<uuid>[0-9a-f\-]+)')
37733a15f2SJerome ForissierTA_INFO_RE = re.compile(':  arch: (?P<arch>\w+)  '
38733a15f2SJerome Forissier                        'load address: (?P<load_addr>0x[0-9a-f]+)')
39733a15f2SJerome ForissierCALL_STACK_RE = re.compile('Call stack:')
40733a15f2SJerome ForissierSTACK_ADDR_RE = re.compile(r':  (?P<addr>0x[0-9a-f]+)')
41733a15f2SJerome ForissierX64_REGS_RE = re.compile(':  x0  [0-9a-f]{16} x1  [0-9a-f]{16}')
42*142c5cccSJerome ForissierABORT_ADDR_RE = re.compile('-abort at address (?P<addr>0x[0-9a-f]+)')
43733a15f2SJerome Forissier
44733a15f2SJerome Forissierepilog = '''
45733a15f2SJerome ForissierThis scripts reads an OP-TEE abort message from stdin and adds debug
46733a15f2SJerome Forissierinformation ('function at file:line') next to each address in the call stack.
47733a15f2SJerome ForissierIt uses the paths provided on the command line to locate the appropriate ELF
48733a15f2SJerome Forissierbinary (tee.elf or Trusted Application) and runs arm-linux-gnueabihf-addr2line
49733a15f2SJerome Forissieror aarch64-linux-gnu-addr2line to process the addresses.
50733a15f2SJerome Forissier
51733a15f2SJerome ForissierOP-TEE abort messages are sent to the secure console. They look like the
52733a15f2SJerome Forissierfollowing:
53733a15f2SJerome Forissier
54733a15f2SJerome Forissier  ERROR:   TEE-CORE: User TA data-abort at address 0xffffdecd (alignment fault)
55733a15f2SJerome Forissier  ...
56733a15f2SJerome Forissier  ERROR:   TEE-CORE: Call stack:
57733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000549e
58733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40001f4b
59733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000273f
60733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40005da7
61733a15f2SJerome Forissier
62733a15f2SJerome ForissierInspired by a script of the same name by the Chromium project.
63733a15f2SJerome Forissier
64733a15f2SJerome ForissierSample usage:
65733a15f2SJerome Forissier
66733a15f2SJerome Forissier  $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
67733a15f2SJerome Forissier  <paste whole dump here>
68733a15f2SJerome Forissier  ^D
69733a15f2SJerome Forissier'''
70733a15f2SJerome Forissier
71733a15f2SJerome Forissierdef get_args():
72733a15f2SJerome Forissier    parser = argparse.ArgumentParser(
73733a15f2SJerome Forissier                formatter_class=argparse.RawDescriptionHelpFormatter,
74733a15f2SJerome Forissier                description='Symbolizes OP-TEE abort dumps',
75733a15f2SJerome Forissier                epilog=epilog)
76733a15f2SJerome Forissier    parser.add_argument('-d', '--dir', action='append', nargs='+',
77733a15f2SJerome Forissier        help='Search for ELF file in DIR. tee.elf is needed to decode '
78733a15f2SJerome Forissier             'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required '
79733a15f2SJerome Forissier             'if a user-mode TA has crashed.')
80733a15f2SJerome Forissier    parser.add_argument('-s', '--strip_path',
81733a15f2SJerome Forissier        help='Strip STRIP_PATH from file paths')
82733a15f2SJerome Forissier
83733a15f2SJerome Forissier    return parser.parse_args()
84733a15f2SJerome Forissier
85733a15f2SJerome Forissierclass Symbolizer(object):
86733a15f2SJerome Forissier    def __init__(self, out, dirs, strip_path):
87733a15f2SJerome Forissier        self._out = out
88733a15f2SJerome Forissier        self._dirs = dirs
89733a15f2SJerome Forissier        self._strip_path = strip_path
90733a15f2SJerome Forissier        self._addr2line = None
91733a15f2SJerome Forissier        self._bin = 'tee.elf'
92733a15f2SJerome Forissier        self.reset()
93733a15f2SJerome Forissier
94733a15f2SJerome Forissier    def get_elf(self, elf_or_uuid):
95733a15f2SJerome Forissier        if not elf_or_uuid.endswith('.elf'):
96733a15f2SJerome Forissier            elf_or_uuid += '.elf'
97733a15f2SJerome Forissier        for d in self._dirs:
98733a15f2SJerome Forissier            elf = glob.glob(d + '/' + elf_or_uuid)
99733a15f2SJerome Forissier            if elf:
100733a15f2SJerome Forissier                return elf[0]
101733a15f2SJerome Forissier
102*142c5cccSJerome Forissier    def arch_prefix(self, cmd):
103*142c5cccSJerome Forissier        if self._arch == 'arm':
104*142c5cccSJerome Forissier            return 'arm-linux-gnueabihf-' + cmd
105*142c5cccSJerome Forissier        elif self._arch == 'aarch64':
106*142c5cccSJerome Forissier            return 'aarch64-linux-gnu-' + cmd
107*142c5cccSJerome Forissier        else:
108*142c5cccSJerome Forissier            return ''
109*142c5cccSJerome Forissier
110733a15f2SJerome Forissier    def spawn_addr2line(self):
111733a15f2SJerome Forissier        if not self._addr2line:
112733a15f2SJerome Forissier            elf = self.get_elf(self._bin)
113733a15f2SJerome Forissier            if not elf:
114733a15f2SJerome Forissier                return
115*142c5cccSJerome Forissier            cmd = self.arch_prefix('addr2line')
116*142c5cccSJerome Forissier            if not cmd:
117733a15f2SJerome Forissier                return
118733a15f2SJerome Forissier            self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
119733a15f2SJerome Forissier                                                stdin = subprocess.PIPE,
120733a15f2SJerome Forissier                                                stdout = subprocess.PIPE)
121733a15f2SJerome Forissier
122*142c5cccSJerome Forissier    def subtract_load_addr(self, addr):
123733a15f2SJerome Forissier        offs = self._load_addr
124fd5d0622SJerome Forissier        if int(offs, 16) > int(addr, 16):
125*142c5cccSJerome Forissier            return ''
126*142c5cccSJerome Forissier        return '0x{:x}'.format(int(addr, 16) - int(offs, 16))
127*142c5cccSJerome Forissier
128*142c5cccSJerome Forissier    def resolve(self, addr):
129*142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
130733a15f2SJerome Forissier        self.spawn_addr2line()
131*142c5cccSJerome Forissier        if not reladdr or not self._addr2line:
132733a15f2SJerome Forissier            return '???'
133733a15f2SJerome Forissier        try:
134733a15f2SJerome Forissier            print >> self._addr2line.stdin, reladdr
135733a15f2SJerome Forissier            ret = self._addr2line.stdout.readline().rstrip('\n')
136733a15f2SJerome Forissier        except IOError:
137733a15f2SJerome Forissier            ret = '!!!'
138733a15f2SJerome Forissier        return ret
139733a15f2SJerome Forissier
140*142c5cccSJerome Forissier    def symbol_plus_offset(self, addr):
141*142c5cccSJerome Forissier        ret = ''
142*142c5cccSJerome Forissier        prevsize = 0
143*142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
144*142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
145*142c5cccSJerome Forissier        cmd = self.arch_prefix('nm')
146*142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
147*142c5cccSJerome Forissier            return ''
148*142c5cccSJerome Forissier        ireladdr = int(reladdr, 0)
149*142c5cccSJerome Forissier        nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
150*142c5cccSJerome Forissier                               stdin = subprocess.PIPE,
151*142c5cccSJerome Forissier                               stdout = subprocess.PIPE)
152*142c5cccSJerome Forissier        for line in iter(nm.stdout.readline, ''):
153*142c5cccSJerome Forissier            try:
154*142c5cccSJerome Forissier                addr, size, _, name = line.split()
155*142c5cccSJerome Forissier            except:
156*142c5cccSJerome Forissier                # Size is missing
157*142c5cccSJerome Forissier                addr, _, name = line.split()
158*142c5cccSJerome Forissier                size = '0'
159*142c5cccSJerome Forissier            iaddr = int(addr, 16)
160*142c5cccSJerome Forissier            isize = int(size, 16)
161*142c5cccSJerome Forissier            if iaddr == ireladdr:
162*142c5cccSJerome Forissier                ret = name
163*142c5cccSJerome Forissier                break
164*142c5cccSJerome Forissier            if iaddr < ireladdr and iaddr + isize >= ireladdr:
165*142c5cccSJerome Forissier                offs = ireladdr - iaddr
166*142c5cccSJerome Forissier                ret = name + '+' + str(offs)
167*142c5cccSJerome Forissier                break
168*142c5cccSJerome Forissier            if iaddr > ireladdr and prevsize == 0:
169*142c5cccSJerome Forissier                offs = iaddr + ireladdr
170*142c5cccSJerome Forissier                ret = prevname + '+' + str(offs)
171*142c5cccSJerome Forissier                break
172*142c5cccSJerome Forissier            prevsize = size
173*142c5cccSJerome Forissier            prevname = name
174*142c5cccSJerome Forissier        nm.terminate()
175*142c5cccSJerome Forissier        return ret
176*142c5cccSJerome Forissier
177*142c5cccSJerome Forissier    def section_plus_offset(self, addr):
178*142c5cccSJerome Forissier        ret = ''
179*142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
180*142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
181*142c5cccSJerome Forissier        cmd = self.arch_prefix('objdump')
182*142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
183*142c5cccSJerome Forissier            return ''
184*142c5cccSJerome Forissier        iaddr = int(reladdr, 0)
185*142c5cccSJerome Forissier        objdump = subprocess.Popen([cmd, '--section-headers', elf],
186*142c5cccSJerome Forissier                                    stdin = subprocess.PIPE,
187*142c5cccSJerome Forissier                                    stdout = subprocess.PIPE)
188*142c5cccSJerome Forissier        for line in iter(objdump.stdout.readline, ''):
189*142c5cccSJerome Forissier            try:
190*142c5cccSJerome Forissier                idx, name, size, vma, lma, offs, algn = line.split()
191*142c5cccSJerome Forissier            except:
192*142c5cccSJerome Forissier                continue;
193*142c5cccSJerome Forissier            ivma = int(vma, 16)
194*142c5cccSJerome Forissier            isize = int(size, 16)
195*142c5cccSJerome Forissier            if ivma == iaddr:
196*142c5cccSJerome Forissier                ret = name
197*142c5cccSJerome Forissier                break
198*142c5cccSJerome Forissier            if ivma < iaddr and ivma + isize >= iaddr:
199*142c5cccSJerome Forissier                offs = iaddr - ivma
200*142c5cccSJerome Forissier                ret = name + '+' + str(offs)
201*142c5cccSJerome Forissier                break
202*142c5cccSJerome Forissier        objdump.terminate()
203*142c5cccSJerome Forissier        return ret
204*142c5cccSJerome Forissier
205*142c5cccSJerome Forissier    def process_abort(self, line):
206*142c5cccSJerome Forissier        ret = ''
207*142c5cccSJerome Forissier        match = re.search(ABORT_ADDR_RE, line)
208*142c5cccSJerome Forissier        addr = match.group('addr')
209*142c5cccSJerome Forissier        pre = match.start('addr')
210*142c5cccSJerome Forissier        post = match.end('addr')
211*142c5cccSJerome Forissier        sym = self.symbol_plus_offset(addr)
212*142c5cccSJerome Forissier        sec = self.section_plus_offset(addr)
213*142c5cccSJerome Forissier        if sym or sec:
214*142c5cccSJerome Forissier            ret += line[:pre]
215*142c5cccSJerome Forissier            ret += addr
216*142c5cccSJerome Forissier            if sym:
217*142c5cccSJerome Forissier                ret += ' ' + sym
218*142c5cccSJerome Forissier            if sec:
219*142c5cccSJerome Forissier                ret += ' ' + sec
220*142c5cccSJerome Forissier            ret += line[post:]
221*142c5cccSJerome Forissier        return ret
222*142c5cccSJerome Forissier
223733a15f2SJerome Forissier    def reset(self):
224733a15f2SJerome Forissier        self._call_stack_found = False
225733a15f2SJerome Forissier        self._load_addr = '0'
226733a15f2SJerome Forissier        if self._addr2line:
227733a15f2SJerome Forissier            self._addr2line.terminate()
228733a15f2SJerome Forissier            self._addr2line = None
229733a15f2SJerome Forissier        self._arch = 'arm'
230*142c5cccSJerome Forissier        self._saved_abort_line = ''
231733a15f2SJerome Forissier
232733a15f2SJerome Forissier    def write(self, line):
233733a15f2SJerome Forissier            if self._call_stack_found:
234733a15f2SJerome Forissier                match = re.search(STACK_ADDR_RE, line)
235733a15f2SJerome Forissier                if match:
236733a15f2SJerome Forissier                    addr = match.group('addr')
237733a15f2SJerome Forissier                    pre = match.start('addr')
238733a15f2SJerome Forissier                    post = match.end('addr')
239733a15f2SJerome Forissier                    self._out.write(line[:pre])
240733a15f2SJerome Forissier                    self._out.write(addr)
241733a15f2SJerome Forissier                    res = self.resolve(addr)
242733a15f2SJerome Forissier                    if self._strip_path:
243733a15f2SJerome Forissier                        res = re.sub(re.escape(self._strip_path) + '/*', '',
244733a15f2SJerome Forissier                              res)
245733a15f2SJerome Forissier                    self._out.write(' ' + res)
246733a15f2SJerome Forissier                    self._out.write(line[post:])
247733a15f2SJerome Forissier                    return
248733a15f2SJerome Forissier                else:
249733a15f2SJerome Forissier                    self.reset()
250733a15f2SJerome Forissier            match = re.search(CALL_STACK_RE, line)
251733a15f2SJerome Forissier            if match:
252733a15f2SJerome Forissier                self._call_stack_found = True
253*142c5cccSJerome Forissier                # Here is a good place to resolve the abort address because we
254*142c5cccSJerome Forissier                # have all the information we need
255*142c5cccSJerome Forissier                if self._saved_abort_line:
256*142c5cccSJerome Forissier                    self._out.write(self.process_abort(self._saved_abort_line))
257733a15f2SJerome Forissier            match = re.search(TA_UUID_RE, line)
258733a15f2SJerome Forissier            if match:
259733a15f2SJerome Forissier                self._bin = match.group('uuid')
260733a15f2SJerome Forissier            match = re.search(TA_INFO_RE, line)
261733a15f2SJerome Forissier            if match:
262733a15f2SJerome Forissier                self._arch = match.group('arch')
263733a15f2SJerome Forissier                self._load_addr = match.group('load_addr')
264733a15f2SJerome Forissier            match = re.search(X64_REGS_RE, line)
265733a15f2SJerome Forissier            if match:
266733a15f2SJerome Forissier                # Assume _arch represents the TEE core. If we have a TA dump,
267733a15f2SJerome Forissier                # it will be overwritten later
268733a15f2SJerome Forissier                self._arch = 'aarch64'
269*142c5cccSJerome Forissier            match = re.search(ABORT_ADDR_RE, line)
270*142c5cccSJerome Forissier            if match:
271*142c5cccSJerome Forissier                # At this point the arch and TA load address are unknown.
272*142c5cccSJerome Forissier                # Save the line so We can translate the abort address later.
273*142c5cccSJerome Forissier                self._saved_abort_line = line
274733a15f2SJerome Forissier            self._out.write(line)
275733a15f2SJerome Forissier
276733a15f2SJerome Forissier    def flush(self):
277733a15f2SJerome Forissier        self._out.flush()
278733a15f2SJerome Forissier
279733a15f2SJerome Forissierdef main():
280733a15f2SJerome Forissier    args = get_args()
281733a15f2SJerome Forissier    if args.dir:
282733a15f2SJerome Forissier        # Flatten list in case -d is used several times *and* with multiple
283733a15f2SJerome Forissier        # arguments
284733a15f2SJerome Forissier        args.dirs = [item for sublist in args.dir for item in sublist]
285733a15f2SJerome Forissier    else:
286733a15f2SJerome Forissier        args.dirs = []
287733a15f2SJerome Forissier    symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
288733a15f2SJerome Forissier
289733a15f2SJerome Forissier    for line in sys.stdin:
290733a15f2SJerome Forissier        symbolizer.write(line)
291733a15f2SJerome Forissier    symbolizer.flush()
292733a15f2SJerome Forissier
293733a15f2SJerome Forissierif __name__ == "__main__":
294733a15f2SJerome Forissier    main()
295