3

将代码移植到新平台时, GNU libc 的回溯在线仿真器/调试器并不总是可用,尤其是当目标是诸如Z80之类的微型C编译器时。(通常,程序错误会“挂在”某处,或者使小工具崩溃。)

手动插入 printf的经典“狼围栏”方法是否有替代方法?程序员在开发包括跟踪和回溯到 C 程序中的程序时可以做的简单和可移植的事情(不使用 C 扩展)?

顺便说一句:这里有几个关于stackoverflow的其他相关问题,但这些都使用GNU GLIBC 的回溯,并且回溯通常是特定于编译器/实现的:

4

3 回答 3

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,RETURN 宏就像 EXIT,但在 } while (0) 之前有一个 return。

创建一个函数来遍历 blob 列表并打印出函数名称,可以将其称为 stacktrace 或 backtrace 之类的东西。

编写一个程序以调用 ENTER(f) 和 EXIT() 和 RETURN(x) 来检测您的 C 代码。

省略了一些细节,让您玩得开心...

另请参阅uclibc 的任何可用回溯移植?

于 2010-07-19T05:33:43.083 回答
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);
}

#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”子句)。

提示/网址赞赏。

于 2010-07-19T23:42:48.487 回答
0

在 Symbian 上,有一些脚本用于检查寄存器和堆栈,以查找看起来像代码地址的东西。

这不是可移植的,但它也不依赖于修饰代码。在字节数很重要的平台上,这是一个必要的权衡……而且它不像 Z80 那样受限!但在没有帧指针等的情况下编译得足够有限。

要从没有帧指针的堆栈计算回溯,您必须向上处理堆栈而不是向下处理。

于 2010-07-17T23:51:27.737 回答