GNU libc's backtrace和In-circuit emulators/debuggers并不总是可用,尤其是当目标是微C编译器时,例如Z80。 (通常程序错误会“暂停”某个地方,或者崩溃小工具。)
是否有替代手动插入printf的经典“wolf fencing”方法?编程人员在开发包含跟踪和回溯到C程序的程序时可以做的简单和便携(不使用C扩展)的东西?
顺便说一句:以下是关于stackoverflow的其他一些相关问题,但这两个问题都使用GNU GLIBC's backtrace,而backtrace通常是编译器/实现特定的:
答案 0 :(得分:4)
这是我的答案内核的核心:编写一些代码。
我的答案的核心是:如果您的编译器总是在堆栈上分配本地,那么......
在记录函数名称的每个函数条目中将blob添加到堆栈中,抛出一些幻数以捕获堆栈碎片。
typedef struct stack_debug_blob_ {
int magic1;
const char * function_name;
int magic2;
struct stack_debug_blob_ * called_by;
int magic3;
} stack_debug_blob;
stack_debug_blob * top_of_stack_debug_blobs = 0;
创建一个宏ENTER(f)获取该函数的名称。宏应该是关于开放后每个函数中的第一行代码{。它添加了一个结构,其中包含指向(const)char *函数名称的指针,指向堆栈上前一个结构的指针,以及一些用于检查健全性的魔术数字。使blob堆栈指针的顶部指向这个新结构。
#define ENTER(f) \
stack_debug_blob new_stack_debug_blob = { \
MAGIC1, (f), MAGIC2, top_of_stack_debug_blobs, MAGIC3}; \
stack_debug_blob * evil_hack = (top_of_stack_debug_blobs = (&new_stack_debug_blob))
为了尽可能保持可移植性,所有ENTER都能做的是声明和初始化变量。因此,evil_hack除了初始化变量之外还要做一些额外的计算。
创建一个函数来查看检查指针和幻数的blob列表。它应该发出一个错误信号(可能是打印到stderr,可能用while(1){/ * nada * /}锁定cpu,也许进入调试器......取决于你的硬件)如果发现事情搞砸了。
创建一个宏EXIT(),用于检查您的blob堆栈,然后从链接列表中取消最顶层的链接。它需要放在所有功能的退出点。
#define EXIT() do { \
check_debug_blobs(); \
top_of_stack_debug_blobs = new_stack_debug_blob.called_by; \
new_stack_debug_blob.magic1 -= 1; /* paranoia */ \
} while (0)
可能还需要用RETURN宏调用替换所有返回值,RETURN宏就像EXIT一样,但在} while(0)之前返回。
创建一个函数来向下遍历打印出函数名称的blob列表,可以将其称为stacktrace或backtrace。
通过调用ENTER(f)和EXIT()和RETURN(x)编写程序来检测C代码。
遗漏了一些细节,让你玩得开心......
答案 1 :(得分:2)
RosettaCode.org有一个实现,它使用与@ jsl4tv建议相同的基本思想。
示例,给出以下内置“ hang ”的经典C代码:
#include <stdio.h>
#include <stdlib.h>
void inner(int k)
{
for(;;){} /* hang */
}
void middle(int x, int y)
{
inner(x*y);
}
void outer(int a, int b, int c)
{
middle(a+b, b+c);
}
int main()
{
outer(2,3,5);
return(EXIT_SUCCESS);
}
从RosettaCode.org #define STACK_TRACE_ON和#include“stack_trace.h”然后在需要时插入BEGIN(f)/ ENDs:
#include <stdio.h>
#include <stdlib.h>
#define STACK_TRACE_ON /* compile in these "stack_trace" routines */
#include "stack_trace.h"
void inner(int k)
BEGIN(inner)
print_indent(); printf("*** Now dump the stack ***\n");
print_stack_trace();
for(;;){} /* hang */
END
void middle(int x, int y)
BEGIN(middle)
inner(x*y);
END
void outer(int a, int b, int c)
BEGIN(outer)
middle(a+b, b+c);
END
int main()
BEGIN(main)
stack_trace.on = TRUE; /* turn on runtime tracing */
outer(2,3,5);
stack_trace.on = FALSE;
RETURN(EXIT_SUCCESS);
END
产地:
stack_trace_test.c:19: BEGIN outer[0x80487b4], stack(depth:1, size:60)
stack_trace_test.c:14: BEGIN middle[0x8048749], stack(depth:2, size:108)
stack_trace_test.c:8: BEGIN inner[0x80486d8], stack(depth:3, size:156)
stack_trace_test.c:8: *** Now dump the stack ***
stack_trace_test.c:8: inner[0x80486d8] --- stack(depth:4, size:156) ---
stack_trace_test.c:14: middle[0x8048749] --- stack(depth:3, size:108) ---
stack_trace_test.c:19: outer[0x80487b4] --- stack(depth:2, size:60) ---
stack_trace_test.c:24: main[0x804882a] --- stack(depth:1, size:0) ---
stack_trace_test.c:8: --- (depth 4) ---
这个BEGIN~END方法的完美抛光[开源]版本将是完美的。 (如果它有异常处理的“FINALLY”子句,则为Esp)。
提示/网址赞赏。
答案 2 :(得分:0)
在Symbian上有一些脚本用于遍历寄存器和堆栈,寻找看起来像代码地址的东西。
这不是便携式的,但它也不依赖于装饰代码。这是字节计数重要的平台上的必要权衡......并且它几乎不像Z80那样有限!但有限,足以编译没有帧指针等。
要计算没有帧指针的堆栈的回溯,你必须处理堆栈而不是它。