帮助理解C Stack

时间:2009-10-03 10:49:43

标签: c memory-management low-level

我正在尝试理解C中的低级内存管理器,特别是Stack。据我所知,当一个函数被调用时,一个返回地址被推入堆栈。然后是局部变量。

所以我写了一个小程序来调查这个。这是我的计划:

#include <stdio.h>

void TestStack();

void DoTestStack() {
    char x1 = 1;
    char x2 = 2;
    char x3 = 3;
    char x4 = 4;
    char *x = &x4;

    printf("TestStack: %08X\n", (&TestStack));
    printf("\n");

    int i;
    x = &x4;
    for(i = 0; i < 32; i++)
        printf("%02d: %08X : %08X\n", i, *(x + i), *(x - i));

    printf("\n");
    printf("x1: %02X\n", x1);
    printf("x2: %02X\n", x2);
    printf("x3: %02X\n", x3);

    printf("DONE!!!\n");
}

void TestStack() {
    DoTestStack();
}

void main() {
    TestStack() ;
}

基本上,它调查位置x4之前和之后的所有内存。这应该很好地覆盖了返回地址的位置。

但我似乎找不到任何类似于返回地址的字节。

这是我的结果:

TestStack: 08048B49

00: 00000004 : 00000004
01: 00000003 : FFFFFFBF
02: 00000002 : FFFFFFAC
03: 00000001 : FFFFFFED
04: 00000004 : 0000001C
05: FFFFFFC3 : 00000000
06: FFFFFFB9 : 00000000
07: 00000000 : 00000000
08: FFFFFFF4 : 00000008
09: FFFFFFBF : 00000000
10: FFFFFFB9 : FFFFFF90
11: 00000000 : FFFFFFBD
12: 00000038 : 00000020
13: FFFFFFED : 00000000
14: FFFFFFAC : 00000000
15: FFFFFFBF : 00000000
16: 00000054 : 00000000
17: FFFFFF8B : 00000000
18: 00000004 : FFFFFFFF
19: 00000008 : 00000000
20: 00000045 : 00000008
21: 00000073 : 00000000
22: FFFFFFA7 : 00000000
23: 00000000 : 00000000
24: 00000020 : 00000017
25: FFFFFFBD : 00000008
26: FFFFFF90 : 00000004
27: 00000000 : FFFFFF8C
28: 00000048 : FFFFFFCF
29: FFFFFFED : 00000008
30: FFFFFFAC : 00000004
31: FFFFFFBF : FFFFFF8A

x1: 01
x2: 02
x3: 03
DONE!!!

我在这里缺少什么?有人可以解释一下。

无论如何我都在使用Ubuntu 9.10。

提前致谢。 :-D

3 个答案:

答案 0 :(得分:6)

你正在查看各个字符,然后将它们转换为32位整数,这让你感到困惑。返回地址位于这四行中最不重要的字节中:

16: 00000054 : 00000000
17: FFFFFF8B : 00000000
18: 00000004 : FFFFFFFF
19: 00000008 : 00000000

即。您的返回地址是0x08048b54。

请改为尝试:

uint32_t *x;
x = (uint32_t *)&x4;
for(i = 0; i < 32; i++)
    printf("%02d: %08X : %08X\n", i, *(x + i), *(x - i));

答案 1 :(得分:4)

在这一行:

    printf("%02d: %08X : %08X\n", i, *(x + i), *(x - i));

x指针是一个字符指针,在解除引用后会被提升为带符号扩展名的整数,这会导致所有输出值为000000xx或FFFFFFxx,具体取决于第7位的值。

相反,您可能想要做的是使用int指针来扫描堆栈值,而不是char指针。

答案 2 :(得分:1)

如果你想知道这一切是如何工作的一个好主意是在调试器中运行你的应用程序。它真的很糟糕,可以找出发生了什么。

任何调试器都没问题,但是我知道windbg最好所以这里有一些关于从代码的略微修改版本开始的地方的一些指示,比如Greg建议,你需要一个int *。

CHANGE:

 char *x = &x4;

TO:

 int *x = &i;

DELETE:

 x = &x4;

MOVE(第一个变量,在x1之前):

 int i;

NEW CHANGE(阅读很多,以及之前做的(x-i)是随机的/当前没有范围内的值):

printf("%02d: %08x : %08x\n", i, x + i, *(x + i));   

此更改将有效地显示堆栈帧地址和值。

在windbg中,获取调用,内存,线程和命令窗口,以便您可以看到它们。 使用编译器编译C代码,我使用MSVC(您可以免费试用它),使用“cl / Zi your.c”编译“Visual Studio 20 ## Command Prompt”。加载Windbg(适用于Windows的调试工具),按Ctrl + E或使用“打开可执行文件”。

加载以下窗口,以便您可以一次查看所有窗口,呼叫,本地,内存,线程和命令。

在命令窗口,使用“bu DoTestStack”在DoTestStack上放置一个断点。

使用“g”命令开始调试。

到达断点后,使用“p”进行单步操作,您也应该弹出源代码,或者您可以在for循环中运行后查看应用程序的输出,然后返回到Windbg。获取内存窗口并将其设置为“指针和符号”以获取“i”中的地址类型,它应该具有来自编译(/ Zi)的调试信息,并将为您提供“指针和符号”列表指向i。

地址的指针

输出应该与测试代码相同(在你做出我建议的更改之后),如果你继续点击p,你会看到在内存窗口中你可以观察到每次执行更改的值,不过由于printf现在正在打印其他值,您只需在输出的原始“0”处看到它;)。

你可以选择使用windbg命令 dds (注意i的值是0xb,因为它在for循环的中间)

0:000> dds i
0018ff24  0000000b
0018ff28  02586bf9
0018ff2c  0018ff24
0018ff30  0018ff38
0018ff34  00401118 a!TestStack+0x8 [c:\temp\a.c @ 33]
0018ff38  0018ff40
0018ff3c  00401128 a!main+0x8 [c:\temp\a.c @ 35]
0018ff40  0018ff88
0018ff44  00401435 a!__tmainCRTStartup+0xf8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crt0.c @ 257]
0018ff48  00000001
0018ff4c  003c1e48

这与修改后的测试代码相同(不作为符号支持);

00: 0018ff24 : 00000000
01: 0018ff28 : 02586bf9
02: 0018ff2c : 0018ff24
03: 0018ff30 : 0018ff38
04: 0018ff34 : 00401118
05: 0018ff38 : 0018ff40
06: 0018ff3c : 00401128
07: 0018ff40 : 0018ff88
08: 0018ff44 : 00401435
09: 0018ff48 : 00000001
10: 0018ff4c : 003c1e48