C 函数回溯只返回程序的一系列函数调用,但我想列出程序中的所有局部变量,就像 gdb 中的信息局部变量一样。知道这是否可以做到吗?谢谢
3 回答
一般来说,没有。您应该摆脱将“堆栈”视为某种上帝给定的事实的想法。调用堆栈只是 C 的一种常见实现技术。它没有内在含义或必需的语义。自动变量(如您所说的“局部变量”)必须以某种方式运行,有时这意味着它们被写入调用堆栈。但是,完全可以想象,局部变量根本不会在内存中实现——它们可能只会存储在处理器寄存器中,或者如果可以在没有它们的情况下制定等效程序,则完全消除它们。
所以,不,没有用于枚举局部变量的语言内在机制。正如您所说,调试器可以在某种程度上这样做(取决于存在的调试符号并进行优化);也许您可以找到一个可以从正在运行的程序中处理调试符号的库。
如果这只是为了偶尔调试,那么您可以调用调试器。但是,由于调试器本身会冻结您的程序,因此您需要一个中介来捕获输出。例如,您可以使用system
并将输出重定向到文件,然后再读取该文件。在下面的示例中,该文件gdbcmds.txt
包含行info locals
.
char buf[512];
FILE *gdb;
snprintf(buf, sizeof(buf), "gdb -batch -x gdbcmds.txt -p %d > gdbout.txt",
(int)getpid());
system(buf);
gdb = fopen("gdbout.txt", "r");
while (fgets(buf, sizeof(buf), gdb) != 0) {
printf("%s", buf);
}
fclose(gdb);
首先,请注意它backtrace
不是标准的 C 库函数,而是 GNU 特定的扩展。
一般来说,很难从编译后的代码中检索局部变量信息,特别是如果它是在没有调试或启用优化的情况下编译的。如果未打开调试,变量名称和类型通常不会保留在生成的机器代码中。
例如,使用以下简单得离谱的代码:
#include <stdio.h>
#include <math.h>
int main(void)
{
int x = 1, y = 2, z;
z = 2 * y - x;
printf("x = %d, y = %d, z = %d\n", x, y, z);
return 0;
}
这是生成的机器代码,没有调试或优化:
.file "varinfo.c"
.version "01.01"
gcc2_compiled.:
.section .rodata
.LC0:
.string "x = %d, y = %d, z = %d\n"
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $1, -4(%ebp)
movl $2, -8(%ebp)
movl -8(%ebp), %eax
movl %eax, %eax
sall $1, %eax
subl -4(%ebp), %eax
movl %eax, -12(%ebp)
pushl -12(%ebp)
pushl -8(%ebp)
pushl -4(%ebp)
pushl $.LC0
call printf
addl $16, %esp
movl $0, %eax
leave
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.2 2.96-112.7.2)"
x
、y
和分别通过、和z
引用。除了用于执行算术的指令外,没有任何迹象表明它们是整数。 -4(%ebp)
-8(%ebp)
-12(%ebp)
打开优化(-O1)会更好:
.file "varinfo.c"
.version "01.01"
gcc2_compiled.:
.section .rodata.str1.1,"ams",@progbits,1
.LC0:
.string "x = %d, y = %d, z = %d\n"
.text
.align 4
.globl main
.type main,@function
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
pushl $3
pushl $2
pushl $1
pushl $.LC0
call printf
movl $0, %eax
leave
ret
.Lfe1:
.size main,.Lfe1-main
.ident "GCC: (GNU) 2.96 20000731 (Red Hat Linux 7.2 2.96-112.7.2)"
在这种情况下,编译器能够进行一些静态分析并z
在编译时计算值;根本不需要为任何变量留出任何内存,因为编译器已经知道这些值必须是什么。