xref: /optee_os/scripts/symbolize.py (revision 309991262b5d996439962aabedd341dcd206abff)
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]+)')
43*30999126SJerome ForissierREGION_RE = re.compile('region [0-9]+: va (?P<addr>0x[0-9a-f]+) '
44*30999126SJerome 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 ''
162*30999126SJerome 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 ''
198*30999126SJerome 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
237*30999126SJerome Forissier    # Return all ELF sections with the ALLOC flag
238*30999126SJerome Forissier    def read_sections(self):
239*30999126SJerome Forissier        if self._sections:
240*30999126SJerome Forissier            return
241*30999126SJerome Forissier        elf = self.get_elf(self._bin)
242*30999126SJerome Forissier        cmd = self.arch_prefix('objdump')
243*30999126SJerome Forissier        if not elf or not cmd:
244*30999126SJerome Forissier            return
245*30999126SJerome Forissier        objdump = subprocess.Popen([cmd, '--section-headers', elf],
246*30999126SJerome Forissier                                    stdin = subprocess.PIPE,
247*30999126SJerome Forissier                                    stdout = subprocess.PIPE)
248*30999126SJerome Forissier        for line in iter(objdump.stdout.readline, ''):
249*30999126SJerome Forissier            try:
250*30999126SJerome Forissier                _, name, size, vma, _, _, _ = line.split()
251*30999126SJerome Forissier            except:
252*30999126SJerome Forissier                if 'ALLOC' in line:
253*30999126SJerome Forissier                    self._sections.append([name, int(vma, 16), int(size, 16)])
254*30999126SJerome Forissier
255*30999126SJerome Forissier    def overlaps(self, section, addr, size):
256*30999126SJerome Forissier        sec_addr = section[1]
257*30999126SJerome Forissier        sec_size = section[2]
258*30999126SJerome Forissier        if not size or not sec_size:
259*30999126SJerome Forissier            return False
260*30999126SJerome Forissier        return (addr <= (sec_addr + sec_size - 1)) and ((addr + size - 1) >= sec_addr)
261*30999126SJerome Forissier
262*30999126SJerome Forissier    def sections_in_region(self, addr, size):
263*30999126SJerome Forissier        ret = ''
264*30999126SJerome Forissier        addr = self.subtract_load_addr(addr)
265*30999126SJerome Forissier        if not addr:
266*30999126SJerome Forissier            return ''
267*30999126SJerome Forissier        iaddr = int(addr, 16)
268*30999126SJerome Forissier        isize = int(size, 16)
269*30999126SJerome Forissier        self.read_sections()
270*30999126SJerome Forissier        for s in self._sections:
271*30999126SJerome Forissier            if self.overlaps(s, iaddr, isize):
272*30999126SJerome Forissier                ret += ' ' + s[0]
273*30999126SJerome Forissier        return ret
274*30999126SJerome 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 = ''
283*30999126SJerome Forissier        self._sections = []
284733a15f2SJerome Forissier
285733a15f2SJerome Forissier    def write(self, line):
286733a15f2SJerome Forissier            if self._call_stack_found:
287733a15f2SJerome Forissier                match = re.search(STACK_ADDR_RE, line)
288733a15f2SJerome Forissier                if match:
289733a15f2SJerome Forissier                    addr = match.group('addr')
290733a15f2SJerome Forissier                    pre = match.start('addr')
291733a15f2SJerome Forissier                    post = match.end('addr')
292733a15f2SJerome Forissier                    self._out.write(line[:pre])
293733a15f2SJerome Forissier                    self._out.write(addr)
294733a15f2SJerome Forissier                    res = self.resolve(addr)
295733a15f2SJerome Forissier                    if self._strip_path:
296733a15f2SJerome Forissier                        res = re.sub(re.escape(self._strip_path) + '/*', '',
297733a15f2SJerome Forissier                              res)
298733a15f2SJerome Forissier                    self._out.write(' ' + res)
299733a15f2SJerome Forissier                    self._out.write(line[post:])
300733a15f2SJerome Forissier                    return
301733a15f2SJerome Forissier                else:
302733a15f2SJerome Forissier                    self.reset()
303*30999126SJerome Forissier            match = re.search(REGION_RE, line)
304*30999126SJerome Forissier            if match:
305*30999126SJerome Forissier                addr = match.group('addr')
306*30999126SJerome Forissier                size = match.group('size')
307*30999126SJerome Forissier                self._out.write(line.strip() +
308*30999126SJerome Forissier                                self.sections_in_region(addr, size) + '\n');
309*30999126SJerome Forissier                return
310733a15f2SJerome Forissier            match = re.search(CALL_STACK_RE, line)
311733a15f2SJerome Forissier            if match:
312733a15f2SJerome Forissier                self._call_stack_found = True
313142c5cccSJerome Forissier                # Here is a good place to resolve the abort address because we
314142c5cccSJerome Forissier                # have all the information we need
315142c5cccSJerome Forissier                if self._saved_abort_line:
316142c5cccSJerome Forissier                    self._out.write(self.process_abort(self._saved_abort_line))
317733a15f2SJerome Forissier            match = re.search(TA_UUID_RE, line)
318733a15f2SJerome Forissier            if match:
319733a15f2SJerome Forissier                self._bin = match.group('uuid')
320733a15f2SJerome Forissier            match = re.search(TA_INFO_RE, line)
321733a15f2SJerome Forissier            if match:
322733a15f2SJerome Forissier                self._load_addr = match.group('load_addr')
323142c5cccSJerome Forissier            match = re.search(ABORT_ADDR_RE, line)
324142c5cccSJerome Forissier            if match:
325142c5cccSJerome Forissier                # At this point the arch and TA load address are unknown.
326142c5cccSJerome Forissier                # Save the line so We can translate the abort address later.
327142c5cccSJerome Forissier                self._saved_abort_line = line
328733a15f2SJerome Forissier            self._out.write(line)
329733a15f2SJerome Forissier
330733a15f2SJerome Forissier    def flush(self):
331733a15f2SJerome Forissier        self._out.flush()
332733a15f2SJerome Forissier
333733a15f2SJerome Forissierdef main():
334733a15f2SJerome Forissier    args = get_args()
335733a15f2SJerome Forissier    if args.dir:
336733a15f2SJerome Forissier        # Flatten list in case -d is used several times *and* with multiple
337733a15f2SJerome Forissier        # arguments
338733a15f2SJerome Forissier        args.dirs = [item for sublist in args.dir for item in sublist]
339733a15f2SJerome Forissier    else:
340733a15f2SJerome Forissier        args.dirs = []
341733a15f2SJerome Forissier    symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
342733a15f2SJerome Forissier
343733a15f2SJerome Forissier    for line in sys.stdin:
344733a15f2SJerome Forissier        symbolizer.write(line)
345733a15f2SJerome Forissier    symbolizer.flush()
346733a15f2SJerome Forissier
347733a15f2SJerome Forissierif __name__ == "__main__":
348733a15f2SJerome Forissier    main()
349