xref: /optee_os/scripts/symbolize.py (revision 1bb929836182ecb96d2d9d268daa807c67596396)
1733a15f2SJerome Forissier#!/usr/bin/env python
2*1bb92983SJerome Forissier# SPDX-License-Identifier: BSD-2-Clause
3733a15f2SJerome Forissier#
4733a15f2SJerome Forissier# Copyright (c) 2017, Linaro Limited
5733a15f2SJerome Forissier# All rights reserved.
6733a15f2SJerome Forissier#
7733a15f2SJerome Forissier# Redistribution and use in source and binary forms, with or without
8733a15f2SJerome Forissier# modification, are permitted provided that the following conditions are met:
9733a15f2SJerome Forissier#
10733a15f2SJerome Forissier# 1. Redistributions of source code must retain the above copyright notice,
11733a15f2SJerome Forissier# this list of conditions and the following disclaimer.
12733a15f2SJerome Forissier#
13733a15f2SJerome Forissier# 2. Redistributions in binary form must reproduce the above copyright notice,
14733a15f2SJerome Forissier# this list of conditions and the following disclaimer in the documentation
15733a15f2SJerome Forissier# and/or other materials provided with the distribution.
16733a15f2SJerome Forissier#
17733a15f2SJerome Forissier# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18733a15f2SJerome Forissier# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19733a15f2SJerome Forissier# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20733a15f2SJerome Forissier# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21733a15f2SJerome Forissier# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22733a15f2SJerome Forissier# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23733a15f2SJerome Forissier# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24733a15f2SJerome Forissier# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25733a15f2SJerome Forissier# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26733a15f2SJerome Forissier# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27733a15f2SJerome Forissier# POSSIBILITY OF SUCH DAMAGE.
28733a15f2SJerome Forissier#
29733a15f2SJerome Forissier
30733a15f2SJerome Forissier
31733a15f2SJerome Forissierimport argparse
32733a15f2SJerome Forissierimport glob
33157e6213SJerome Forissierimport os
34733a15f2SJerome Forissierimport re
35733a15f2SJerome Forissierimport subprocess
36733a15f2SJerome Forissierimport sys
37733a15f2SJerome Forissier
38733a15f2SJerome ForissierTA_UUID_RE = re.compile(r'Status of TA (?P<uuid>[0-9a-f\-]+)')
39a2b984bdSJoakim BechTA_INFO_RE = re.compile('  arch: (?P<arch>\w+)  '
40733a15f2SJerome Forissier                        'load address: (?P<load_addr>0x[0-9a-f]+)')
41733a15f2SJerome ForissierCALL_STACK_RE = re.compile('Call stack:')
42a2b984bdSJoakim Bech
43a2b984bdSJoakim Bech# This gets the address from lines looking like this:
44a2b984bdSJoakim Bech# E/TC:0  0x001044a8
45a2b984bdSJoakim BechSTACK_ADDR_RE = re.compile(r'[UEIDFM]/T[AC]:.*(?P<addr>0x[0-9a-f]+)')
46142c5cccSJerome ForissierABORT_ADDR_RE = re.compile('-abort at address (?P<addr>0x[0-9a-f]+)')
4730999126SJerome ForissierREGION_RE = re.compile('region [0-9]+: va (?P<addr>0x[0-9a-f]+) '
4830999126SJerome Forissier                       'pa 0x[0-9a-f]+ size (?P<size>0x[0-9a-f]+)')
49733a15f2SJerome Forissier
50733a15f2SJerome Forissierepilog = '''
51733a15f2SJerome ForissierThis scripts reads an OP-TEE abort message from stdin and adds debug
52733a15f2SJerome Forissierinformation ('function at file:line') next to each address in the call stack.
53733a15f2SJerome ForissierIt uses the paths provided on the command line to locate the appropriate ELF
54733a15f2SJerome Forissierbinary (tee.elf or Trusted Application) and runs arm-linux-gnueabihf-addr2line
55733a15f2SJerome Forissieror aarch64-linux-gnu-addr2line to process the addresses.
56733a15f2SJerome Forissier
57733a15f2SJerome ForissierOP-TEE abort messages are sent to the secure console. They look like the
58733a15f2SJerome Forissierfollowing:
59733a15f2SJerome Forissier
60733a15f2SJerome Forissier  ERROR:   TEE-CORE: User TA data-abort at address 0xffffdecd (alignment fault)
61733a15f2SJerome Forissier  ...
62733a15f2SJerome Forissier  ERROR:   TEE-CORE: Call stack:
63733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000549e
64733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40001f4b
65733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000273f
66733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40005da7
67733a15f2SJerome Forissier
68733a15f2SJerome ForissierInspired by a script of the same name by the Chromium project.
69733a15f2SJerome Forissier
70733a15f2SJerome ForissierSample usage:
71733a15f2SJerome Forissier
72733a15f2SJerome Forissier  $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
73733a15f2SJerome Forissier  <paste whole dump here>
74733a15f2SJerome Forissier  ^D
75733a15f2SJerome Forissier'''
76733a15f2SJerome Forissier
77733a15f2SJerome Forissierdef get_args():
78733a15f2SJerome Forissier    parser = argparse.ArgumentParser(
79733a15f2SJerome Forissier                formatter_class=argparse.RawDescriptionHelpFormatter,
80733a15f2SJerome Forissier                description='Symbolizes OP-TEE abort dumps',
81733a15f2SJerome Forissier                epilog=epilog)
82733a15f2SJerome Forissier    parser.add_argument('-d', '--dir', action='append', nargs='+',
83733a15f2SJerome Forissier        help='Search for ELF file in DIR. tee.elf is needed to decode '
84733a15f2SJerome Forissier             'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required '
85157e6213SJerome Forissier             'if a user-mode TA has crashed. For convenience, ELF files '
86157e6213SJerome Forissier             'may also be given.')
87733a15f2SJerome Forissier    parser.add_argument('-s', '--strip_path',
88733a15f2SJerome Forissier        help='Strip STRIP_PATH from file paths')
89733a15f2SJerome Forissier
90733a15f2SJerome Forissier    return parser.parse_args()
91733a15f2SJerome Forissier
92733a15f2SJerome Forissierclass Symbolizer(object):
93733a15f2SJerome Forissier    def __init__(self, out, dirs, strip_path):
94733a15f2SJerome Forissier        self._out = out
95733a15f2SJerome Forissier        self._dirs = dirs
96733a15f2SJerome Forissier        self._strip_path = strip_path
97733a15f2SJerome Forissier        self._addr2line = None
98733a15f2SJerome Forissier        self._bin = 'tee.elf'
99733a15f2SJerome Forissier        self.reset()
100733a15f2SJerome Forissier
101733a15f2SJerome Forissier    def get_elf(self, elf_or_uuid):
102733a15f2SJerome Forissier        if not elf_or_uuid.endswith('.elf'):
103733a15f2SJerome Forissier            elf_or_uuid += '.elf'
104733a15f2SJerome Forissier        for d in self._dirs:
105157e6213SJerome Forissier            if d.endswith(elf_or_uuid) and os.path.isfile(d):
106157e6213SJerome Forissier                return d
107733a15f2SJerome Forissier            elf = glob.glob(d + '/' + elf_or_uuid)
108733a15f2SJerome Forissier            if elf:
109733a15f2SJerome Forissier                return elf[0]
110733a15f2SJerome Forissier
111d720431cSJerome Forissier    def set_arch(self):
112d720431cSJerome Forissier        if self._arch:
113d720431cSJerome Forissier            return
114d720431cSJerome Forissier        if self._bin:
115d720431cSJerome Forissier            p = subprocess.Popen([ 'file', self.get_elf(self._bin) ],
116d720431cSJerome Forissier                                 stdout=subprocess.PIPE)
117d720431cSJerome Forissier            output = p.stdout.readlines()
118d720431cSJerome Forissier            p.terminate()
119d720431cSJerome Forissier            if 'ARM aarch64,' in output[0]:
120d720431cSJerome Forissier                self._arch = 'aarch64-linux-gnu-'
121d720431cSJerome Forissier            elif 'ARM,' in output[0]:
122d720431cSJerome Forissier                self._arch = 'arm-linux-gnueabihf-'
123d720431cSJerome Forissier
124142c5cccSJerome Forissier    def arch_prefix(self, cmd):
125d720431cSJerome Forissier        self.set_arch()
126d720431cSJerome Forissier        return self._arch + cmd
127142c5cccSJerome Forissier
128733a15f2SJerome Forissier    def spawn_addr2line(self):
129733a15f2SJerome Forissier        if not self._addr2line:
130733a15f2SJerome Forissier            elf = self.get_elf(self._bin)
131733a15f2SJerome Forissier            if not elf:
132733a15f2SJerome Forissier                return
133142c5cccSJerome Forissier            cmd = self.arch_prefix('addr2line')
134142c5cccSJerome Forissier            if not cmd:
135733a15f2SJerome Forissier                return
136733a15f2SJerome Forissier            self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
137733a15f2SJerome Forissier                                                stdin = subprocess.PIPE,
138733a15f2SJerome Forissier                                                stdout = subprocess.PIPE)
139733a15f2SJerome Forissier
140142c5cccSJerome Forissier    def subtract_load_addr(self, addr):
141733a15f2SJerome Forissier        offs = self._load_addr
142fd5d0622SJerome Forissier        if int(offs, 16) > int(addr, 16):
143142c5cccSJerome Forissier            return ''
144142c5cccSJerome Forissier        return '0x{:x}'.format(int(addr, 16) - int(offs, 16))
145142c5cccSJerome Forissier
146142c5cccSJerome Forissier    def resolve(self, addr):
147142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
148733a15f2SJerome Forissier        self.spawn_addr2line()
149142c5cccSJerome Forissier        if not reladdr or not self._addr2line:
150733a15f2SJerome Forissier            return '???'
151733a15f2SJerome Forissier        try:
152733a15f2SJerome Forissier            print >> self._addr2line.stdin, reladdr
153733a15f2SJerome Forissier            ret = self._addr2line.stdout.readline().rstrip('\n')
154733a15f2SJerome Forissier        except IOError:
155733a15f2SJerome Forissier            ret = '!!!'
156733a15f2SJerome Forissier        return ret
157733a15f2SJerome Forissier
158142c5cccSJerome Forissier    def symbol_plus_offset(self, addr):
159142c5cccSJerome Forissier        ret = ''
160142c5cccSJerome Forissier        prevsize = 0
161142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
162142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
163142c5cccSJerome Forissier        cmd = self.arch_prefix('nm')
164142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
165142c5cccSJerome Forissier            return ''
16630999126SJerome Forissier        ireladdr = int(reladdr, 16)
167142c5cccSJerome Forissier        nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
168142c5cccSJerome Forissier                               stdin = subprocess.PIPE,
169142c5cccSJerome Forissier                               stdout = subprocess.PIPE)
170142c5cccSJerome Forissier        for line in iter(nm.stdout.readline, ''):
171142c5cccSJerome Forissier            try:
172142c5cccSJerome Forissier                addr, size, _, name = line.split()
173142c5cccSJerome Forissier            except:
174142c5cccSJerome Forissier                # Size is missing
175142c5cccSJerome Forissier                addr, _, name = line.split()
176142c5cccSJerome Forissier                size = '0'
177142c5cccSJerome Forissier            iaddr = int(addr, 16)
178142c5cccSJerome Forissier            isize = int(size, 16)
179142c5cccSJerome Forissier            if iaddr == ireladdr:
180142c5cccSJerome Forissier                ret = name
181142c5cccSJerome Forissier                break
182142c5cccSJerome Forissier            if iaddr < ireladdr and iaddr + isize >= ireladdr:
183142c5cccSJerome Forissier                offs = ireladdr - iaddr
184142c5cccSJerome Forissier                ret = name + '+' + str(offs)
185142c5cccSJerome Forissier                break
186142c5cccSJerome Forissier            if iaddr > ireladdr and prevsize == 0:
187142c5cccSJerome Forissier                offs = iaddr + ireladdr
188142c5cccSJerome Forissier                ret = prevname + '+' + str(offs)
189142c5cccSJerome Forissier                break
190142c5cccSJerome Forissier            prevsize = size
191142c5cccSJerome Forissier            prevname = name
192142c5cccSJerome Forissier        nm.terminate()
193142c5cccSJerome Forissier        return ret
194142c5cccSJerome Forissier
195142c5cccSJerome Forissier    def section_plus_offset(self, addr):
196142c5cccSJerome Forissier        ret = ''
197142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
198142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
199142c5cccSJerome Forissier        cmd = self.arch_prefix('objdump')
200142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
201142c5cccSJerome Forissier            return ''
20230999126SJerome Forissier        iaddr = int(reladdr, 16)
203142c5cccSJerome Forissier        objdump = subprocess.Popen([cmd, '--section-headers', elf],
204142c5cccSJerome Forissier                                    stdin = subprocess.PIPE,
205142c5cccSJerome Forissier                                    stdout = subprocess.PIPE)
206142c5cccSJerome Forissier        for line in iter(objdump.stdout.readline, ''):
207142c5cccSJerome Forissier            try:
208142c5cccSJerome Forissier                idx, name, size, vma, lma, offs, algn = line.split()
209142c5cccSJerome Forissier            except:
210142c5cccSJerome Forissier                continue;
211142c5cccSJerome Forissier            ivma = int(vma, 16)
212142c5cccSJerome Forissier            isize = int(size, 16)
213142c5cccSJerome Forissier            if ivma == iaddr:
214142c5cccSJerome Forissier                ret = name
215142c5cccSJerome Forissier                break
216142c5cccSJerome Forissier            if ivma < iaddr and ivma + isize >= iaddr:
217142c5cccSJerome Forissier                offs = iaddr - ivma
218142c5cccSJerome Forissier                ret = name + '+' + str(offs)
219142c5cccSJerome Forissier                break
220142c5cccSJerome Forissier        objdump.terminate()
221142c5cccSJerome Forissier        return ret
222142c5cccSJerome Forissier
223142c5cccSJerome Forissier    def process_abort(self, line):
224142c5cccSJerome Forissier        ret = ''
225142c5cccSJerome Forissier        match = re.search(ABORT_ADDR_RE, line)
226142c5cccSJerome Forissier        addr = match.group('addr')
227142c5cccSJerome Forissier        pre = match.start('addr')
228142c5cccSJerome Forissier        post = match.end('addr')
229142c5cccSJerome Forissier        sym = self.symbol_plus_offset(addr)
230142c5cccSJerome Forissier        sec = self.section_plus_offset(addr)
231142c5cccSJerome Forissier        if sym or sec:
232142c5cccSJerome Forissier            ret += line[:pre]
233142c5cccSJerome Forissier            ret += addr
234142c5cccSJerome Forissier            if sym:
235142c5cccSJerome Forissier                ret += ' ' + sym
236142c5cccSJerome Forissier            if sec:
237142c5cccSJerome Forissier                ret += ' ' + sec
238142c5cccSJerome Forissier            ret += line[post:]
239142c5cccSJerome Forissier        return ret
240142c5cccSJerome Forissier
24130999126SJerome Forissier    # Return all ELF sections with the ALLOC flag
24230999126SJerome Forissier    def read_sections(self):
24330999126SJerome Forissier        if self._sections:
24430999126SJerome Forissier            return
24530999126SJerome Forissier        elf = self.get_elf(self._bin)
24630999126SJerome Forissier        cmd = self.arch_prefix('objdump')
24730999126SJerome Forissier        if not elf or not cmd:
24830999126SJerome Forissier            return
24930999126SJerome Forissier        objdump = subprocess.Popen([cmd, '--section-headers', elf],
25030999126SJerome Forissier                                    stdin = subprocess.PIPE,
25130999126SJerome Forissier                                    stdout = subprocess.PIPE)
25230999126SJerome Forissier        for line in iter(objdump.stdout.readline, ''):
25330999126SJerome Forissier            try:
25430999126SJerome Forissier                _, name, size, vma, _, _, _ = line.split()
25530999126SJerome Forissier            except:
25630999126SJerome Forissier                if 'ALLOC' in line:
25730999126SJerome Forissier                    self._sections.append([name, int(vma, 16), int(size, 16)])
25830999126SJerome Forissier
25930999126SJerome Forissier    def overlaps(self, section, addr, size):
26030999126SJerome Forissier        sec_addr = section[1]
26130999126SJerome Forissier        sec_size = section[2]
26230999126SJerome Forissier        if not size or not sec_size:
26330999126SJerome Forissier            return False
26430999126SJerome Forissier        return (addr <= (sec_addr + sec_size - 1)) and ((addr + size - 1) >= sec_addr)
26530999126SJerome Forissier
26630999126SJerome Forissier    def sections_in_region(self, addr, size):
26730999126SJerome Forissier        ret = ''
26830999126SJerome Forissier        addr = self.subtract_load_addr(addr)
26930999126SJerome Forissier        if not addr:
27030999126SJerome Forissier            return ''
27130999126SJerome Forissier        iaddr = int(addr, 16)
27230999126SJerome Forissier        isize = int(size, 16)
27330999126SJerome Forissier        self.read_sections()
27430999126SJerome Forissier        for s in self._sections:
27530999126SJerome Forissier            if self.overlaps(s, iaddr, isize):
27630999126SJerome Forissier                ret += ' ' + s[0]
27730999126SJerome Forissier        return ret
27830999126SJerome Forissier
279733a15f2SJerome Forissier    def reset(self):
280733a15f2SJerome Forissier        self._call_stack_found = False
281733a15f2SJerome Forissier        self._load_addr = '0'
282733a15f2SJerome Forissier        if self._addr2line:
283733a15f2SJerome Forissier            self._addr2line.terminate()
284733a15f2SJerome Forissier            self._addr2line = None
285d720431cSJerome Forissier        self._arch = None
286142c5cccSJerome Forissier        self._saved_abort_line = ''
28730999126SJerome Forissier        self._sections = []
28827b83ad2SJerome Forissier        self._bin = "tee.elf"
289733a15f2SJerome Forissier
290733a15f2SJerome Forissier    def write(self, line):
291733a15f2SJerome Forissier            if self._call_stack_found:
292733a15f2SJerome Forissier                match = re.search(STACK_ADDR_RE, line)
293733a15f2SJerome Forissier                if match:
294733a15f2SJerome Forissier                    addr = match.group('addr')
295733a15f2SJerome Forissier                    pre = match.start('addr')
296733a15f2SJerome Forissier                    post = match.end('addr')
297733a15f2SJerome Forissier                    self._out.write(line[:pre])
298733a15f2SJerome Forissier                    self._out.write(addr)
299733a15f2SJerome Forissier                    res = self.resolve(addr)
300733a15f2SJerome Forissier                    if self._strip_path:
301733a15f2SJerome Forissier                        res = re.sub(re.escape(self._strip_path) + '/*', '',
302733a15f2SJerome Forissier                              res)
303733a15f2SJerome Forissier                    self._out.write(' ' + res)
304733a15f2SJerome Forissier                    self._out.write(line[post:])
305733a15f2SJerome Forissier                    return
306733a15f2SJerome Forissier                else:
307733a15f2SJerome Forissier                    self.reset()
30830999126SJerome Forissier            match = re.search(REGION_RE, line)
30930999126SJerome Forissier            if match:
31030999126SJerome Forissier                addr = match.group('addr')
31130999126SJerome Forissier                size = match.group('size')
31230999126SJerome Forissier                self._out.write(line.strip() +
31330999126SJerome Forissier                                self.sections_in_region(addr, size) + '\n');
31430999126SJerome Forissier                return
315733a15f2SJerome Forissier            match = re.search(CALL_STACK_RE, line)
316733a15f2SJerome Forissier            if match:
317733a15f2SJerome Forissier                self._call_stack_found = True
318142c5cccSJerome Forissier                # Here is a good place to resolve the abort address because we
319142c5cccSJerome Forissier                # have all the information we need
320142c5cccSJerome Forissier                if self._saved_abort_line:
321142c5cccSJerome Forissier                    self._out.write(self.process_abort(self._saved_abort_line))
322733a15f2SJerome Forissier            match = re.search(TA_UUID_RE, line)
323733a15f2SJerome Forissier            if match:
324733a15f2SJerome Forissier                self._bin = match.group('uuid')
325733a15f2SJerome Forissier            match = re.search(TA_INFO_RE, line)
326733a15f2SJerome Forissier            if match:
327733a15f2SJerome Forissier                self._load_addr = match.group('load_addr')
328142c5cccSJerome Forissier            match = re.search(ABORT_ADDR_RE, line)
329142c5cccSJerome Forissier            if match:
33027b83ad2SJerome Forissier                self.reset()
331142c5cccSJerome Forissier                # At this point the arch and TA load address are unknown.
332142c5cccSJerome Forissier                # Save the line so We can translate the abort address later.
333142c5cccSJerome Forissier                self._saved_abort_line = line
334733a15f2SJerome Forissier            self._out.write(line)
335733a15f2SJerome Forissier
336733a15f2SJerome Forissier    def flush(self):
337733a15f2SJerome Forissier        self._out.flush()
338733a15f2SJerome Forissier
339733a15f2SJerome Forissierdef main():
340733a15f2SJerome Forissier    args = get_args()
341733a15f2SJerome Forissier    if args.dir:
342733a15f2SJerome Forissier        # Flatten list in case -d is used several times *and* with multiple
343733a15f2SJerome Forissier        # arguments
344733a15f2SJerome Forissier        args.dirs = [item for sublist in args.dir for item in sublist]
345733a15f2SJerome Forissier    else:
346733a15f2SJerome Forissier        args.dirs = []
347733a15f2SJerome Forissier    symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
348733a15f2SJerome Forissier
349733a15f2SJerome Forissier    for line in sys.stdin:
350733a15f2SJerome Forissier        symbolizer.write(line)
351733a15f2SJerome Forissier    symbolizer.flush()
352733a15f2SJerome Forissier
353733a15f2SJerome Forissierif __name__ == "__main__":
354733a15f2SJerome Forissier    main()
355