C 中的调用堆栈回溯
Call stack backtrace in C
我正在尝试在我的 assert/exception 处理程序中获取调用堆栈回溯。不能include "execinfo.h"
因此不能使用int backtrace(void **buffer, int size);
。
此外,尝试使用 __builtin_return_address()
但根据 :http://codingrelic.geekhold.com/2009/05/pre-mortem-backtracing.html
... on some architectures, including my beloved MIPS, only __builtin_return_address(0) works.MIPS has no frame pointer, making it difficult to walk back up the stack. Frame 0 can use the return address register directly.
如何重现完整的调用堆栈回溯?
我已成功使用 here 中描述的方法从 MIPS32 上的堆栈中获取调用跟踪。
然后您可以打印出调用堆栈:
void *retaddrs[16];
int n, i;
n = get_call_stack_no_fp (retaddrs, 16);
printf ("CALL STACK: ");
for (i = 0; i < n; i++) {
printf ("0x%08X ", (uintptr_t)retaddrs[i]);
}
printf ("\r\n");
... 如果您有 ELF 文件,则使用 addr2line
将 return 地址转换为函数名称:
addr2line -a -f -p -e xxxxxxx.elf addr addr ...
当然有很多问题,当使用这样的方法时,包括中断和异常处理程序或代码优化的结果。但是,尽管如此,它有时可能会有所帮助。
我已经成功使用了@Erki A建议的方法并描述了here。
以下是该方法的简短摘要:
问题:
获取没有帧指针的调用堆栈。
解决方案主要思想:
从汇编代码中得出调试器从调试信息中理解的内容。
我们需要的信息:
1.保存return地址的地方。
2.栈指针减多少。
要重现整个堆栈跟踪,需要:
1. Get the current $sp and $ra
2. Scan towards the beginning of the function and look for "addui
sp,sp,spofft" command (spofft<0)
3. Reprodece prev. $sp (sp- spofft)
4. Scan forward and look for "sw r31,raofft(sp)"
5. Prev. return address stored at [sp+ raofft]
上面我描述了一次迭代。当 $ra 为 0 时停止。
如何获得第一个$ra?
__builtin_return_address(0)
如何获得第一个$sp?
register unsigned sp asm("29");
asm("" : "=r" (sp));
*** 因为我的大部分文件都是用 micro-mips 优化编译的,所以我不得不处理 micro-mips-ISA。
当我尝试分析使用 microMips 优化编译的代码时出现了很多问题(请记住,每一步的目标都是重现 prev.ra 和 prev.sp):
这让事情变得有点复杂:
1. ra () register contain unaligned return address.
You may find more information at Linked questions.
The unaligned ra helps you understand that you run over different
ISA(micro-mips-isa)
2. There are functions that do not move the sp. You can find more
information [here][3].
(If a "leaf" function only modifies the temporary registers and returns
to a return statement in its caller's code, then there is no need for
$ra to be changed, and there is no need for a stack frame for that
function.)
3. Functions that do not store the ra
4. MicroMips instructions can be both - 16bit and 32bit: run over the
commnds using unsinged short*.
5. There are functions that perform "addiu sp, sp, spofft" more than once
6. micro-mips-isa has couple variations for the same command
for example: addiu,addiusp.
我决定忽略部分问题,这就是为什么它适用于 95% 的情况。
我正在尝试在我的 assert/exception 处理程序中获取调用堆栈回溯。不能include "execinfo.h"
因此不能使用int backtrace(void **buffer, int size);
。
此外,尝试使用 __builtin_return_address()
但根据 :http://codingrelic.geekhold.com/2009/05/pre-mortem-backtracing.html
... on some architectures, including my beloved MIPS, only __builtin_return_address(0) works.MIPS has no frame pointer, making it difficult to walk back up the stack. Frame 0 can use the return address register directly.
如何重现完整的调用堆栈回溯?
我已成功使用 here 中描述的方法从 MIPS32 上的堆栈中获取调用跟踪。
然后您可以打印出调用堆栈:
void *retaddrs[16];
int n, i;
n = get_call_stack_no_fp (retaddrs, 16);
printf ("CALL STACK: ");
for (i = 0; i < n; i++) {
printf ("0x%08X ", (uintptr_t)retaddrs[i]);
}
printf ("\r\n");
... 如果您有 ELF 文件,则使用 addr2line
将 return 地址转换为函数名称:
addr2line -a -f -p -e xxxxxxx.elf addr addr ...
当然有很多问题,当使用这样的方法时,包括中断和异常处理程序或代码优化的结果。但是,尽管如此,它有时可能会有所帮助。
我已经成功使用了@Erki A建议的方法并描述了here。 以下是该方法的简短摘要:
问题:
获取没有帧指针的调用堆栈。
解决方案主要思想:
从汇编代码中得出调试器从调试信息中理解的内容。
我们需要的信息:
1.保存return地址的地方。
2.栈指针减多少。
要重现整个堆栈跟踪,需要:
1. Get the current $sp and $ra
2. Scan towards the beginning of the function and look for "addui
sp,sp,spofft" command (spofft<0)
3. Reprodece prev. $sp (sp- spofft)
4. Scan forward and look for "sw r31,raofft(sp)"
5. Prev. return address stored at [sp+ raofft]
上面我描述了一次迭代。当 $ra 为 0 时停止。 如何获得第一个$ra?
__builtin_return_address(0)
如何获得第一个$sp?
register unsigned sp asm("29");
asm("" : "=r" (sp));
*** 因为我的大部分文件都是用 micro-mips 优化编译的,所以我不得不处理 micro-mips-ISA。 当我尝试分析使用 microMips 优化编译的代码时出现了很多问题(请记住,每一步的目标都是重现 prev.ra 和 prev.sp): 这让事情变得有点复杂:
1. ra () register contain unaligned return address.
You may find more information at Linked questions.
The unaligned ra helps you understand that you run over different
ISA(micro-mips-isa)
2. There are functions that do not move the sp. You can find more
information [here][3].
(If a "leaf" function only modifies the temporary registers and returns
to a return statement in its caller's code, then there is no need for
$ra to be changed, and there is no need for a stack frame for that
function.)
3. Functions that do not store the ra
4. MicroMips instructions can be both - 16bit and 32bit: run over the
commnds using unsinged short*.
5. There are functions that perform "addiu sp, sp, spofft" more than once
6. micro-mips-isa has couple variations for the same command
for example: addiu,addiusp.
我决定忽略部分问题,这就是为什么它适用于 95% 的情况。