aboutsummaryrefslogtreecommitdiff
path: root/contrib/plugins/uftrace_symbols.py
blob: b49e03203c8fc04ddc90db13cc47cad5dfa52f8a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Create symbols and mapping files for uftrace.
#
# Copyright 2025 Linaro Ltd
# Author: Pierrick Bouvier <pierrick.bouvier@linaro.org>
#
# SPDX-License-Identifier: GPL-2.0-or-later

import argparse
import elftools # pip install pyelftools
import os

from elftools.elf.elffile import ELFFile
from elftools.elf.sections import SymbolTableSection

def elf_func_symbols(elf):
    symbol_tables = [(idx, s) for idx, s in enumerate(elf.iter_sections())
                  if isinstance(s, SymbolTableSection)]
    symbols = []
    for _, section in symbol_tables:
        for _, symbol in enumerate(section.iter_symbols()):
            if symbol_size(symbol) == 0:
                continue
            type = symbol['st_info']['type']
            if type == 'STT_FUNC' or type == 'STT_NOTYPE':
                symbols.append(symbol)
    symbols.sort(key = lambda x: symbol_addr(x))
    return symbols

def symbol_size(symbol):
    return symbol['st_size']

def symbol_addr(symbol):
    addr = symbol['st_value']
    # clamp addr to 48 bits, like uftrace entries
    return addr & 0xffffffffffff

def symbol_name(symbol):
    return symbol.name

class BinaryFile:
    def __init__(self, path, map_offset):
        self.fullpath = os.path.realpath(path)
        self.map_offset = map_offset
        with open(path, 'rb') as f:
            self.elf = ELFFile(f)
            self.symbols = elf_func_symbols(self.elf)

    def path(self):
        return self.fullpath

    def addr_start(self):
        return self.map_offset

    def addr_end(self):
        last_sym = self.symbols[-1]
        return symbol_addr(last_sym) + symbol_size(last_sym) + self.map_offset

    def generate_symbol_file(self, prefix_symbols):
        binary_name = os.path.basename(self.fullpath)
        sym_file_path = f'./uftrace.data/{binary_name}.sym'
        print(f'{sym_file_path} ({len(self.symbols)} symbols)')
        with open(sym_file_path, 'w') as sym_file:
            # print hexadecimal addresses on 48 bits
            addrx = "0>12x"
            for s in self.symbols:
                addr = symbol_addr(s)
                addr = f'{addr:{addrx}}'
                size = f'{symbol_size(s):{addrx}}'
                name = symbol_name(s)
                if prefix_symbols:
                    name = f'{binary_name}:{name}'
                print(addr, size, 'T', name, file=sym_file)

def parse_parameter(p):
    s = p.split(":")
    path = s[0]
    if len(s) == 1:
        return path, 0
    if len(s) > 2:
        raise ValueError('only one offset can be set')
    offset = s[1]
    if not offset.startswith('0x'):
        err = f'offset "{offset}" is not an hexadecimal constant. '
        err += 'It should starts with "0x".'
        raise ValueError(err)
    offset = int(offset, 16)
    return path, offset

def is_from_user_mode(map_file_path):
    if os.path.exists(map_file_path):
        with open(map_file_path, 'r') as map_file:
            if not map_file.readline().startswith('# map stack on'):
                return True
    return False

def generate_map(binaries):
    map_file_path = './uftrace.data/sid-0.map'

    if is_from_user_mode(map_file_path):
        print(f'do not overwrite {map_file_path} generated from qemu-user')
        return

    mappings = []

    # print hexadecimal addresses on 48 bits
    addrx = "0>12x"

    mappings += ['# map stack on highest address possible, to prevent uftrace']
    mappings += ['# from considering any kernel address']
    mappings += ['ffffffffffff-ffffffffffff rw-p 00000000 00:00 0 [stack]']

    for b in binaries:
        m = f'{b.addr_start():{addrx}}-{b.addr_end():{addrx}}'
        m += f' r--p 00000000 00:00 0 {b.path()}'
        mappings.append(m)

    with open(map_file_path, 'w') as map_file:
        print('\n'.join(mappings), file=map_file)
    print(f'{map_file_path}')
    print('\n'.join(mappings))

def main():
    parser = argparse.ArgumentParser(description=
                                     'generate symbol files for uftrace')
    parser.add_argument('elf_file', nargs='+',
                        help='path to an ELF file. '
                        'Use /path/to/file:0xdeadbeef to add a mapping offset.')
    parser.add_argument('--prefix-symbols',
                        help='prepend binary name to symbols',
                        action=argparse.BooleanOptionalAction)
    args = parser.parse_args()

    if not os.path.exists('./uftrace.data'):
        os.mkdir('./uftrace.data')

    binaries = []
    for file in args.elf_file:
        path, offset = parse_parameter(file)
        b = BinaryFile(path, offset)
        binaries.append(b)
    binaries.sort(key = lambda b: b.addr_end());

    for b in binaries:
        b.generate_symbol_file(args.prefix_symbols)

    generate_map(binaries)

if __name__ == '__main__':
    main()