libdragon
|
Backtrace (call stack) support. More...
Data Structures | |
struct | symtable_header_t |
Symbol table file header. More... | |
struct | symtable_entry_t |
Symbol table entry. More... | |
Macros | |
#define | BACKTRACE_DEBUG 0 |
Enable to debug why a backtrace is wrong. | |
#define | FUNCTION_ALIGNMENT 32 |
Function alignment enfored by the compiler (-falign-functions). | |
#define | ADDRENTRY_ADDR(e) ((e) & ~3) |
Address (without the flags9) | |
#define | ADDRENTRY_IS_FUNC(e) ((e) & 1) |
True if the address is the start of a function. | |
#define | ADDRENTRY_IS_INLINE(e) ((e) & 2) |
True if the address is an inline duplicate. | |
#define | MIPS_OP_ADDIU_SP(op) (((op) & 0xFFFF0000) == 0x27BD0000) |
Matches: addiu $sp, $sp, imm. | |
#define | MIPS_OP_DADDIU_SP(op) (((op) & 0xFFFF0000) == 0x67BD0000) |
Matches: daddiu $sp, $sp, imm. | |
#define | MIPS_OP_JR_RA(op) (((op) & 0xFFFFFFFF) == 0x03E00008) |
Matches: jr $ra. | |
#define | MIPS_OP_SD_RA_SP(op) (((op) & 0xFFFF0000) == 0xFFBF0000) |
Matches: sd $ra, imm($sp) | |
#define | MIPS_OP_SD_FP_SP(op) (((op) & 0xFFFF0000) == 0xFFBE0000) |
Matches: sd $fp, imm($sp) | |
#define | MIPS_OP_LUI_GP(op) (((op) & 0xFFFF0000) == 0x3C1C0000) |
Matches: lui $gp, imm. | |
#define | MIPS_OP_NOP(op) ((op) == 0x00000000) |
Matches: nop. | |
#define | MIPS_OP_MOVE_FP_SP(op) ((op) == 0x03A0F025) |
Matches: move $fp, $sp. | |
Typedefs | |
typedef uint32_t | addrtable_entry_t |
Entry in the address table. | |
Functions | |
char * | __symbolize (void *vaddr, char *buf, int size) |
Return the symbol associated to a given address. | |
bool | __bt_analyze_func (bt_func_t *func, uint32_t *ptr, uint32_t func_start, bool from_exception) |
Analyze a function to find out its stack frame layout and properties (useful for backtracing). | |
int | backtrace (void **buffer, int size) |
Walk the stack and return the current call stack. | |
bool | backtrace_symbols_cb (void **buffer, int size, uint32_t flags, void(*cb)(void *, backtrace_frame_t *), void *cb_arg) |
Symbolize the buffer returned by backtrace, calling a callback for each frame. | |
char ** | backtrace_symbols (void **buffer, int size) |
Translate the buffer returned by backtrace into a list of strings. | |
void | backtrace_frame_print (backtrace_frame_t *frame, FILE *out) |
Print a single frame of a backtrace. | |
void | backtrace_frame_print_compact (backtrace_frame_t *frame, FILE *out, int width) |
Print a single frame of a backtrace, in a compact format. | |
Variables | |
uint32_t | inthandler [] |
Exception handler (see inthandler.S) | |
uint32_t | inthandler_end [] |
End of exception handler (see inthandler.S) | |
Backtrace (call stack) support.
This file contains the implementation of the backtrace support. See backtrace.h for an overview of the API. Here follows some implementation details.
MIPS ABIs do not generally provide a way to walk the stack, as the frame pointer is not guaranteed to be present. It is possible to force its presence via "-fno-omit-frame-pointer", but we tried to provide a solution that works with standard compilation settings.
To perform backtracing, we scan the code backward starting from the return address of each frame. While scanning, we note some special instructions that we look for. The two main instructions that we look for are sd ra, offset(sp)
which is used to save the previous return address to the stack, and addiu sp, sp, offset
which creates the stack frame for the current function. When we find both, we know how to get back to the previous frame.
Notice that this also works through exceptions, as the exception handler does create a stack frame exactly like a standard function (see inthandler.S).
Only a few functions do use a frame pointer: those that allocate a runtime-calculated amount of stack (eg: using alloca). Because of this, we actually look for usages of the frame pointer register fp, and track those as well to be able to correctly walk the stack in those cases.
To symbolize the backtrace, we use a symbol table file (SYMT) that is generated by the n64sym tool during the build process. The symbol table is put into the rompak (see rompak_internal.h) and is structured in a way that can be queried directly from ROM, without even allocating memory. This is especially useful to provide backtrace in catastrophic situations where the heap is not available.
The symbol table file contains the source code references (function name, file name, line number) for a number of addresses in the ROM. Since it would be impractical to save information for all the addresses in the text segment, only special addresses are saved: in particular, those where a function call is made (ie: the address of JAL / JALR instructions), which are the ones that are commonly found in backtraces and thus need to be symbolized. In addition to these, the symbol table contains also information associated to the addresses that mark the start of each function, so that it's always possible to infer the function a certain address belongs to.
Given that not all addresses are saved, it is important to provide accurate source code references for stack frames that are interrupted by interrupts or exceptions; in those cases, the symbolization will simply return the function name the addresses belongs to, without any source code reference.
To see more details on how the symbol table is structured in the ROM, see symtable_header_t and the source code of the n64sym tool.
struct symtable_header_t |
Symbol table file header.
The SYMT file is made of three main tables:
The SYMT file is generated by the n64sym tool during the build process.
struct symtable_entry_t |
Symbol table entry.
#define FUNCTION_ALIGNMENT 32 |
Function alignment enfored by the compiler (-falign-functions).
typedef uint32_t addrtable_entry_t |
Entry in the address table.
This is an address in RAM, with the lowest 2 bits used to store additional information. See the ADDRENTRY_* macros to access the various components.
char * __symbolize | ( | void * | vaddr, |
char * | buf, | ||
int | size | ||
) |
Return the symbol associated to a given address.
This function inspect the symbol table (if any) to search for the specified address. It returns the function name the address belongs to, and the offset within the function as a string in the format "function_name+0x1234".
If the symbol table is not found in the rompack or the address is not found, the return string is "???".
vaddr | Address to symbolize |
buf | Buffer where to store the result |
size | Size of the buffer |
bool __bt_analyze_func | ( | bt_func_t * | func, |
uint32_t * | ptr, | ||
uint32_t | func_start, | ||
bool | from_exception | ||
) |
Analyze a function to find out its stack frame layout and properties (useful for backtracing).
This function implements the core heuristic used by the backtrace engine. It analyzes the actual code of a function in memory instruction by instruction, trying to find out whether the function uses a stack frame or not, whether it uses a frame pointer, and where the return address is stored.
Since we do not have DWARF informations or similar metadata, we can just do educated guesses. A mistake in the heuristic will result probably in a wrong backtrace from this point on.
The heuristic works as follows:
sd $ra, nn($sp)
), and an instruction creating the stack frame (eg: addiu $sp, $sp, -nn
). Once both are found, the heuristic knows how to fill in .stack_size
and .ra_offset
fields of the function description structure, and it can stop.move $fp, $sp
, it knows that the function uses $fp as frame pointer, and will mark the function as BT_FUNCTION_FRAMEPOINTER. In any case, the field .fp_offset
will be filled in with the offset in the stack where $fp is stored, so that the backtrace engine can track the current value of the register in any case.func | Output function description structure |
ptr | Pointer to the function code at the point where the backtrace starts. This is normally the point where a JAL opcode is found, as we are walking up the call stack. |
func_start | Start of the function being analyzed. This is optional: the heuristic can work without this hint, but it is useful in certain situations (eg: to better walk up after an exception). |
from_exception | If true, this function was interrupted by an exception. This is a hint that the function might even be a leaf function without a stack frame, and that we must use special heuristics for it. |