master
py 198 lines 6.47 KB
Raw
1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3 #
4 # Create symbols, debug and mapping files for uftrace.
5 #
6 # Copyright 2025 Linaro Ltd
7 # Author: Pierrick Bouvier <pierrick.bouvier@linaro.org>
8 #
9 # SPDX-License-Identifier: GPL-2.0-or-later
10
11 import argparse
12 import os
13 import subprocess
14
15 class Symbol:
16 def __init__(self, name, addr, size):
17 self.name = name
18 # clamp addr to 48 bits, like uftrace entries
19 self.addr = addr & 0xffffffffffff
20 self.full_addr = addr
21 self.size = size
22
23 def set_loc(self, file, line):
24 self.file = file
25 self.line = line
26
27 def get_symbols(elf_file):
28 symbols=[]
29 try:
30 out = subprocess.check_output(['nm', '--print-size', elf_file],
31 stderr=subprocess.STDOUT,
32 text=True)
33 except subprocess.CalledProcessError as e:
34 print(e.output)
35 raise
36 out = out.strip().split('\n')
37 for line in out:
38 info = line.split(' ')
39 if len(info) != 4:
40 # missing size/address information
41 continue
42 addr, size, type, name = info
43 # add only symbols from .text section
44 if type.lower() != 't':
45 continue
46 addr = int(addr, 16)
47 size = int(size, 16)
48 if size == 0:
49 continue
50 symbols.append(Symbol(name, addr, size))
51 symbols.sort(key = lambda x: x.addr)
52 return symbols
53
54 def find_symbols_locations(elf_file, symbols):
55 addresses = '\n'.join([hex(x.full_addr) for x in symbols])
56 try:
57 out = subprocess.check_output(['addr2line', '--exe', elf_file],
58 stderr=subprocess.STDOUT,
59 input=addresses, text=True)
60 except subprocess.CalledProcessError as e:
61 print(e.output)
62 raise
63 out = out.strip().split('\n')
64 # filter out some addr2line error messages
65 skip_err='addr2line: DWARF error: mangled line number section (bad file number)'
66 out = [src for src in out if src != skip_err]
67 assert len(out) == len(symbols)
68 for i in range(len(symbols)):
69 s = symbols[i]
70 file, line = out[i].split(':')
71 # addr2line may return 'line (discriminator [0-9]+)' sometimes,
72 # remove this to keep only line number.
73 if line == '?':
74 line = 0
75 else:
76 line = int(line.split(' ')[0])
77 s.set_loc(file, line)
78
79 class BinaryFile:
80 def __init__(self, path, map_offset):
81 self.fullpath = os.path.realpath(path)
82 self.map_offset = map_offset
83 self.symbols = get_symbols(self.fullpath)
84 find_symbols_locations(self.fullpath, self.symbols)
85
86 def path(self):
87 return self.fullpath
88
89 def addr_start(self):
90 return self.map_offset
91
92 def addr_end(self):
93 last_sym = self.symbols[-1]
94 return last_sym.addr + last_sym.size + self.map_offset
95
96 def generate_symbol_file(self, prefix_symbols):
97 binary_name = os.path.basename(self.fullpath)
98 sym_file_path = os.path.join('uftrace.data', f'{binary_name}.sym')
99 print(f'{sym_file_path} ({len(self.symbols)} symbols)')
100 with open(sym_file_path, 'w') as sym_file:
101 # print hexadecimal addresses on 48 bits
102 addrx = "0>12x"
103 for s in self.symbols:
104 addr = s.addr
105 addr = f'{addr:{addrx}}'
106 size = f'{s.size:{addrx}}'
107 if prefix_symbols:
108 name = f'{binary_name}:{s.name}'
109 else:
110 name = s.name
111 print(addr, size, 'T', name, file=sym_file)
112
113 def generate_debug_file(self):
114 binary_name = os.path.basename(self.fullpath)
115 dbg_file_path = os.path.join('uftrace.data', f'{binary_name}.dbg')
116 with open(dbg_file_path, 'w') as dbg_file:
117 for s in self.symbols:
118 print(f'F: {hex(s.addr)} {s.name}', file=dbg_file)
119 print(f'L: {s.line} {s.file}', file=dbg_file)
120
121 def parse_parameter(p):
122 s = p.split(":")
123 path = s[0]
124 if len(s) == 1:
125 return path, 0
126 if len(s) > 2:
127 raise ValueError('only one offset can be set')
128 offset = s[1]
129 if not offset.startswith('0x'):
130 err = f'offset "{offset}" is not an hexadecimal constant. '
131 err += 'It should start with "0x".'
132 raise ValueError(err)
133 offset = int(offset, 16)
134 return path, offset
135
136 def is_from_user_mode(map_file_path):
137 if os.path.exists(map_file_path):
138 with open(map_file_path, 'r') as map_file:
139 if not map_file.readline().startswith('# map stack on'):
140 return True
141 return False
142
143 def generate_map(binaries):
144 map_file_path = os.path.join('uftrace.data', 'sid-0.map')
145
146 if is_from_user_mode(map_file_path):
147 print(f'do not overwrite {map_file_path} generated from qemu-user')
148 return
149
150 mappings = []
151
152 # print hexadecimal addresses on 48 bits
153 addrx = "0>12x"
154
155 mappings += ['# map stack on highest address possible, to prevent uftrace']
156 mappings += ['# from considering any kernel address']
157 mappings += ['ffffffffffff-ffffffffffff rw-p 00000000 00:00 0 [stack]']
158
159 for b in binaries:
160 m = f'{b.addr_start():{addrx}}-{b.addr_end():{addrx}}'
161 m += f' r--p 00000000 00:00 0 {b.path()}'
162 mappings.append(m)
163
164 with open(map_file_path, 'w') as map_file:
165 print('\n'.join(mappings), file=map_file)
166 print(f'{map_file_path}')
167 print('\n'.join(mappings))
168
169 def main():
170 parser = argparse.ArgumentParser(description=
171 'generate symbol files for uftrace. '
172 'Require binutils (nm and addr2line).')
173 parser.add_argument('elf_file', nargs='+',
174 help='path to an ELF file. '
175 'Use /path/to/file:0xdeadbeef to add a mapping offset.')
176 parser.add_argument('--prefix-symbols',
177 help='prepend binary name to symbols',
178 action=argparse.BooleanOptionalAction)
179 args = parser.parse_args()
180
181 if not os.path.exists('uftrace.data'):
182 os.mkdir('uftrace.data')
183
184 binaries = []
185 for file in args.elf_file:
186 path, offset = parse_parameter(file)
187 b = BinaryFile(path, offset)
188 binaries.append(b)
189 binaries.sort(key = lambda b: b.addr_end());
190
191 for b in binaries:
192 b.generate_symbol_file(args.prefix_symbols)
193 b.generate_debug_file()
194
195 generate_map(binaries)
196
197 if __name__ == '__main__':
198 main()