将代码移植到新平台时, GNU libc 的回溯和在线仿真器/调试器并不总是可用,尤其是当目标是诸如Z80之类的微型C编译器时。(通常,程序错误会“挂在”某处,或者使小工具崩溃。)
手动插入 printf的经典“狼围栏”方法是否有替代方法?程序员在开发包括跟踪和回溯到 C 程序中的程序时可以做的简单和可移植的事情(不使用 C 扩展)?
顺便说一句:这里有几个关于stackoverflow的其他相关问题,但这些都使用GNU GLIBC 的回溯,并且回溯通常是特定于编译器/实现的:
将代码移植到新平台时, GNU libc 的回溯和在线仿真器/调试器并不总是可用,尤其是当目标是诸如Z80之类的微型C编译器时。(通常,程序错误会“挂在”某处,或者使小工具崩溃。)
手动插入 printf的经典“狼围栏”方法是否有替代方法?程序员在开发包括跟踪和回溯到 C 程序中的程序时可以做的简单和可移植的事情(不使用 C 扩展)?
顺便说一句:这里有几个关于stackoverflow的其他相关问题,但这些都使用GNU GLIBC 的回溯,并且回溯通常是特定于编译器/实现的:
这是我的内核的内核:写一些代码。
我的回答的核心是:如果你的编译器总是在堆栈上分配局部变量,那么......
在每个记录函数名称的函数入口处将 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,RETURN 宏就像 EXIT,但在 } while (0) 之前有一个 return。
创建一个函数来遍历 blob 列表并打印出函数名称,可以将其称为 stacktrace 或 backtrace 之类的东西。
编写一个程序以调用 ENTER(f) 和 EXIT() 和 RETURN(x) 来检测您的 C 代码。
省略了一些细节,让您玩得开心...
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);
}
#define STACK_TRACE_ON 和 #include "stack_trace.h" from RosettaCode.org然后在需要的地方插入 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”子句)。
提示/网址赞赏。
在 Symbian 上,有一些脚本用于检查寄存器和堆栈,以查找看起来像代码地址的东西。
这不是可移植的,但它也不依赖于修饰代码。在字节数很重要的平台上,这是一个必要的权衡……而且它不像 Z80 那样受限!但在没有帧指针等的情况下编译得足够有限。
要从没有帧指针的堆栈计算回溯,您必须向上处理堆栈而不是向下处理。