ARM平台FreeRTOS中dump_stack()替代:栈地址转函数符号需求
Great question — I’ve dealt with this exact problem when debugging FreeRTOS on ARM targets. The core issue is that _Unwind_Backtrace only gives you raw program counter (PC) addresses; it doesn’t have built-in logic to map those addresses to function names, filenames, or line numbers. Let’s walk through two main approaches to solve this: offline debugging (quick for one-off checks) and runtime symbol resolution (for in-app call chain printing).
1. First: Ensure Debug Symbols Are Enabled
Before anything else, you need to compile your code with debug symbols included — otherwise, there’s no data to map addresses to symbols. Update your GCC compile flags to include:
-g: Generates debug information (level 2 is default and sufficient)- Avoid
-s(which strips symbols) and don’t runstripon your final ELF binary
Example compile command (adjust mcpu for your ARM core):
arm-none-eabi-gcc -g -O0 -mcpu=cortex-m4 -mthumb -o your_app your_app.c -lfreertos
2. Offline Address-to-Symbol Resolution (Quick Debugging)
If you just need to debug a crash or call chain after the fact, use the GNU addr2line tool. This is the fastest way to translate PC addresses to human-readable info.
Take a PC address from your output (like 0x40076b) and run:
arm-none-eabi-addr2line -e your_app.elf 0x40076b
You’ll get output like:
/home/you/projects/your_app.c:22
Or with function names if you add -f:
arm-none-eabi-addr2line -f -e your_app.elf 0x40076b
Output:
func_1 /home/you/projects/your_app.c:22
3. Runtime Symbol Resolution (In-App Call Chain Printing)
If you want your FreeRTOS application to print function names directly when calling your stack trace function, you have two options depending on your environment:
Option A: For Linux/Android on ARM (Dynamic Linking Supported)
If you’re running FreeRTOS on top of a Linux kernel (or another OS with dynamic linking), use the dladdr() function from libdl to resolve symbols at runtime. Modify your callback like this:
#include <stdio.h> #include <unwind.h> #include <stdint.h> #include <dlfcn.h> static _Unwind_Reason_Code unwind_backtrace_callback(struct _Unwind_Context* context, void* arg) { uintptr_t pc = _Unwind_GetIP(context); if (pc) { printf("unwind got pc ...0x%x\n", pc); Dl_info symbol_info; if (dladdr((void*)pc, &symbol_info)) { if (symbol_info.dli_sname) { printf(" Function: %s\n", symbol_info.dli_sname); } if (symbol_info.dli_fname) { printf(" Source File: %s\n", symbol_info.dli_fname); } } } return _URC_NO_REASON; } // Rest of your existing code remains the same ssize_t unwind_backtrace() { _Unwind_Reason_Code rc = _Unwind_Backtrace(unwind_backtrace_callback, 0); return rc == _URC_END_OF_STACK ? 0 : -1; } void func_1() { int ret = unwind_backtrace(); printf("unwind_backtrace return ...%d\n", ret); } void func_2() { func_1(); } int main() { func_2(); return 0; }
Compile with -ldl to link the dynamic linking library:
arm-none-eabi-gcc -g -o your_app your_app.c -ldl
Option B: For Bare-Metal FreeRTOS (No Dynamic Linking)
Bare-metal FreeRTOS targets don’t have a dynamic linker, so we’ll embed the ELF symbol table directly into the binary and parse it manually. Here’s how:
Step 1: Update Your Linker Script
Add these lines to your linker script (e.g., linker.ld) to expose the symbol table and string table addresses:
// Place these inside the .text or .data section (adjust as needed) PROVIDE(__symtab_start = .); *(.symtab) PROVIDE(__symtab_end = .); PROVIDE(__strtab_start = .); *(.strtab) PROVIDE(__strtab_end = .);
Step 2: Modify Your Code to Parse Symbols
Add a function to map PC addresses to function names, then update your callback:
#include <stdio.h> #include <unwind.h> #include <stdint.h> #include <elf.h> // Import symbols from the linker script extern Elf32_Sym __symtab_start; extern Elf32_Sym __symtab_end; extern char __strtab_start; extern char __strtab_end; // Resolve a PC address to its function name const char* addr_to_symbol(uintptr_t addr) { Elf32_Sym* current_sym = &__symtab_start; while (current_sym < &__symtab_end) { // Only consider function symbols (STT_FUNC) if (ELF32_ST_TYPE(current_sym->st_info) == STT_FUNC) { uintptr_t sym_start = current_sym->st_value; uintptr_t sym_end = sym_start + current_sym->st_size; // Check if the PC falls within this function's address range if (addr >= sym_start && addr < sym_end) { // Grab the symbol name from the string table return &__strtab_start + current_sym->st_name; } } current_sym++; } return "unknown"; } static _Unwind_Reason_Code unwind_backtrace_callback(struct _Unwind_Context* context, void* arg) { uintptr_t pc = _Unwind_GetIP(context); if (pc) { printf("unwind got pc ...0x%x\n", pc); const char* func_name = addr_to_symbol(pc); printf(" Function: %s\n", func_name); } return _URC_NO_REASON; } // Rest of your code stays the same ssize_t unwind_backtrace() { _Unwind_Reason_Code rc = _Unwind_Backtrace(unwind_backtrace_callback, 0); return rc == _URC_END_OF_STACK ? 0 : -1; } void func_1() { int ret = unwind_backtrace(); printf("unwind_backtrace return ...%d\n", ret); } void func_2() { func_1(); } int main() { func_2(); return 0; }
Compile with your modified linker script:
arm-none-eabi-gcc -g -O0 -mcpu=cortex-m4 -mthumb -T linker.ld -o your_app your_app.c
4. FreeRTOS-Specific Notes
- Task Stack Sizes: Make sure your FreeRTOS task stacks are large enough — stack overflow will corrupt your backtrace data.
- Interrupt Context: If you’re calling the stack trace from an interrupt, ensure you’re using the correct stack (some ARM cores have separate interrupt stacks) to get accurate call chains.
Example Output
After implementing one of these methods, your output will look like this:
unwind got pc ...0x40076b Function: func_1 unwind got pc ...0x400796 Function: func_2 unwind got pc ...0x4007bd Function: main unwind got pc ...0x400819 Function: __libc_start_main unwind got pc ...0x67314b15 Function: unknown unwind got pc ...0x400649 Function: _start unwind_backtrace return ...0
内容的提问来源于stack exchange,提问作者KSoni

