装配中保留的堆栈空间不会被包含在内。似乎与c代码匹配

时间:2014-03-11 20:12:46

标签: c memory-management assembly x86 stack

我有以下代码示例:

int main(int argc, char **argv) {
    char *name[2];
    name[0] = "/bin/sh";
    name[1] = NULL;
    execve(name[0], name, NULL);
    exit(0);
}

反汇编程序会导致类似于:

1.  0x08048250 <main+0>: push %ebp
2.  0x08048251 <main+1>: mov %esp,%ebp
3.  0x08048253 <main+3>: and $0xfffffff0,%esp
4.  0x08048256 <main+6>: sub $0x20,%esp
5.  0x08048259 <main+9>: movl $0x80a6f68,0x18(%esp)
6.  0x08048261 <main+17>: movl $0x0,0x1c(%esp)
7.  0x08048269 <main+25>: mov 0x18(%esp),%eax
8.  0x0804826d <main+29>: movl $0x0,0x8(%esp)
9.  0x08048275 <main+37>: lea 0x18(%esp),%edx
10. 0x08048279 <main+41>: mov %edx,0x4(%esp)
11. 0x0804827d <main+45>: mov %eax,(%esp)
12. 0x08048280 <main+48>: call 0x804f5c0 <execve>
13. 0x08048285 <main+53>: movl $0x0,(%esp)
14. 0x0804828c <main+60>: call 0x8048af0 <exit>

我想了解集会:

在第4行,stackpointer被减少以为局部变量分配空间,但我不明白为什么它保留32个字节(0x20 = 32个字节)?据我所知,它只需要分配:

  • 4个字节用于指针“name”
  • 字符串“/ bin / sh”;
  • 的8个字节
  • NULL是否占用空间?如果是这样,它相当于4个字节的0x0对吗?
  • argc和argv不属于这个堆栈框架吗?它们由电源调用者推动,因此不需要在此堆栈帧中保留。

此外,我看到一些数据存储在堆栈指针的偏移处,但似乎并没有使用所有空间。

有人可以解释这件装配吗?我遇到了将c代码映射到给定汇编指令的麻烦。特别是因为保留空间的长度似乎与c代码不匹配。

我对以下内容特别感兴趣:

  • 第3行:这是为了什么?
  • 第9行:这是做什么的?
  • 第11行和第13行:(%esp)语法是什么意思?

谢谢!

2 个答案:

答案 0 :(得分:3)

关于&#34;它应该只需要分配&#34;:

  • &#34; 4个字节用于指针名称&#34; - 不完全的。 name是一个由两个指针组成的数组,所以2 * 4 = 8个字节(假设您使用的是32位指针,这就是它的样子)。

  • &#34;字符串的8个字节&#34; / bin / sh&#34; - 这不会成为筹码。它将在二进制文件的其他地方(可能是.rodata段,即只读数据),因此不会占用任何堆栈空间。

  • &#34; NULL是否占用空间?&#34; - NULL(可能,除非你有一个反向的C编译器)值为0. A&#34; value&#34;本身不能占用堆栈空间,但如果堆栈上的变量值为NULL,那么您将在堆栈中找到零。

  • &#34; argc和argv [etc]&#34; - 这些可以说是此函数调用的堆栈帧的一部分,但是由调用者初始化,因此不会通过减少%esp来保留。

&#34;似乎没有使用所有空间&#34; - 由于对齐,一般是正确的。考虑:

struct {
    char ch;
    int *ptr;
};

对于这种结构情况,我们将有一个字节的字符,然后是三个字节的填充,以便正确对齐ptr,然后是4个字节的ptr。 (如果指针是64位,则为7个字节的填充。)如果我们在堆栈中分配这些结构中的一个,那么我们将有三个(或7个)&#34;未使用的&#34;堆栈字节。

但是,在这种情况下,编译器在堆栈中为初始&#34; sub ...%esp&#34;中的execve()调用的参数创建空间。指令,这是一个轻微的优化。

第3行:对齐:在16字节边界上对齐%esp。 (应该对任何人都足够了。) 第4行:创建一些堆栈空间。其中一些是局部变量;在汇总函数调用时,编译器的其他一些字节用于临时空间。

第9行:这(有效地)将字符串&#34; / bin / sh&#34;进入%edx。该字符串的地址位于二进制文件中的0x80a6f68位置(将加载.rodata的位置)。第5行将值0x80a6f68置入(%esp + 0x18)。第9行将*(%esp + 0x18),即*(char **)0x80a6f68放入%edx。

第11行:(%esp)实际上是指针deference:* esp = eax。它将eax寄存器的值放入* esp,即堆栈指针正上方的四个字节。 eax的值在第7行定义。

第13行:这将* esp设置为0x00000000。因为我们然后调用exit(),这会将exit的第一个参数(即* esp)设置为0.

答案 1 :(得分:2)

name是一个包含2个指针的数组。这将需要8或16个字节,具体取决于您的平台是32位还是64位。

字符串/bin/sh根本不在堆上,而是在初始化的数据段中;为了完整性,\0确实需要空间。

argcargv将作为参数传递到堆栈上。

第3行将堆栈指针对齐为16字节的倍数

有效地说,第19行是edx为0x18加上esp&#39;的值; lea已加载有效地址&#39 ;;把它想象成mov而不是内存加载它会返回它从内存中加载的地址。

在第11行和第13行,(esp)表示esp指向的位置内容。

我会承认我的装配工有点生锈。