一种将跟踪和回溯纳入 C 程序的简单、统一且可移植的方法

发布于 2024-09-10 07:53:37 字数 1208 浏览 3 评论 0原文

GNU libc 的回溯在线仿真器/调试器在将代码移植到新平台时并不总是可用,特别是当目标是微型< a href="http://en.wikipedia.org/wiki/C_%28programming_language%29" rel="nofollow noreferrer">C 编译器,例如用于 Z80。 (通常,程序错误会“挂在”某处,或者使小工具崩溃。)

是否有经典的“狼击剑”手动插入printf的方法?编码员在开发包含对 C 程序进行跟踪和回溯的程序时可以做一些简单且可移植的事情(不使用 C 扩展)?

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

GNU libc's backtrace and In-circuit emulators/debuggers are not always available when porting code to a new platform, especially when the target is a micro C compiler such as for the Z80. (Typically a program bug would "just hang" somewhere, or crash the gadget.)

Is there an alternative to the classic "wolf fencing" method of manually inserting printf? Something simple and portable (using no C extensions) that a coder can do while developing a program that includes tracing and backtracing into a C program?

BTW: Here are a couple of other question on stackoverflow that are related, but these both use GNU GLIBC's backtrace and backtrace is often compiler/implementation specific:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

瞎闹 2024-09-17 07:53:37

这是我的答案的内核:编写一些代码。

我的答案的核心是:如果你的编译器总是在堆栈上分配局部变量,那么...

在记录函数名称的每个函数入口处将 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 代码。

省略一些细节,让您享受其中的乐趣...

另请参阅 任何uclibc 的 backtrace 是否可以移植?

Here is the kernel of the kernel of my answer: write some code.

The kernel of my answer is: If your compiler allocates locals on the stack always, then...

Add blobs to the stack at every function entry that record the name of the function, throw in some magic numbers to maybe catch stack smashes.

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;

Create a macro ENTER(f) taking the name of the function. The macro should be about the first line of code in every function after the opening {. It adds a struct with a pointer to the (const) char * function name, a pointer to the previous struct on the stack, and maybe some magic numbers to check sanity. Make the top of blob stack pointer point at this new struct.

#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))

To keep things as portable as possible, all ENTER can do is declare and initialize variables. Hence the evil_hack to do a little extra computation than just initializing a variable.

Create a function to walk down the list of blobs checking pointers and magic numbers. It should signal an error (maybe print to stderr, maybe lockup the cpu with while (1) { /* nada */ }, maybe enter the debugger... depends on your hardware) if it finds things messed up.

Create a macro EXIT() that checks your stack of blobs, then de-links the topmost from the linked list. It needs to be put at the exit points of all your functions.

#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)

Probably will also need to replace all return's with RETURN macro calls, the RETURN macro is just like EXIT, but has a return before the } while (0).

Create a function to walk down the list of blobs printing out the function names, call it something like stacktrace or backtrace maybe.

Write a program to instrument your C code with calls to ENTER(f) and EXIT() and RETURN(x).

Left out a few details to let you have fun with it...

See also Any porting available of backtrace for uclibc?

猥︴琐丶欲为 2024-09-17 07:53:37

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”,来自 RosettaCode.org 然后在需要的地方插入 BEGIN(f)/END:

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

提示/网址表示赞赏。

There is an implementation at RosettaCode.org which uses the same basic idea as @jsl4tv's suggestion.

Example, given the following classic C code with built in "hang":

#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 and #include "stack_trace.h" from RosettaCode.org then insert BEGIN(f)/ENDs where required:

#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

Produces:

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) ---

A well polished [open source] version of this BEGIN ~ END method would be perfect. (Esp if it has a "FINALLY" clause for exception handling).

Hints/URLs appreciated.

薆情海 2024-09-17 07:53:37

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

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

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

on Symbian there were some scripts made to go over the registers and stack looking for things that looked like code addresses.

This is not portable, but it doesn't depend on decorating the code either. This was a necessary tradeoff on a platform where byte counts mattered... and it wasn't nearly as limited as Z80! But limited enough to compile without frame-pointers and such.

To calculate a backtrace from a stack without frame-pointers you have to work up the stack not down it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文