xref: /optee_os/scripts/symbolize.py (revision 27b83ad29a73d284705b8384bce73dea4f7c84cd)
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
32157e6213SJerome 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]+)')
42142c5cccSJerome ForissierABORT_ADDR_RE = re.compile('-abort at address (?P<addr>0x[0-9a-f]+)')
4330999126SJerome ForissierREGION_RE = re.compile('region [0-9]+: va (?P<addr>0x[0-9a-f]+) '
4430999126SJerome Forissier                       'pa 0x[0-9a-f]+ size (?P<size>0x[0-9a-f]+)')
45733a15f2SJerome Forissier
46733a15f2SJerome Forissierepilog = '''
47733a15f2SJerome ForissierThis scripts reads an OP-TEE abort message from stdin and adds debug
48733a15f2SJerome Forissierinformation ('function at file:line') next to each address in the call stack.
49733a15f2SJerome ForissierIt uses the paths provided on the command line to locate the appropriate ELF
50733a15f2SJerome Forissierbinary (tee.elf or Trusted Application) and runs arm-linux-gnueabihf-addr2line
51733a15f2SJerome Forissieror aarch64-linux-gnu-addr2line to process the addresses.
52733a15f2SJerome Forissier
53733a15f2SJerome ForissierOP-TEE abort messages are sent to the secure console. They look like the
54733a15f2SJerome Forissierfollowing:
55733a15f2SJerome Forissier
56733a15f2SJerome Forissier  ERROR:   TEE-CORE: User TA data-abort at address 0xffffdecd (alignment fault)
57733a15f2SJerome Forissier  ...
58733a15f2SJerome Forissier  ERROR:   TEE-CORE: Call stack:
59733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000549e
60733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40001f4b
61733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000273f
62733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40005da7
63733a15f2SJerome Forissier
64733a15f2SJerome ForissierInspired by a script of the same name by the Chromium project.
65733a15f2SJerome Forissier
66733a15f2SJerome ForissierSample usage:
67733a15f2SJerome Forissier
68733a15f2SJerome Forissier  $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
69733a15f2SJerome Forissier  <paste whole dump here>
70733a15f2SJerome Forissier  ^D
71733a15f2SJerome Forissier'''
72733a15f2SJerome Forissier
73733a15f2SJerome Forissierdef get_args():
74733a15f2SJerome Forissier    parser = argparse.ArgumentParser(
75733a15f2SJerome Forissier                formatter_class=argparse.RawDescriptionHelpFormatter,
76733a15f2SJerome Forissier                description='Symbolizes OP-TEE abort dumps',
77733a15f2SJerome Forissier                epilog=epilog)
78733a15f2SJerome Forissier    parser.add_argument('-d', '--dir', action='append', nargs='+',
79733a15f2SJerome Forissier        help='Search for ELF file in DIR. tee.elf is needed to decode '
80733a15f2SJerome Forissier             'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required '
81157e6213SJerome Forissier             'if a user-mode TA has crashed. For convenience, ELF files '
82157e6213SJerome Forissier             'may also be given.')
83733a15f2SJerome Forissier    parser.add_argument('-s', '--strip_path',
84733a15f2SJerome Forissier        help='Strip STRIP_PATH from file paths')
85733a15f2SJerome Forissier
86733a15f2SJerome Forissier    return parser.parse_args()
87733a15f2SJerome Forissier
88733a15f2SJerome Forissierclass Symbolizer(object):
89733a15f2SJerome Forissier    def __init__(self, out, dirs, strip_path):
90733a15f2SJerome Forissier        self._out = out
91733a15f2SJerome Forissier        self._dirs = dirs
92733a15f2SJerome Forissier        self._strip_path = strip_path
93733a15f2SJerome Forissier        self._addr2line = None
94733a15f2SJerome Forissier        self._bin = 'tee.elf'
95733a15f2SJerome Forissier        self.reset()
96733a15f2SJerome Forissier
97733a15f2SJerome Forissier    def get_elf(self, elf_or_uuid):
98733a15f2SJerome Forissier        if not elf_or_uuid.endswith('.elf'):
99733a15f2SJerome Forissier            elf_or_uuid += '.elf'
100733a15f2SJerome Forissier        for d in self._dirs:
101157e6213SJerome Forissier            if d.endswith(elf_or_uuid) and os.path.isfile(d):
102157e6213SJerome Forissier                return d
103733a15f2SJerome Forissier            elf = glob.glob(d + '/' + elf_or_uuid)
104733a15f2SJerome Forissier            if elf:
105733a15f2SJerome Forissier                return elf[0]
106733a15f2SJerome Forissier
107d720431cSJerome Forissier    def set_arch(self):
108d720431cSJerome Forissier        if self._arch:
109d720431cSJerome Forissier            return
110d720431cSJerome Forissier        if self._bin:
111d720431cSJerome Forissier            p = subprocess.Popen([ 'file', self.get_elf(self._bin) ],
112d720431cSJerome Forissier                                 stdout=subprocess.PIPE)
113d720431cSJerome Forissier            output = p.stdout.readlines()
114d720431cSJerome Forissier            p.terminate()
115d720431cSJerome Forissier            if 'ARM aarch64,' in output[0]:
116d720431cSJerome Forissier                self._arch = 'aarch64-linux-gnu-'
117d720431cSJerome Forissier            elif 'ARM,' in output[0]:
118d720431cSJerome Forissier                self._arch = 'arm-linux-gnueabihf-'
119d720431cSJerome Forissier
120142c5cccSJerome Forissier    def arch_prefix(self, cmd):
121d720431cSJerome Forissier        self.set_arch()
122d720431cSJerome Forissier        return self._arch + cmd
123142c5cccSJerome Forissier
124733a15f2SJerome Forissier    def spawn_addr2line(self):
125733a15f2SJerome Forissier        if not self._addr2line:
126733a15f2SJerome Forissier            elf = self.get_elf(self._bin)
127733a15f2SJerome Forissier            if not elf:
128733a15f2SJerome Forissier                return
129142c5cccSJerome Forissier            cmd = self.arch_prefix('addr2line')
130142c5cccSJerome Forissier            if not cmd:
131733a15f2SJerome Forissier                return
132733a15f2SJerome Forissier            self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
133733a15f2SJerome Forissier                                                stdin = subprocess.PIPE,
134733a15f2SJerome Forissier                                                stdout = subprocess.PIPE)
135733a15f2SJerome Forissier
136142c5cccSJerome Forissier    def subtract_load_addr(self, addr):
137733a15f2SJerome Forissier        offs = self._load_addr
138fd5d0622SJerome Forissier        if int(offs, 16) > int(addr, 16):
139142c5cccSJerome Forissier            return ''
140142c5cccSJerome Forissier        return '0x{:x}'.format(int(addr, 16) - int(offs, 16))
141142c5cccSJerome Forissier
142142c5cccSJerome Forissier    def resolve(self, addr):
143142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
144733a15f2SJerome Forissier        self.spawn_addr2line()
145142c5cccSJerome Forissier        if not reladdr or not self._addr2line:
146733a15f2SJerome Forissier            return '???'
147733a15f2SJerome Forissier        try:
148733a15f2SJerome Forissier            print >> self._addr2line.stdin, reladdr
149733a15f2SJerome Forissier            ret = self._addr2line.stdout.readline().rstrip('\n')
150733a15f2SJerome Forissier        except IOError:
151733a15f2SJerome Forissier            ret = '!!!'
152733a15f2SJerome Forissier        return ret
153733a15f2SJerome Forissier
154142c5cccSJerome Forissier    def symbol_plus_offset(self, addr):
155142c5cccSJerome Forissier        ret = ''
156142c5cccSJerome Forissier        prevsize = 0
157142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
158142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
159142c5cccSJerome Forissier        cmd = self.arch_prefix('nm')
160142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
161142c5cccSJerome Forissier            return ''
16230999126SJerome Forissier        ireladdr = int(reladdr, 16)
163142c5cccSJerome Forissier        nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
164142c5cccSJerome Forissier                               stdin = subprocess.PIPE,
165142c5cccSJerome Forissier                               stdout = subprocess.PIPE)
166142c5cccSJerome Forissier        for line in iter(nm.stdout.readline, ''):
167142c5cccSJerome Forissier            try:
168142c5cccSJerome Forissier                addr, size, _, name = line.split()
169142c5cccSJerome Forissier            except:
170142c5cccSJerome Forissier                # Size is missing
171142c5cccSJerome Forissier                addr, _, name = line.split()
172142c5cccSJerome Forissier                size = '0'
173142c5cccSJerome Forissier            iaddr = int(addr, 16)
174142c5cccSJerome Forissier            isize = int(size, 16)
175142c5cccSJerome Forissier            if iaddr == ireladdr:
176142c5cccSJerome Forissier                ret = name
177142c5cccSJerome Forissier                break
178142c5cccSJerome Forissier            if iaddr < ireladdr and iaddr + isize >= ireladdr:
179142c5cccSJerome Forissier                offs = ireladdr - iaddr
180142c5cccSJerome Forissier                ret = name + '+' + str(offs)
181142c5cccSJerome Forissier                break
182142c5cccSJerome Forissier            if iaddr > ireladdr and prevsize == 0:
183142c5cccSJerome Forissier                offs = iaddr + ireladdr
184142c5cccSJerome Forissier                ret = prevname + '+' + str(offs)
185142c5cccSJerome Forissier                break
186142c5cccSJerome Forissier            prevsize = size
187142c5cccSJerome Forissier            prevname = name
188142c5cccSJerome Forissier        nm.terminate()
189142c5cccSJerome Forissier        return ret
190142c5cccSJerome Forissier
191142c5cccSJerome Forissier    def section_plus_offset(self, addr):
192142c5cccSJerome Forissier        ret = ''
193142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
194142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
195142c5cccSJerome Forissier        cmd = self.arch_prefix('objdump')
196142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
197142c5cccSJerome Forissier            return ''
19830999126SJerome Forissier        iaddr = int(reladdr, 16)
199142c5cccSJerome Forissier        objdump = subprocess.Popen([cmd, '--section-headers', elf],
200142c5cccSJerome Forissier                                    stdin = subprocess.PIPE,
201142c5cccSJerome Forissier                                    stdout = subprocess.PIPE)
202142c5cccSJerome Forissier        for line in iter(objdump.stdout.readline, ''):
203142c5cccSJerome Forissier            try:
204142c5cccSJerome Forissier                idx, name, size, vma, lma, offs, algn = line.split()
205142c5cccSJerome Forissier            except:
206142c5cccSJerome Forissier                continue;
207142c5cccSJerome Forissier            ivma = int(vma, 16)
208142c5cccSJerome Forissier            isize = int(size, 16)
209142c5cccSJerome Forissier            if ivma == iaddr:
210142c5cccSJerome Forissier                ret = name
211142c5cccSJerome Forissier                break
212142c5cccSJerome Forissier            if ivma < iaddr and ivma + isize >= iaddr:
213142c5cccSJerome Forissier                offs = iaddr - ivma
214142c5cccSJerome Forissier                ret = name + '+' + str(offs)
215142c5cccSJerome Forissier                break
216142c5cccSJerome Forissier        objdump.terminate()
217142c5cccSJerome Forissier        return ret
218142c5cccSJerome Forissier
219142c5cccSJerome Forissier    def process_abort(self, line):
220142c5cccSJerome Forissier        ret = ''
221142c5cccSJerome Forissier        match = re.search(ABORT_ADDR_RE, line)
222142c5cccSJerome Forissier        addr = match.group('addr')
223142c5cccSJerome Forissier        pre = match.start('addr')
224142c5cccSJerome Forissier        post = match.end('addr')
225142c5cccSJerome Forissier        sym = self.symbol_plus_offset(addr)
226142c5cccSJerome Forissier        sec = self.section_plus_offset(addr)
227142c5cccSJerome Forissier        if sym or sec:
228142c5cccSJerome Forissier            ret += line[:pre]
229142c5cccSJerome Forissier            ret += addr
230142c5cccSJerome Forissier            if sym:
231142c5cccSJerome Forissier                ret += ' ' + sym
232142c5cccSJerome Forissier            if sec:
233142c5cccSJerome Forissier                ret += ' ' + sec
234142c5cccSJerome Forissier            ret += line[post:]
235142c5cccSJerome Forissier        return ret
236142c5cccSJerome Forissier
23730999126SJerome Forissier    # Return all ELF sections with the ALLOC flag
23830999126SJerome Forissier    def read_sections(self):
23930999126SJerome Forissier        if self._sections:
24030999126SJerome Forissier            return
24130999126SJerome Forissier        elf = self.get_elf(self._bin)
24230999126SJerome Forissier        cmd = self.arch_prefix('objdump')
24330999126SJerome Forissier        if not elf or not cmd:
24430999126SJerome Forissier            return
24530999126SJerome Forissier        objdump = subprocess.Popen([cmd, '--section-headers', elf],
24630999126SJerome Forissier                                    stdin = subprocess.PIPE,
24730999126SJerome Forissier                                    stdout = subprocess.PIPE)
24830999126SJerome Forissier        for line in iter(objdump.stdout.readline, ''):
24930999126SJerome Forissier            try:
25030999126SJerome Forissier                _, name, size, vma, _, _, _ = line.split()
25130999126SJerome Forissier            except:
25230999126SJerome Forissier                if 'ALLOC' in line:
25330999126SJerome Forissier                    self._sections.append([name, int(vma, 16), int(size, 16)])
25430999126SJerome Forissier
25530999126SJerome Forissier    def overlaps(self, section, addr, size):
25630999126SJerome Forissier        sec_addr = section[1]
25730999126SJerome Forissier        sec_size = section[2]
25830999126SJerome Forissier        if not size or not sec_size:
25930999126SJerome Forissier            return False
26030999126SJerome Forissier        return (addr <= (sec_addr + sec_size - 1)) and ((addr + size - 1) >= sec_addr)
26130999126SJerome Forissier
26230999126SJerome Forissier    def sections_in_region(self, addr, size):
26330999126SJerome Forissier        ret = ''
26430999126SJerome Forissier        addr = self.subtract_load_addr(addr)
26530999126SJerome Forissier        if not addr:
26630999126SJerome Forissier            return ''
26730999126SJerome Forissier        iaddr = int(addr, 16)
26830999126SJerome Forissier        isize = int(size, 16)
26930999126SJerome Forissier        self.read_sections()
27030999126SJerome Forissier        for s in self._sections:
27130999126SJerome Forissier            if self.overlaps(s, iaddr, isize):
27230999126SJerome Forissier                ret += ' ' + s[0]
27330999126SJerome Forissier        return ret
27430999126SJerome Forissier
275733a15f2SJerome Forissier    def reset(self):
276733a15f2SJerome Forissier        self._call_stack_found = False
277733a15f2SJerome Forissier        self._load_addr = '0'
278733a15f2SJerome Forissier        if self._addr2line:
279733a15f2SJerome Forissier            self._addr2line.terminate()
280733a15f2SJerome Forissier            self._addr2line = None
281d720431cSJerome Forissier        self._arch = None
282142c5cccSJerome Forissier        self._saved_abort_line = ''
28330999126SJerome Forissier        self._sections = []
284*27b83ad2SJerome Forissier        self._bin = "tee.elf"
285733a15f2SJerome Forissier
286733a15f2SJerome Forissier    def write(self, line):
287733a15f2SJerome Forissier            if self._call_stack_found:
288733a15f2SJerome Forissier                match = re.search(STACK_ADDR_RE, line)
289733a15f2SJerome Forissier                if match:
290733a15f2SJerome Forissier                    addr = match.group('addr')
291733a15f2SJerome Forissier                    pre = match.start('addr')
292733a15f2SJerome Forissier                    post = match.end('addr')
293733a15f2SJerome Forissier                    self._out.write(line[:pre])
294733a15f2SJerome Forissier                    self._out.write(addr)
295733a15f2SJerome Forissier                    res = self.resolve(addr)
296733a15f2SJerome Forissier                    if self._strip_path:
297733a15f2SJerome Forissier                        res = re.sub(re.escape(self._strip_path) + '/*', '',
298733a15f2SJerome Forissier                              res)
299733a15f2SJerome Forissier                    self._out.write(' ' + res)
300733a15f2SJerome Forissier                    self._out.write(line[post:])
301733a15f2SJerome Forissier                    return
302733a15f2SJerome Forissier                else:
303733a15f2SJerome Forissier                    self.reset()
30430999126SJerome Forissier            match = re.search(REGION_RE, line)
30530999126SJerome Forissier            if match:
30630999126SJerome Forissier                addr = match.group('addr')
30730999126SJerome Forissier                size = match.group('size')
30830999126SJerome Forissier                self._out.write(line.strip() +
30930999126SJerome Forissier                                self.sections_in_region(addr, size) + '\n');
31030999126SJerome Forissier                return
311733a15f2SJerome Forissier            match = re.search(CALL_STACK_RE, line)
312733a15f2SJerome Forissier            if match:
313733a15f2SJerome Forissier                self._call_stack_found = True
314142c5cccSJerome Forissier                # Here is a good place to resolve the abort address because we
315142c5cccSJerome Forissier                # have all the information we need
316142c5cccSJerome Forissier                if self._saved_abort_line:
317142c5cccSJerome Forissier                    self._out.write(self.process_abort(self._saved_abort_line))
318733a15f2SJerome Forissier            match = re.search(TA_UUID_RE, line)
319733a15f2SJerome Forissier            if match:
320733a15f2SJerome Forissier                self._bin = match.group('uuid')
321733a15f2SJerome Forissier            match = re.search(TA_INFO_RE, line)
322733a15f2SJerome Forissier            if match:
323733a15f2SJerome Forissier                self._load_addr = match.group('load_addr')
324142c5cccSJerome Forissier            match = re.search(ABORT_ADDR_RE, line)
325142c5cccSJerome Forissier            if match:
326*27b83ad2SJerome Forissier                self.reset()
327142c5cccSJerome Forissier                # At this point the arch and TA load address are unknown.
328142c5cccSJerome Forissier                # Save the line so We can translate the abort address later.
329142c5cccSJerome Forissier                self._saved_abort_line = line
330733a15f2SJerome Forissier            self._out.write(line)
331733a15f2SJerome Forissier
332733a15f2SJerome Forissier    def flush(self):
333733a15f2SJerome Forissier        self._out.flush()
334733a15f2SJerome Forissier
335733a15f2SJerome Forissierdef main():
336733a15f2SJerome Forissier    args = get_args()
337733a15f2SJerome Forissier    if args.dir:
338733a15f2SJerome Forissier        # Flatten list in case -d is used several times *and* with multiple
339733a15f2SJerome Forissier        # arguments
340733a15f2SJerome Forissier        args.dirs = [item for sublist in args.dir for item in sublist]
341733a15f2SJerome Forissier    else:
342733a15f2SJerome Forissier        args.dirs = []
343733a15f2SJerome Forissier    symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
344733a15f2SJerome Forissier
345733a15f2SJerome Forissier    for line in sys.stdin:
346733a15f2SJerome Forissier        symbolizer.write(line)
347733a15f2SJerome Forissier    symbolizer.flush()
348733a15f2SJerome Forissier
349733a15f2SJerome Forissierif __name__ == "__main__":
350733a15f2SJerome Forissier    main()
351