diff --git a/include/rtthread.h b/include/rtthread.h index 177ea877831e..0b552095eb3d 100644 --- a/include/rtthread.h +++ b/include/rtthread.h @@ -800,6 +800,18 @@ rt_err_t rt_backtrace_formatted_print(rt_ubase_t *buffer, long buflen); rt_err_t rt_backtrace_to_buffer(rt_thread_t thread, struct rt_hw_backtrace_frame *frame, long skip, rt_ubase_t *buffer, long buflen); +#ifdef RT_USING_KSYMS +struct rt_ksym_info +{ + const char *name; + rt_uintptr_t start; + rt_size_t size; + rt_size_t offset; +}; + +rt_err_t rt_ksym_lookup(rt_uintptr_t addr, struct rt_ksym_info *info); +#endif /* RT_USING_KSYMS */ + #if defined(RT_USING_DEVICE) && defined(RT_USING_CONSOLE) rt_device_t rt_console_set_device(const char *name); rt_device_t rt_console_get_device(void); diff --git a/libcpu/arm/cortex-a/backtrace.c b/libcpu/arm/cortex-a/backtrace.c index c9960f25aff1..f89c6cf9da31 100644 --- a/libcpu/arm/cortex-a/backtrace.c +++ b/libcpu/arm/cortex-a/backtrace.c @@ -361,7 +361,7 @@ static int unwind_exec_insn(struct unwind_ctrl_block *ctrl) return ret; } -#ifdef RT_BACKTRACE_FUNCTION_NAME +#if defined(RT_BACKTRACE_FUNCTION_NAME) && !defined(RT_USING_KSYMS) static char *unwind_get_function_name(void *address) { uint32_t flag_word = *(uint32_t *)((char*)address - 4); @@ -400,7 +400,7 @@ int unwind_frame(struct stackframe *frame, const struct unwind_idx **origin_idx, return -URC_FAILURE; } -#ifdef RT_BACKTRACE_FUNCTION_NAME +#if defined(RT_BACKTRACE_FUNCTION_NAME) && !defined(RT_USING_KSYMS) { char *fun_name; fun_name = unwind_get_function_name((void *)prel31_to_addr(&idx->addr_offset)); @@ -488,6 +488,27 @@ void unwind_backtrace(struct pt_regs *regs, const struct unwind_idx exidx_start[ arm_get_current_stackframe(regs, &frame); +#ifdef RT_USING_KSYMS + { + rt_ubase_t buffer[RT_BACKTRACE_LEVEL_MAX_NR]; + long count = 0; + + if (count < RT_BACKTRACE_LEVEL_MAX_NR) + buffer[count++] = frame.pc; + + while (count < RT_BACKTRACE_LEVEL_MAX_NR) + { + int urc = unwind_frame(&frame, &origin_idx, + exidx_start, exidx_end); + if (urc < 0) + break; + buffer[count++] = frame.pc; + LOG_D("from: pc = %08x, frame = %08x", frame.pc, frame.sp - 4); + } + + rt_backtrace_formatted_print(buffer, count); + } +#else #ifndef RT_BACKTRACE_FUNCTION_NAME rt_kprintf("please use: addr2line -e rtthread.elf -a -f %08x\n", frame.pc); #endif @@ -507,6 +528,7 @@ void unwind_backtrace(struct pt_regs *regs, const struct unwind_idx exidx_start[ LOG_D("from: pc = %08x, frame = %08x", frame.pc, frame.sp - 4); } rt_kprintf("\n"); +#endif } extern const struct unwind_idx __exidx_start[]; diff --git a/src/Kconfig b/src/Kconfig index dc1d4564de74..df7716b81408 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -477,4 +477,17 @@ config RT_BACKTRACE_LEVEL_MAX_NR int "Max number of backtrace level" default 32 +config RT_USING_KSYMS + bool "Enable kernel symbol table" + default n + help + Embed a compact GCC/ELF kernel function symbol table and use it to + resolve addresses printed by the kernel backtrace API. + + This option is supported by the native SCons GCC/ELF build only. + Enabling it with another toolchain is a build error. + + The symbol table consumes read-only memory. It is generated during + the GCC build and includes local, global, and weak functions. + endmenu diff --git a/src/kservice.c b/src/kservice.c index db321552616f..fa232bfa9e33 100644 --- a/src/kservice.c +++ b/src/kservice.c @@ -444,6 +444,23 @@ rt_weak rt_err_t rt_backtrace(void) return rt_backtrace_frame(thread, &frame); } +#ifdef RT_USING_KSYMS +static void _rt_backtrace_print_pc(rt_ubase_t pc) +{ + struct rt_ksym_info info; + + rt_kprintf(" 0x%lx", (unsigned long)pc); + if (rt_ksym_lookup(pc, &info) == RT_EOK) + { + rt_kprintf(" <%s+0x%lx", info.name, + (unsigned long)info.offset); + if (info.size != 0) + rt_kprintf("/0x%lx", (unsigned long)info.size); + rt_kprintf(">"); + } +} +#endif /* RT_USING_KSYMS */ + /** * @brief Print backtrace from frame to system console device * @@ -455,11 +472,17 @@ rt_weak rt_err_t rt_backtrace_frame(rt_thread_t thread, struct rt_hw_backtrace_f { long nesting = 0; +#ifndef RT_USING_KSYMS rt_kprintf("please use: addr2line -e rtthread.elf -a -f\n"); +#endif while (nesting < RT_BACKTRACE_LEVEL_MAX_NR) { +#ifdef RT_USING_KSYMS + _rt_backtrace_print_pc((rt_ubase_t)frame->pc); +#else rt_kprintf(" 0x%lx", (rt_ubase_t)frame->pc); +#endif if (rt_hw_backtrace_frame_unwind(thread, frame)) { break; @@ -479,11 +502,17 @@ rt_weak rt_err_t rt_backtrace_frame(rt_thread_t thread, struct rt_hw_backtrace_f */ rt_weak rt_err_t rt_backtrace_formatted_print(rt_ubase_t *buffer, long buflen) { +#ifndef RT_USING_KSYMS rt_kprintf("please use: addr2line -e rtthread.elf -a -f\n"); +#endif for (rt_size_t i = 0; i < buflen && buffer[i] != 0; i++) { +#ifdef RT_USING_KSYMS + _rt_backtrace_print_pc((rt_ubase_t)buffer[i]); +#else rt_kprintf(" 0x%lx", (rt_ubase_t)buffer[i]); +#endif } rt_kprintf("\n"); diff --git a/src/ksym.c b/src/ksym.c new file mode 100644 index 000000000000..07825cb152af --- /dev/null +++ b/src/ksym.c @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#ifdef RT_USING_KSYMS + +/* These objects are replaced by the generated table when using GCC builds. */ +rt_weak volatile const rt_uintptr_t rt_ksym_first_addr = 0; +rt_weak volatile const rt_uint32_t rt_ksym_count = 0; +rt_weak volatile const rt_uint32_t rt_ksym_entries[1][3] = {{0, 0, 0}}; +rt_weak const char rt_ksym_names[1] = ""; + +static rt_uintptr_t _rt_ksym_normalize_addr(rt_uintptr_t addr) +{ +#if defined(ARCH_ARM) && !defined(ARCH_CPU_64BIT) + return addr & ~((rt_uintptr_t)1); +#else + return addr; +#endif +} + +static rt_uintptr_t _rt_ksym_entry_addr(rt_uint32_t index) +{ + return rt_ksym_first_addr + rt_ksym_entries[index][0]; +} + +rt_err_t rt_ksym_lookup(rt_uintptr_t addr, struct rt_ksym_info *info) +{ + rt_uint32_t low; + rt_uint32_t high; + rt_uintptr_t start; + rt_uint32_t size; + + if (!info) + return -RT_EINVAL; + if (rt_ksym_count == 0) + return -RT_ENOSYS; + + addr = _rt_ksym_normalize_addr(addr); + + if (addr < rt_ksym_first_addr || + addr - rt_ksym_first_addr > 0xffffffffu) + return -RT_ENOENT; + + low = 0; + high = rt_ksym_count; + while (high - low > 1) + { + rt_uint32_t middle = low + (high - low) / 2; + + if (_rt_ksym_entry_addr(middle) <= addr) + low = middle; + else + high = middle; + } + + if (_rt_ksym_entry_addr(low) > addr) + return -RT_ENOENT; + + /* Keep aliases together even if a hand-built table contains them. */ + while (low > 0 && + _rt_ksym_entry_addr(low - 1) == _rt_ksym_entry_addr(low)) + { + low--; + } + + start = _rt_ksym_entry_addr(low); + size = rt_ksym_entries[low][2]; + + if (size == 0 && addr != start) + return -RT_ENOENT; + if (size != 0 && addr - start >= size) + return -RT_ENOENT; + + info->name = &rt_ksym_names[rt_ksym_entries[low][1]]; + info->start = start; + info->offset = addr - start; + info->size = size; + + return RT_EOK; +} + +#endif /* RT_USING_KSYMS */ diff --git a/src/utest/Kconfig b/src/utest/Kconfig index 7f478c591ffa..9b37981e9cb9 100644 --- a/src/utest/Kconfig +++ b/src/utest/Kconfig @@ -75,6 +75,11 @@ menu "Kernel Core" bool "MT-Safe Kprint Test" default n + config RT_UTEST_KSYMS + bool "Kernel Symbol Table Test" + default n + depends on RT_USING_KSYMS + config RT_UTEST_SCHEDULER bool "Scheduler Test" default n diff --git a/src/utest/SConscript b/src/utest/SConscript index 52aca3dcf698..2e789bc75d41 100644 --- a/src/utest/SConscript +++ b/src/utest/SConscript @@ -55,6 +55,9 @@ if GetDepend(['RT_UTEST_HOOKLIST']): if GetDepend(['RT_UTEST_MTSAFE_KPRINT']): src += ['mtsafe_kprint_tc.c'] +if GetDepend(['RT_UTEST_KSYMS']): + src += ['ksym_tc.c'] + if GetDepend(['RT_UTEST_MEMPOOL']): src += ['mempool_tc.c'] @@ -74,4 +77,3 @@ for item in list: group = group + SConscript(os.path.join(item, 'SConscript')) Return('group') - diff --git a/src/utest/ksym_tc.c b/src/utest/ksym_tc.c new file mode 100644 index 000000000000..f244b6e08f15 --- /dev/null +++ b/src/utest/ksym_tc.c @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2006-2026, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "utest.h" +#include "utest_assert.h" + +static void __attribute__((noinline)) ksym_test_level1(void); +static void __attribute__((noinline)) ksym_test_level2(void); +static void __attribute__((noinline)) ksym_test_level3(void); + +static void __attribute__((noinline)) ksym_test_level1(void) +{ + ksym_test_level2(); +} + +static void __attribute__((noinline)) ksym_test_level2(void) +{ + ksym_test_level3(); +} + +static void __attribute__((noinline)) ksym_test_level3(void) +{ + rt_backtrace(); +} + +static void test_ksym_lookup(void) +{ + struct rt_ksym_info info; + rt_uintptr_t address; + + address = (rt_uintptr_t)(void *)ksym_test_level1; + uassert_int_equal(rt_ksym_lookup(address, &info), RT_EOK); + uassert_str_equal(info.name, "ksym_test_level1"); + uassert_int_equal(info.start, +#if defined(ARCH_ARM) && !defined(ARCH_CPU_64BIT) + address & ~((rt_uintptr_t)1) +#else + address +#endif + ); + uassert_int_equal(info.offset, 0); + +#if defined(ARCH_ARM) && !defined(ARCH_CPU_64BIT) + uassert_int_equal(rt_ksym_lookup(address | 1, &info), RT_EOK); + uassert_int_equal(info.offset, 0); +#endif + + uassert_int_equal(rt_ksym_lookup((rt_uintptr_t)-1, &info), -RT_ENOENT); + uassert_int_equal(rt_ksym_lookup(address, RT_NULL), -RT_EINVAL); +} + +static void test_ksym_static_chain(void) +{ + struct rt_ksym_info info; + +#if defined(ARCH_ARM_CORTEX_A) || defined(ARCH_ARMV8) + ksym_test_level1(); +#endif + uassert_int_equal(rt_ksym_lookup((rt_uintptr_t)(void *)ksym_test_level2, + &info), RT_EOK); + uassert_str_equal(info.name, "ksym_test_level2"); +} + +static rt_err_t utest_tc_init(void) +{ + return RT_EOK; +} + +static rt_err_t utest_tc_cleanup(void) +{ + return RT_EOK; +} + +static void testcase(void) +{ + UTEST_UNIT_RUN(test_ksym_lookup); + UTEST_UNIT_RUN(test_ksym_static_chain); +} + +UTEST_TC_EXPORT(testcase, "core.ksym", utest_tc_init, utest_tc_cleanup, 10); diff --git a/tools/building.py b/tools/building.py index 125da8521e76..22daa67c6155 100644 --- a/tools/building.py +++ b/tools/building.py @@ -874,6 +874,14 @@ def local_group(group, objects): PreBuilding() objects = one_list(objects) + if GetDepend('RT_USING_KSYMS'): + if GetOption('target'): + raise RuntimeError('RT_USING_KSYMS is supported by native SCons builds only') + if rtconfig.PLATFORM != 'gcc': + raise RuntimeError('RT_USING_KSYMS requires the GCC/ELF toolchain') + if not getattr(rtconfig, 'OBJDUMP', None): + raise RuntimeError('RT_USING_KSYMS requires rtconfig.OBJDUMP') + program = None # check whether special buildlib option lib_name = GetOption('buildlib') @@ -918,6 +926,11 @@ def local_group(group, objects): objects = sorted(objects) objects.append(objects_in_group) + if GetDepend('RT_USING_KSYMS') and rtconfig.PLATFORM == 'gcc': + from ksym import BuildWithKSym + program = BuildWithKSym(Env, target, objects, EndBuilding) + return + program = Env.Program(target, objects) EndBuilding(target, program) diff --git a/tools/gen_ksym.py b/tools/gen_ksym.py new file mode 100644 index 000000000000..92f19dc82e7d --- /dev/null +++ b/tools/gen_ksym.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2006-2026, RT-Thread Development Team +# +# SPDX-License-Identifier: Apache-2.0 +# +"""Generate the compact RT-Thread kernel function symbol table.""" + +import argparse +import os +import re +import subprocess +import sys + + +UINT32_MAX = 0xffffffff + + +def is_32bit_arm_elf(file_text): + """Return whether objdump reports a 32-bit ARM ELF image.""" + return bool(re.search(r"file format\s+elf32-(?:little|big)arm\b", + file_text)) + + +def _run_objdump(objdump, option, elf): + try: + result = subprocess.run([objdump, option, elf], check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + except (OSError, subprocess.CalledProcessError) as exc: + message = getattr(exc, "stderr", "") or str(exc) + raise RuntimeError("failed to run %s %s: %s" % + (objdump, option, message.strip())) from exc + return result.stdout + + +def parse_executable_sections(text): + """Return executable section ranges from GNU objdump -h output.""" + sections = {} + current = None + + for line in text.splitlines(): + match = re.match( + r"^\s*\d+\s+(\S+)\s+([0-9a-fA-F]+)\s+" + r"([0-9a-fA-F]+)\s+([0-9a-fA-F]+)\s+", + line) + if match: + name = match.group(1) + size = int(match.group(2), 16) + address = int(match.group(3), 16) + current = (name, address, size) + continue + + if current and "CODE" in line and "ALLOC" in line: + sections[current[0]] = current + current = None + + return sections + + +def parse_symbols(text, executable_sections, normalize_arm=False): + """Parse function symbols from GNU objdump -t output.""" + symbols = [] + + for line in text.splitlines(): + fields = line.split() + if len(fields) < 6: + continue + if not re.fullmatch(r"[0-9a-fA-F]+", fields[0]): + continue + if fields[2] != "F" or fields[3] not in executable_sections: + continue + + address = int(fields[0], 16) + size = int(fields[4], 16) + if normalize_arm: + address &= ~1 + name_index = 5 + if fields[name_index] in (".hidden", ".weak", ".protected"): + name_index += 1 + if name_index >= len(fields): + continue + name = fields[name_index] + if not name: + continue + symbols.append((address, size, name, fields[3])) + + if not symbols: + raise RuntimeError("no executable function symbols found in ELF") + + # A single address can have aliases. Keep a deterministic canonical name; + # the runtime resolver still handles duplicate addresses in hand-built data. + symbols.sort(key=lambda item: (item[0], item[2])) + unique = [] + index = 0 + while index < len(symbols): + address = symbols[index][0] + aliases = [] + while index < len(symbols) and symbols[index][0] == address: + aliases.append(symbols[index]) + index += 1 + + canonical = min(aliases, key=lambda item: item[2]) + size = max(item[1] for item in aliases) + unique.append((address, size, canonical[2], canonical[3])) + return unique + + +def resolve_symbol_sizes(symbols, executable_sections=None): + """Fill zero-sized functions from the next symbol in the same section.""" + resolved = [] + + for index, (address, size, name, section) in enumerate(symbols): + if size == 0: + for next_address, _, _, next_section in symbols[index + 1:]: + if next_section == section and next_address > address: + section_info = (executable_sections or {}).get(section) + if not section_info or next_address < section_info[1] + section_info[2]: + size = next_address - address + break + resolved.append((address, size, name)) + + return resolved + + +def collect_symbols(elf, objdump): + section_text = _run_objdump(objdump, "-h", elf) + executable_sections = parse_executable_sections(section_text) + if not executable_sections: + raise RuntimeError("no executable sections found in ELF") + + file_text = _run_objdump(objdump, "-f", elf) + normalize_arm = is_32bit_arm_elf(file_text) + symbols = parse_symbols(_run_objdump(objdump, "-t", elf), + executable_sections, normalize_arm) + symbols = resolve_symbol_sizes(symbols, executable_sections) + first = symbols[0][0] + if max(symbol[0] - first for symbol in symbols) > UINT32_MAX: + raise RuntimeError("kernel symbol address span does not fit uint32_t") + if any(symbol[1] > UINT32_MAX for symbol in symbols): + raise RuntimeError("kernel symbol size does not fit uint32_t") + + return symbols, first + + +def generate_source(symbols, first): + names = bytearray() + name_offsets = {} + entries = [] + + for address, size, name in symbols: + if name not in name_offsets: + name_offsets[name] = len(names) + names.extend(name.encode("utf-8")) + names.append(0) + entries.append((address - first, name_offsets[name], size)) + + name_literal = "".join("\\x%02x" % value for value in names) + lines = [ + "/* Generated by tools/gen_ksym.py. Do not edit. */", + "#include ", + "", + 'rt_used rt_section(".rodata.rt_ksym")', + "const rt_uintptr_t rt_ksym_first_addr = 0x%x;" % first, + 'rt_used rt_section(".rodata.rt_ksym")', + "const rt_uint32_t rt_ksym_count = %d;" % len(entries), + 'rt_used rt_section(".rodata.rt_ksym")', + "const rt_uint32_t rt_ksym_entries[][3] = {", + ] + lines.extend(" {0x%x, %d, 0x%x}," % entry for entry in entries) + lines.extend([ + "};", + 'rt_used rt_section(".rodata.rt_ksym")', + "const char rt_ksym_names[] = \"%s\";" % name_literal, + "", + ]) + return "\n".join(lines) + + +def main(argv=None): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--elf", required=True) + parser.add_argument("--objdump", default="objdump") + parser.add_argument("--output") + parser.add_argument("--verify", action="store_true") + parser.add_argument("--table") + args = parser.parse_args(argv) + + if args.verify and not args.table: + parser.error("--verify requires --table") + if not args.verify and not args.output: + parser.error("--output is required unless --verify is used") + + try: + symbols, first = collect_symbols(args.elf, args.objdump) + source = generate_source(symbols, first) + + if args.verify: + with open(args.table, "r", encoding="utf-8") as table: + actual = table.read() + if actual != source: + raise RuntimeError("ksym table did not converge after final link") + else: + output_dir = os.path.dirname(os.path.abspath(args.output)) + os.makedirs(output_dir, exist_ok=True) + with open(args.output, "w", encoding="utf-8", newline="\n") as table: + table.write(source) + except (OSError, RuntimeError) as exc: + print("gen_ksym.py: %s" % exc, file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/ksym.py b/tools/ksym.py new file mode 100644 index 000000000000..534611005f97 --- /dev/null +++ b/tools/ksym.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2006-2026, RT-Thread Development Team +# +# SPDX-License-Identifier: Apache-2.0 +# +"""SCons integration for the generated RT-Thread kernel symbol table.""" + +import os +import subprocess +import sys + +import rtconfig + + +def _run_generator(target, source, env): + command = [sys.executable, + os.path.join(env['RTT_ROOT'], 'tools', 'gen_ksym.py'), + '--elf', source[0].abspath, + '--output', target[0].abspath, + '--objdump', rtconfig.OBJDUMP] + return subprocess.call(command, env=dict(env['ENV'])) + + +def BuildWithKSym(env, target, objects, end_building): + """Build preliminary links, generate tables, then link and verify final ELF.""" + target_name = str(target) + target_base = os.path.basename(target_name) + target_stem, target_ext = os.path.splitext(target_base) + artifact_dir = os.path.join('build', 'ksym') + pre1_name = os.path.join(artifact_dir, target_stem + '.pre1' + target_ext) + pre2_name = os.path.join(artifact_dir, target_stem + '.pre2' + target_ext) + table1_name = os.path.join(artifact_dir, target_stem + '.pre1.c') + table2_name = os.path.join(artifact_dir, target_stem + '.final.c') + object1_name = os.path.join(artifact_dir, target_stem + '.pre1.o') + object2_name = os.path.join(artifact_dir, target_stem + '.final.o') + + pre1 = env.Program(pre1_name, objects) + table1 = env.Command(table1_name, pre1, _run_generator) + object1 = env.Object(object1_name, table1) + pre2 = env.Program(pre2_name, objects + [object1]) + table2 = env.Command(table2_name, pre2, _run_generator) + object2 = env.Object(object2_name, table2) + program = env.Program(target, objects + [object2]) + + # SCons post actions run in registration order, so verification precedes + # the normal binary/size post action installed by EndBuilding(). + def verify_final(target, source, env): + command = [sys.executable, + os.path.join(env['RTT_ROOT'], 'tools', 'gen_ksym.py'), + '--verify', + '--elf', target[0].abspath, + '--table', table2[0].abspath, + '--objdump', rtconfig.OBJDUMP] + return subprocess.call(command, env=dict(env['ENV'])) + + env.AddPostAction(program, verify_final) + end_building(target, program) + return program diff --git a/tools/testcases/test_gen_ksym.py b/tools/testcases/test_gen_ksym.py new file mode 100644 index 000000000000..08699b1fe4bd --- /dev/null +++ b/tools/testcases/test_gen_ksym.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2006-2026, RT-Thread Development Team +# +# SPDX-License-Identifier: Apache-2.0 +# + +import importlib.util +import os +import unittest + + +MODULE_PATH = os.path.join(os.path.dirname(__file__), "..", "gen_ksym.py") +SPEC = importlib.util.spec_from_file_location("gen_ksym", MODULE_PATH) +GEN_KSYM = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(GEN_KSYM) + + +class GenKsymTest(unittest.TestCase): + def test_parse_local_global_weak_and_aliases(self): + sections = """\ +Idx Name Size VMA LMA File off Algn + 0 .text 00000080 00001000 00001000 00000034 2**2 + CONTENTS, ALLOC, LOAD, READONLY, CODE + 1 .data 00000010 00002000 00002000 000000b4 2**2 + CONTENTS, ALLOC, LOAD, DATA +""" + symbols = """\ +SYMBOL TABLE: +00001000 l F .text 00000010 static_func +00001000 g F .text 00000010 public_alias +00001020 w F .text 00000020 weak_func +00002000 g O .data 00000004 data_object +00000000 g F *UND* 00000000 undefined_func +""" + + executable = GEN_KSYM.parse_executable_sections(sections) + parsed = GEN_KSYM.parse_symbols(symbols, executable) + + self.assertEqual(parsed, [ + (0x1000, 0x10, "public_alias", ".text"), + (0x1020, 0x20, "weak_func", ".text"), + ]) + + def test_function_at_zero_is_kept(self): + sections = """\ +Idx Name Size VMA LMA File off Algn + 0 .text 00000020 00000000 00000000 00000034 2**2 + CONTENTS, ALLOC, LOAD, READONLY, CODE +""" + symbols = """\ +SYMBOL TABLE: +00000000 l F .text 00000010 function_at_zero +""" + + executable = GEN_KSYM.parse_executable_sections(sections) + parsed = GEN_KSYM.parse_symbols(symbols, executable) + + self.assertEqual(parsed, [ + (0x0, 0x10, "function_at_zero", ".text"), + ]) + + def test_arm32_elf_detection(self): + for architecture in ("arm", "armv7", "armv7e-m"): + file_text = ("file format elf32-littlearm\n" + "architecture: %s, flags 0x0:" % architecture) + self.assertTrue(GEN_KSYM.is_32bit_arm_elf(file_text)) + + self.assertFalse(GEN_KSYM.is_32bit_arm_elf( + "file format elf64-littleaarch64\n" + "architecture: aarch64, flags 0x0:")) + + def test_zero_size_uses_same_section_boundary(self): + symbols = GEN_KSYM.resolve_symbol_sizes([ + (0x1000, 0, "first", ".text"), + (0x1020, 0, "middle", ".text"), + (0x1040, 0, "last_text", ".text"), + (0x8000, 0x30, "ramfunc", ".ramfunc"), + ]) + + self.assertEqual(symbols, [ + (0x1000, 0x20, "first"), + (0x1020, 0x20, "middle"), + (0x1040, 0, "last_text"), + (0x8000, 0x30, "ramfunc"), + ]) + + def test_generated_table_uses_relative_addresses_and_size(self): + source = GEN_KSYM.generate_source( + [(0x1000, 0x10, "first"), (0x1020, 0, "last")], 0x1000) + + self.assertIn("rt_ksym_first_addr = 0x1000", source) + self.assertIn("{0x20, 6, 0x0}", source) + self.assertIn("{0x0, 0, 0x10}", source) + + def test_arm_thumb_addresses_are_canonicalized(self): + symbols = GEN_KSYM.parse_symbols( + "00001001 l F .text 00000010 thumb_func\n", + {".text": (".text", 0x1000, 0x20)}, + normalize_arm=True) + + self.assertEqual(symbols[0][0], 0x1000) + + +if __name__ == "__main__": + unittest.main()