xref: /optee_os/scripts/symbolize.py (revision 5f7df50732ee837f1866a39e3ee4bac4d28dc679)
1733a15f2SJerome Forissier#!/usr/bin/env python
21bb92983SJerome Forissier# SPDX-License-Identifier: BSD-2-Clause
3733a15f2SJerome Forissier#
4733a15f2SJerome Forissier# Copyright (c) 2017, Linaro Limited
5733a15f2SJerome Forissier#
6733a15f2SJerome Forissier
7733a15f2SJerome Forissier
8733a15f2SJerome Forissierimport argparse
9733a15f2SJerome Forissierimport glob
10157e6213SJerome Forissierimport os
11733a15f2SJerome Forissierimport re
12733a15f2SJerome Forissierimport subprocess
13733a15f2SJerome Forissierimport sys
14733a15f2SJerome Forissier
15733a15f2SJerome ForissierTA_UUID_RE = re.compile(r'Status of TA (?P<uuid>[0-9a-f\-]+)')
16a2b984bdSJoakim BechTA_INFO_RE = re.compile('  arch: (?P<arch>\w+)  '
17733a15f2SJerome Forissier                        'load address: (?P<load_addr>0x[0-9a-f]+)')
18733a15f2SJerome ForissierCALL_STACK_RE = re.compile('Call stack:')
19a2b984bdSJoakim Bech
20a2b984bdSJoakim Bech# This gets the address from lines looking like this:
21a2b984bdSJoakim Bech# E/TC:0  0x001044a8
22a2b984bdSJoakim BechSTACK_ADDR_RE = re.compile(r'[UEIDFM]/T[AC]:.*(?P<addr>0x[0-9a-f]+)')
23142c5cccSJerome ForissierABORT_ADDR_RE = re.compile('-abort at address (?P<addr>0x[0-9a-f]+)')
2430999126SJerome ForissierREGION_RE = re.compile('region [0-9]+: va (?P<addr>0x[0-9a-f]+) '
2530999126SJerome Forissier                       'pa 0x[0-9a-f]+ size (?P<size>0x[0-9a-f]+)')
26733a15f2SJerome Forissier
27733a15f2SJerome Forissierepilog = '''
28733a15f2SJerome ForissierThis scripts reads an OP-TEE abort message from stdin and adds debug
29733a15f2SJerome Forissierinformation ('function at file:line') next to each address in the call stack.
30733a15f2SJerome ForissierIt uses the paths provided on the command line to locate the appropriate ELF
31733a15f2SJerome Forissierbinary (tee.elf or Trusted Application) and runs arm-linux-gnueabihf-addr2line
32733a15f2SJerome Forissieror aarch64-linux-gnu-addr2line to process the addresses.
33733a15f2SJerome Forissier
34733a15f2SJerome ForissierOP-TEE abort messages are sent to the secure console. They look like the
35733a15f2SJerome Forissierfollowing:
36733a15f2SJerome Forissier
37733a15f2SJerome Forissier  ERROR:   TEE-CORE: User TA data-abort at address 0xffffdecd (alignment fault)
38733a15f2SJerome Forissier  ...
39733a15f2SJerome Forissier  ERROR:   TEE-CORE: Call stack:
40733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000549e
41733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40001f4b
42733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x4000273f
43733a15f2SJerome Forissier  ERROR:   TEE-CORE:  0x40005da7
44733a15f2SJerome Forissier
45733a15f2SJerome ForissierInspired by a script of the same name by the Chromium project.
46733a15f2SJerome Forissier
47733a15f2SJerome ForissierSample usage:
48733a15f2SJerome Forissier
49733a15f2SJerome Forissier  $ scripts/symbolize.py -d out/arm-plat-hikey/core -d ../optee_test/out/ta/*
50733a15f2SJerome Forissier  <paste whole dump here>
51733a15f2SJerome Forissier  ^D
52733a15f2SJerome Forissier'''
53733a15f2SJerome Forissier
54733a15f2SJerome Forissierdef get_args():
55733a15f2SJerome Forissier    parser = argparse.ArgumentParser(
56733a15f2SJerome Forissier                formatter_class=argparse.RawDescriptionHelpFormatter,
57733a15f2SJerome Forissier                description='Symbolizes OP-TEE abort dumps',
58733a15f2SJerome Forissier                epilog=epilog)
59733a15f2SJerome Forissier    parser.add_argument('-d', '--dir', action='append', nargs='+',
60733a15f2SJerome Forissier        help='Search for ELF file in DIR. tee.elf is needed to decode '
61733a15f2SJerome Forissier             'a TEE Core or pseudo-TA abort, while <TA_uuid>.elf is required '
62157e6213SJerome Forissier             'if a user-mode TA has crashed. For convenience, ELF files '
63157e6213SJerome Forissier             'may also be given.')
64*5f7df507SJerome Forissier    parser.add_argument('-s', '--strip_path', nargs='?',
65*5f7df507SJerome Forissier        help='Strip STRIP_PATH from file paths (default: current directory, '
66*5f7df507SJerome Forissier             'use -s with no argument to show full paths)',
67*5f7df507SJerome Forissier        default=os.getcwd())
68733a15f2SJerome Forissier
69733a15f2SJerome Forissier    return parser.parse_args()
70733a15f2SJerome Forissier
71733a15f2SJerome Forissierclass Symbolizer(object):
72733a15f2SJerome Forissier    def __init__(self, out, dirs, strip_path):
73733a15f2SJerome Forissier        self._out = out
74733a15f2SJerome Forissier        self._dirs = dirs
75733a15f2SJerome Forissier        self._strip_path = strip_path
76733a15f2SJerome Forissier        self._addr2line = None
77733a15f2SJerome Forissier        self._bin = 'tee.elf'
78733a15f2SJerome Forissier        self.reset()
79733a15f2SJerome Forissier
80733a15f2SJerome Forissier    def get_elf(self, elf_or_uuid):
81733a15f2SJerome Forissier        if not elf_or_uuid.endswith('.elf'):
82733a15f2SJerome Forissier            elf_or_uuid += '.elf'
83733a15f2SJerome Forissier        for d in self._dirs:
84157e6213SJerome Forissier            if d.endswith(elf_or_uuid) and os.path.isfile(d):
85157e6213SJerome Forissier                return d
86733a15f2SJerome Forissier            elf = glob.glob(d + '/' + elf_or_uuid)
87733a15f2SJerome Forissier            if elf:
88733a15f2SJerome Forissier                return elf[0]
89733a15f2SJerome Forissier
90d720431cSJerome Forissier    def set_arch(self):
91d720431cSJerome Forissier        if self._arch:
92d720431cSJerome Forissier            return
93d720431cSJerome Forissier        if self._bin:
94d720431cSJerome Forissier            p = subprocess.Popen([ 'file', self.get_elf(self._bin) ],
95d720431cSJerome Forissier                                 stdout=subprocess.PIPE)
96d720431cSJerome Forissier            output = p.stdout.readlines()
97d720431cSJerome Forissier            p.terminate()
98d720431cSJerome Forissier            if 'ARM aarch64,' in output[0]:
99d720431cSJerome Forissier                self._arch = 'aarch64-linux-gnu-'
100d720431cSJerome Forissier            elif 'ARM,' in output[0]:
101d720431cSJerome Forissier                self._arch = 'arm-linux-gnueabihf-'
102d720431cSJerome Forissier
103142c5cccSJerome Forissier    def arch_prefix(self, cmd):
104d720431cSJerome Forissier        self.set_arch()
105d720431cSJerome Forissier        return self._arch + cmd
106142c5cccSJerome Forissier
107733a15f2SJerome Forissier    def spawn_addr2line(self):
108733a15f2SJerome Forissier        if not self._addr2line:
109733a15f2SJerome Forissier            elf = self.get_elf(self._bin)
110733a15f2SJerome Forissier            if not elf:
111733a15f2SJerome Forissier                return
112142c5cccSJerome Forissier            cmd = self.arch_prefix('addr2line')
113142c5cccSJerome Forissier            if not cmd:
114733a15f2SJerome Forissier                return
115733a15f2SJerome Forissier            self._addr2line = subprocess.Popen([cmd, '-f', '-p', '-e', elf],
116733a15f2SJerome Forissier                                                stdin = subprocess.PIPE,
117733a15f2SJerome Forissier                                                stdout = subprocess.PIPE)
118733a15f2SJerome Forissier
119142c5cccSJerome Forissier    def subtract_load_addr(self, addr):
120733a15f2SJerome Forissier        offs = self._load_addr
121fd5d0622SJerome Forissier        if int(offs, 16) > int(addr, 16):
122142c5cccSJerome Forissier            return ''
123142c5cccSJerome Forissier        return '0x{:x}'.format(int(addr, 16) - int(offs, 16))
124142c5cccSJerome Forissier
125142c5cccSJerome Forissier    def resolve(self, addr):
126142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
127733a15f2SJerome Forissier        self.spawn_addr2line()
128142c5cccSJerome Forissier        if not reladdr or not self._addr2line:
129733a15f2SJerome Forissier            return '???'
130733a15f2SJerome Forissier        try:
131733a15f2SJerome Forissier            print >> self._addr2line.stdin, reladdr
132733a15f2SJerome Forissier            ret = self._addr2line.stdout.readline().rstrip('\n')
133733a15f2SJerome Forissier        except IOError:
134733a15f2SJerome Forissier            ret = '!!!'
135733a15f2SJerome Forissier        return ret
136733a15f2SJerome Forissier
137142c5cccSJerome Forissier    def symbol_plus_offset(self, addr):
138142c5cccSJerome Forissier        ret = ''
139142c5cccSJerome Forissier        prevsize = 0
140142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
141142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
142142c5cccSJerome Forissier        cmd = self.arch_prefix('nm')
143142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
144142c5cccSJerome Forissier            return ''
14530999126SJerome Forissier        ireladdr = int(reladdr, 16)
146142c5cccSJerome Forissier        nm = subprocess.Popen([cmd, '--numeric-sort', '--print-size', elf],
147142c5cccSJerome Forissier                               stdin = subprocess.PIPE,
148142c5cccSJerome Forissier                               stdout = subprocess.PIPE)
149142c5cccSJerome Forissier        for line in iter(nm.stdout.readline, ''):
150142c5cccSJerome Forissier            try:
151142c5cccSJerome Forissier                addr, size, _, name = line.split()
152142c5cccSJerome Forissier            except:
153142c5cccSJerome Forissier                # Size is missing
154142c5cccSJerome Forissier                addr, _, name = line.split()
155142c5cccSJerome Forissier                size = '0'
156142c5cccSJerome Forissier            iaddr = int(addr, 16)
157142c5cccSJerome Forissier            isize = int(size, 16)
158142c5cccSJerome Forissier            if iaddr == ireladdr:
159142c5cccSJerome Forissier                ret = name
160142c5cccSJerome Forissier                break
161142c5cccSJerome Forissier            if iaddr < ireladdr and iaddr + isize >= ireladdr:
162142c5cccSJerome Forissier                offs = ireladdr - iaddr
163142c5cccSJerome Forissier                ret = name + '+' + str(offs)
164142c5cccSJerome Forissier                break
165142c5cccSJerome Forissier            if iaddr > ireladdr and prevsize == 0:
166142c5cccSJerome Forissier                offs = iaddr + ireladdr
167142c5cccSJerome Forissier                ret = prevname + '+' + str(offs)
168142c5cccSJerome Forissier                break
169142c5cccSJerome Forissier            prevsize = size
170142c5cccSJerome Forissier            prevname = name
171142c5cccSJerome Forissier        nm.terminate()
172142c5cccSJerome Forissier        return ret
173142c5cccSJerome Forissier
174142c5cccSJerome Forissier    def section_plus_offset(self, addr):
175142c5cccSJerome Forissier        ret = ''
176142c5cccSJerome Forissier        reladdr = self.subtract_load_addr(addr)
177142c5cccSJerome Forissier        elf = self.get_elf(self._bin)
178142c5cccSJerome Forissier        cmd = self.arch_prefix('objdump')
179142c5cccSJerome Forissier        if not reladdr or not elf or not cmd:
180142c5cccSJerome Forissier            return ''
18130999126SJerome Forissier        iaddr = int(reladdr, 16)
182142c5cccSJerome Forissier        objdump = subprocess.Popen([cmd, '--section-headers', elf],
183142c5cccSJerome Forissier                                    stdin = subprocess.PIPE,
184142c5cccSJerome Forissier                                    stdout = subprocess.PIPE)
185142c5cccSJerome Forissier        for line in iter(objdump.stdout.readline, ''):
186142c5cccSJerome Forissier            try:
187142c5cccSJerome Forissier                idx, name, size, vma, lma, offs, algn = line.split()
188142c5cccSJerome Forissier            except:
189142c5cccSJerome Forissier                continue;
190142c5cccSJerome Forissier            ivma = int(vma, 16)
191142c5cccSJerome Forissier            isize = int(size, 16)
192142c5cccSJerome Forissier            if ivma == iaddr:
193142c5cccSJerome Forissier                ret = name
194142c5cccSJerome Forissier                break
195142c5cccSJerome Forissier            if ivma < iaddr and ivma + isize >= iaddr:
196142c5cccSJerome Forissier                offs = iaddr - ivma
197142c5cccSJerome Forissier                ret = name + '+' + str(offs)
198142c5cccSJerome Forissier                break
199142c5cccSJerome Forissier        objdump.terminate()
200142c5cccSJerome Forissier        return ret
201142c5cccSJerome Forissier
202142c5cccSJerome Forissier    def process_abort(self, line):
203142c5cccSJerome Forissier        ret = ''
204142c5cccSJerome Forissier        match = re.search(ABORT_ADDR_RE, line)
205142c5cccSJerome Forissier        addr = match.group('addr')
206142c5cccSJerome Forissier        pre = match.start('addr')
207142c5cccSJerome Forissier        post = match.end('addr')
208142c5cccSJerome Forissier        sym = self.symbol_plus_offset(addr)
209142c5cccSJerome Forissier        sec = self.section_plus_offset(addr)
210142c5cccSJerome Forissier        if sym or sec:
211142c5cccSJerome Forissier            ret += line[:pre]
212142c5cccSJerome Forissier            ret += addr
213142c5cccSJerome Forissier            if sym:
214142c5cccSJerome Forissier                ret += ' ' + sym
215142c5cccSJerome Forissier            if sec:
216142c5cccSJerome Forissier                ret += ' ' + sec
217142c5cccSJerome Forissier            ret += line[post:]
218142c5cccSJerome Forissier        return ret
219142c5cccSJerome Forissier
22030999126SJerome Forissier    # Return all ELF sections with the ALLOC flag
22130999126SJerome Forissier    def read_sections(self):
22230999126SJerome Forissier        if self._sections:
22330999126SJerome Forissier            return
22430999126SJerome Forissier        elf = self.get_elf(self._bin)
22530999126SJerome Forissier        cmd = self.arch_prefix('objdump')
22630999126SJerome Forissier        if not elf or not cmd:
22730999126SJerome Forissier            return
22830999126SJerome Forissier        objdump = subprocess.Popen([cmd, '--section-headers', elf],
22930999126SJerome Forissier                                    stdin = subprocess.PIPE,
23030999126SJerome Forissier                                    stdout = subprocess.PIPE)
23130999126SJerome Forissier        for line in iter(objdump.stdout.readline, ''):
23230999126SJerome Forissier            try:
23330999126SJerome Forissier                _, name, size, vma, _, _, _ = line.split()
23430999126SJerome Forissier            except:
23530999126SJerome Forissier                if 'ALLOC' in line:
23630999126SJerome Forissier                    self._sections.append([name, int(vma, 16), int(size, 16)])
23730999126SJerome Forissier
23830999126SJerome Forissier    def overlaps(self, section, addr, size):
23930999126SJerome Forissier        sec_addr = section[1]
24030999126SJerome Forissier        sec_size = section[2]
24130999126SJerome Forissier        if not size or not sec_size:
24230999126SJerome Forissier            return False
24330999126SJerome Forissier        return (addr <= (sec_addr + sec_size - 1)) and ((addr + size - 1) >= sec_addr)
24430999126SJerome Forissier
24530999126SJerome Forissier    def sections_in_region(self, addr, size):
24630999126SJerome Forissier        ret = ''
24730999126SJerome Forissier        addr = self.subtract_load_addr(addr)
24830999126SJerome Forissier        if not addr:
24930999126SJerome Forissier            return ''
25030999126SJerome Forissier        iaddr = int(addr, 16)
25130999126SJerome Forissier        isize = int(size, 16)
25230999126SJerome Forissier        self.read_sections()
25330999126SJerome Forissier        for s in self._sections:
25430999126SJerome Forissier            if self.overlaps(s, iaddr, isize):
25530999126SJerome Forissier                ret += ' ' + s[0]
25630999126SJerome Forissier        return ret
25730999126SJerome Forissier
258733a15f2SJerome Forissier    def reset(self):
259733a15f2SJerome Forissier        self._call_stack_found = False
260733a15f2SJerome Forissier        self._load_addr = '0'
261733a15f2SJerome Forissier        if self._addr2line:
262733a15f2SJerome Forissier            self._addr2line.terminate()
263733a15f2SJerome Forissier            self._addr2line = None
264d720431cSJerome Forissier        self._arch = None
265142c5cccSJerome Forissier        self._saved_abort_line = ''
26630999126SJerome Forissier        self._sections = []
26727b83ad2SJerome Forissier        self._bin = "tee.elf"
268733a15f2SJerome Forissier
269733a15f2SJerome Forissier    def write(self, line):
270733a15f2SJerome Forissier            if self._call_stack_found:
271733a15f2SJerome Forissier                match = re.search(STACK_ADDR_RE, line)
272733a15f2SJerome Forissier                if match:
273733a15f2SJerome Forissier                    addr = match.group('addr')
274733a15f2SJerome Forissier                    pre = match.start('addr')
275733a15f2SJerome Forissier                    post = match.end('addr')
276733a15f2SJerome Forissier                    self._out.write(line[:pre])
277733a15f2SJerome Forissier                    self._out.write(addr)
278733a15f2SJerome Forissier                    res = self.resolve(addr)
279733a15f2SJerome Forissier                    if self._strip_path:
280733a15f2SJerome Forissier                        res = re.sub(re.escape(self._strip_path) + '/*', '',
281733a15f2SJerome Forissier                              res)
282733a15f2SJerome Forissier                    self._out.write(' ' + res)
283733a15f2SJerome Forissier                    self._out.write(line[post:])
284733a15f2SJerome Forissier                    return
285733a15f2SJerome Forissier                else:
286733a15f2SJerome Forissier                    self.reset()
28730999126SJerome Forissier            match = re.search(REGION_RE, line)
28830999126SJerome Forissier            if match:
28930999126SJerome Forissier                addr = match.group('addr')
29030999126SJerome Forissier                size = match.group('size')
29130999126SJerome Forissier                self._out.write(line.strip() +
29230999126SJerome Forissier                                self.sections_in_region(addr, size) + '\n');
29330999126SJerome Forissier                return
294733a15f2SJerome Forissier            match = re.search(CALL_STACK_RE, line)
295733a15f2SJerome Forissier            if match:
296733a15f2SJerome Forissier                self._call_stack_found = True
297142c5cccSJerome Forissier                # Here is a good place to resolve the abort address because we
298142c5cccSJerome Forissier                # have all the information we need
299142c5cccSJerome Forissier                if self._saved_abort_line:
300142c5cccSJerome Forissier                    self._out.write(self.process_abort(self._saved_abort_line))
301733a15f2SJerome Forissier            match = re.search(TA_UUID_RE, line)
302733a15f2SJerome Forissier            if match:
303733a15f2SJerome Forissier                self._bin = match.group('uuid')
304733a15f2SJerome Forissier            match = re.search(TA_INFO_RE, line)
305733a15f2SJerome Forissier            if match:
306733a15f2SJerome Forissier                self._load_addr = match.group('load_addr')
307142c5cccSJerome Forissier            match = re.search(ABORT_ADDR_RE, line)
308142c5cccSJerome Forissier            if match:
30927b83ad2SJerome Forissier                self.reset()
310142c5cccSJerome Forissier                # At this point the arch and TA load address are unknown.
311142c5cccSJerome Forissier                # Save the line so We can translate the abort address later.
312142c5cccSJerome Forissier                self._saved_abort_line = line
313733a15f2SJerome Forissier            self._out.write(line)
314733a15f2SJerome Forissier
315733a15f2SJerome Forissier    def flush(self):
316733a15f2SJerome Forissier        self._out.flush()
317733a15f2SJerome Forissier
318733a15f2SJerome Forissierdef main():
319733a15f2SJerome Forissier    args = get_args()
320733a15f2SJerome Forissier    if args.dir:
321733a15f2SJerome Forissier        # Flatten list in case -d is used several times *and* with multiple
322733a15f2SJerome Forissier        # arguments
323733a15f2SJerome Forissier        args.dirs = [item for sublist in args.dir for item in sublist]
324733a15f2SJerome Forissier    else:
325733a15f2SJerome Forissier        args.dirs = []
326733a15f2SJerome Forissier    symbolizer = Symbolizer(sys.stdout, args.dirs, args.strip_path)
327733a15f2SJerome Forissier
328733a15f2SJerome Forissier    for line in sys.stdin:
329733a15f2SJerome Forissier        symbolizer.write(line)
330733a15f2SJerome Forissier    symbolizer.flush()
331733a15f2SJerome Forissier
332733a15f2SJerome Forissierif __name__ == "__main__":
333733a15f2SJerome Forissier    main()
334