debug: implement symtab generation

Use pyelftools to extract the symbol table from the
link stage executable. Then, filter out the function names
and sort them based on their offsets before writing into the
`symtab.c`, this is similar to how the `isr_tables` works.

To access the structure, simply include the new header:
```c
#include <zephyr/debug/symtab.h>
```

Signed-off-by: Yong Cong Sin <ycsin@meta.com>
This commit is contained in:
Yong Cong Sin 2024-05-18 16:45:44 +08:00 committed by Anas Nashif
commit e1ce0aefff
9 changed files with 276 additions and 0 deletions

View file

@ -1297,6 +1297,20 @@ if(CONFIG_GEN_ISR_TABLES)
set_property(GLOBAL APPEND PROPERTY GENERATED_KERNEL_SOURCE_FILES isr_tables.c) set_property(GLOBAL APPEND PROPERTY GENERATED_KERNEL_SOURCE_FILES isr_tables.c)
endif() endif()
if(CONFIG_SYMTAB)
add_custom_command(
OUTPUT symtab.c
COMMAND
${PYTHON_EXECUTABLE}
${ZEPHYR_BASE}/scripts/build/gen_symtab.py
-k $<TARGET_FILE:${ZEPHYR_LINK_STAGE_EXECUTABLE}>
-o symtab.c
DEPENDS ${ZEPHYR_LINK_STAGE_EXECUTABLE}
COMMAND_EXPAND_LISTS
)
set_property(GLOBAL APPEND PROPERTY GENERATED_KERNEL_SOURCE_FILES symtab.c)
endif()
if(CONFIG_USERSPACE) if(CONFIG_USERSPACE)
set(KOBJECT_HASH_LIST kobject_hash.gperf) set(KOBJECT_HASH_LIST kobject_hash.gperf)
set(KOBJECT_HASH_OUTPUT_SRC_PRE kobject_hash_preprocessed.c) set(KOBJECT_HASH_OUTPUT_SRC_PRE kobject_hash_preprocessed.c)

View file

@ -323,6 +323,13 @@ Devicetree
Libraries / Subsystems Libraries / Subsystems
********************** **********************
* Debug
* symtab
* By enabling :kconfig:option:`CONFIG_SYMTAB`, the symbol table will be
generated with Zephyr link stage executable on supported architectures.
* Management * Management
* hawkBit * hawkBit

View file

@ -0,0 +1,52 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_DEBUG_SYMTAB_H_
#define ZEPHYR_INCLUDE_DEBUG_SYMTAB_H_
#include <stdint.h>
/**
* @cond INTERNAL_HIDDEN
*/
struct z_symtab_entry {
const uint32_t offset;
const char *const name;
};
/**
* INTERNAL_HIDDEN @endcond
*/
struct symtab_info {
/* Absolute address of the first symbol */
const uintptr_t start_addr;
/* Number of symbol entries */
const uint32_t length;
/* Symbol entries */
const struct z_symtab_entry *const entries;
};
/**
* @brief Get the pointer to the symbol table.
*
* @return Pointer to the symbol table.
*/
const struct symtab_info *const symtab_get(void);
/**
* @brief Find the symbol name with a binary search
*
* @param[in] addr Address of the symbol to find
* @param[out] offset Offset of the symbol from the nearest symbol. If the symbol can't be found,
* this will be 0.
*
* @return Name of the nearest symbol if found, otherwise "?" is returned.
*/
const char *const symtab_find_symbol_name(uintptr_t addr, uint32_t *offset);
#endif /* ZEPHYR_INCLUDE_DEBUG_SYMTAB_H_ */

119
scripts/build/gen_symtab.py Normal file
View file

@ -0,0 +1,119 @@
#!/usr/bin/env python3
#
# Copyright (c) 2024 Meta Platforms
#
# SPDX-License-Identifier: Apache-2.0
import argparse
import sys
import os
from elftools.elf.elffile import ELFFile
from elftools.elf.descriptions import (
describe_symbol_type,
)
class gen_symtab_log:
def __init__(self, debug=False):
self.__debug = debug
def debug(self, text):
"""Print debug message if debugging is enabled.
Note - this function requires config global variable to be initialized.
"""
if self.__debug:
sys.stdout.write(os.path.basename(
sys.argv[0]) + ": " + text + "\n")
@staticmethod
def error(text):
sys.exit(os.path.basename(sys.argv[0]) + ": error: " + text + "\n")
def set_debug(self, state):
self.__debug = state
log = gen_symtab_log()
def parse_args():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter, allow_abbrev=False)
parser.add_argument("-k", "--kernel", required=True,
help="Zephyr kernel image")
parser.add_argument("-o", "--output", required=True,
help="Output source file")
parser.add_argument("-d", "--debug", action="store_true",
help="Print additional debugging information")
return parser.parse_args()
class symtab_entry:
def __init__(self, addr, offset, name):
self.addr = addr
self.offset = offset
self.name = name
start_addr = 0
symtab_list = []
def main():
args = parse_args()
log.set_debug(args.debug)
with open(args.kernel, "rb") as rf:
elf = ELFFile(rf)
# Find the symbol table.
symtab = elf.get_section_by_name('.symtab')
i = 1
for nsym, symbol in enumerate(symtab.iter_symbols()): # pylint: disable=unused-variable
symbol_type = describe_symbol_type(symbol['st_info']['type'])
symbol_addr = symbol['st_value']
if symbol_type == 'FUNC' and symbol_addr != 0:
symtab_list.append(symtab_entry(
symbol_addr, symbol_addr, symbol.name))
log.debug('%6d: %s %.25s' % (
i,
hex(symbol_addr),
symbol.name))
i = i + 1
symtab_list.sort(key=lambda x: x.addr, reverse=False)
# Get the address of the first symbol
start_addr = symtab_list[0].addr
# Use that to calculate the offset of other symbols relative to the first one
for i, entry in enumerate(symtab_list):
entry.offset = entry.addr - start_addr
with open(args.output, 'w') as wf:
print("/* AUTO-GENERATED by gen_symtab.py, do not edit! */", file=wf)
print("", file=wf)
print("#include <zephyr/debug/symtab.h>", file=wf)
print("", file=wf)
print(
f"const struct z_symtab_entry z_symtab_entries[{len(symtab_list)}] = {{", file=wf)
for i, entry in enumerate(symtab_list):
print(
f"\t[{i}] = {{.offset = {hex(entry.offset)}, .name = \"{entry.name}\"}}, /* {hex(entry.addr)} */", file=wf)
print(f"}};\n", file=wf)
print(f"const struct symtab_info z_symtab = {{", file=wf)
print(f"\t.start_addr = {hex(start_addr)},", file=wf)
print(f"\t.length = {len(symtab_list)},", file=wf)
print(f"\t.entries = z_symtab_entries,", file=wf)
print(f"}};\n", file=wf)
if __name__ == "__main__":
main()

View file

@ -34,3 +34,8 @@ zephyr_sources_ifdef(
CONFIG_MIPI_STP_DECODER CONFIG_MIPI_STP_DECODER
mipi_stp_decoder.c mipi_stp_decoder.c
) )
add_subdirectory_ifdef(
CONFIG_SYMTAB
symtab
)

View file

@ -368,6 +368,16 @@ config DEBUG_INFO
used by debuggers in debugging the system, or enable additional used by debuggers in debugging the system, or enable additional
debugging information to be reported at runtime. debugging information to be reported at runtime.
config SYMTAB
bool "Generate symbol table"
help
Generate the symbol table with the offset and name of every
function.
The symbol table can be accessed by including the
<zephyr/debug/symtab.h> header.
Choose N if you have no idea what is this.
config EXCEPTION_STACK_TRACE config EXCEPTION_STACK_TRACE
bool "Attempt to print stack traces upon exceptions" bool "Attempt to print stack traces upon exceptions"
default y default y

View file

@ -0,0 +1,9 @@
# SPDX-License-Identifier: Apache-2.0
add_library(symtab
symtab.c
symtab_stub.c
)
target_link_libraries(symtab zephyr_interface)
zephyr_library_link_libraries(symtab)

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <zephyr/debug/symtab.h>
const struct symtab_info *const symtab_get(void)
{
extern const struct symtab_info z_symtab;
return &z_symtab;
}
const char *const symtab_find_symbol_name(uintptr_t addr, uint32_t *offset)
{
const struct symtab_info *const symtab = symtab_get();
const uint32_t symbol_offset = addr - symtab->start_addr;
uint32_t left = 0, right = symtab->length - 1;
uint32_t ret_offset = 0;
const char *ret_name = "?";
while (left <= right) {
uint32_t mid = left + (right - left) / 2;
if ((symbol_offset >= symtab->entries[mid].offset) &&
(symbol_offset < symtab->entries[mid + 1].offset)) {
ret_offset = symbol_offset - symtab->entries[mid].offset;
ret_name = symtab->entries[mid].name;
break;
} else if (symbol_offset < symtab->entries[mid].offset) {
right = mid - 1;
} else {
left = mid + 1;
}
}
if (offset != NULL) {
*offset = ret_offset;
}
return ret_name;
}

View file

@ -0,0 +1,14 @@
/*
* Copyright (c) 2024 Meta Platforms
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/debug/symtab.h>
/**
* These are placeholder variables. They will be replaced by the real ones
* generated by `gen_symtab.py`.
*/
const struct z_symtab_entry *z_symtab_entries;
const struct symtab_info z_symtab;