1#!/usr/bin/env python3
2#
3# Copyright (c) 2024 Meta Platforms
4#
5# SPDX-License-Identifier: Apache-2.0
6
7import argparse
8import sys
9import os
10import re
11
12from elftools.elf.elffile import ELFFile
13from elftools.elf.descriptions import (
14    describe_symbol_type,
15)
16
17
18class gen_symtab_log:
19
20    def __init__(self, debug=False):
21        self.__debug = debug
22
23    def debug(self, text):
24        """Print debug message if debugging is enabled.
25
26        Note - this function requires config global variable to be initialized.
27        """
28        if self.__debug:
29            sys.stdout.write(os.path.basename(
30                sys.argv[0]) + ": " + text + "\n")
31
32    @staticmethod
33    def error(text):
34        sys.exit(os.path.basename(sys.argv[0]) + ": error: " + text + "\n")
35
36    def set_debug(self, state):
37        self.__debug = state
38
39
40log = gen_symtab_log()
41
42
43def parse_args():
44    parser = argparse.ArgumentParser(description=__doc__,
45                                     formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
46
47    parser.add_argument("-k", "--kernel", required=True,
48                        help="Zephyr kernel image")
49    parser.add_argument("-o", "--output", required=True,
50                        help="Output source file")
51    parser.add_argument("-d", "--debug", action="store_true",
52                        help="Print additional debugging information")
53
54    return parser.parse_args()
55
56
57class symtab_entry:
58    def __init__(self, addr, size, offset, name):
59        self.addr = addr
60        self.size = size
61        self.offset = offset
62        self.name = name
63
64    def __eq__(self, other):
65        return self.addr == other.addr
66
67
68first_addr = 0
69symtab_list = []
70
71
72def sanitize_func_name(name):
73    pattern = r'(^[a-zA-Z_][a-zA-Z0-9_]*)'
74    match = re.match(pattern, name)
75    if match:
76        return match.group(0)
77    else:
78        log.error(f"Failed to sanitize function name: {name}")
79
80    return name
81
82
83def main():
84    args = parse_args()
85    log.set_debug(args.debug)
86
87    with open(args.kernel, "rb") as rf:
88        elf = ELFFile(rf)
89
90        # Find the symbol table.
91        symtab = elf.get_section_by_name('.symtab')
92
93        i = 1
94        for nsym, symbol in enumerate(symtab.iter_symbols()):  # pylint: disable=unused-variable
95            symbol_type = describe_symbol_type(symbol['st_info']['type'])
96            symbol_addr = symbol['st_value']
97            symbol_size = symbol['st_size']
98
99            if symbol_type == 'FUNC' and symbol_addr != 0:
100                symbol_name = sanitize_func_name(symbol.name)
101                dummy_offset = 0  # offsets will be calculated later after we know the first address
102                entry = symtab_entry(
103                    symbol_addr, symbol_size, dummy_offset, symbol_name)
104                # Prevent entries with duplicated addresses
105                if entry not in symtab_list:
106                    symtab_list.append(entry)
107
108        # Sort the address in ascending order
109        symtab_list.sort(key=lambda x: x.addr, reverse=False)
110
111        # Get the address of the first symbol
112        first_addr = symtab_list[0].addr
113
114        for i, entry in enumerate(symtab_list):
115            # Offset is calculated here
116            entry.offset = entry.addr - first_addr
117
118            # Debug print
119            log.debug('%6d: %s %s %.25s' % (
120                i,
121                hex(entry.addr),
122                hex(entry.size),
123                entry.name))
124
125    with open(args.output, 'w') as wf:
126        print("/* AUTO-GENERATED by gen_symtab.py, do not edit! */", file=wf)
127        print("", file=wf)
128        print("#include <zephyr/linker/sections.h>", file=wf)
129        print("#include <zephyr/debug/symtab.h>", file=wf)
130        print("", file=wf)
131        print(
132            f"const struct z_symtab_entry __symtab_entry z_symtab_entries[{len(symtab_list) + 1}] = {{", file=wf)
133        for i, entry in enumerate(symtab_list):
134            print(
135                f"\t/* ADDR: {hex(entry.addr)} SIZE: {hex(entry.size)} */", file=wf)
136            print(
137                f"\t[{i}] = {{.offset = {hex(entry.offset)}, .name = \"{entry.name}\"}},", file=wf)
138
139        # Append a dummy entry at the end to facilitate the binary search
140        if symtab_list[-1].size == 0:
141            dummy_offset = f"{hex(symtab_list[-1].offset)} + sizeof(uintptr_t)"
142        else:
143            dummy_offset = f"{hex(symtab_list[-1].offset + symtab_list[-1].size)}"
144        print("\t/* dummy entry */", file=wf)
145        print(
146            f"\t[{len(symtab_list)}] = {{.offset = {dummy_offset}, .name = \"?\"}},", file=wf)
147        print(f"}};\n", file=wf)
148
149        print(f"const struct symtab_info __symtab_info z_symtab = {{", file=wf)
150        print(f"\t.first_addr = {hex(first_addr)},", file=wf)
151        print(f"\t.length = {len(symtab_list)},", file=wf)
152        print(f"\t.entries = z_symtab_entries,", file=wf)
153        print(f"}};\n", file=wf)
154
155
156if __name__ == "__main__":
157    main()
158