试图理解 alloca() 函数在 x86 上的汇编实现

Trying to understand the Assembly implementation of the alloca() function on x86

我是汇编的新手,我目前正在阅读一本名为初学者逆向工程的书,我读到了关于堆栈内存分配的部分。 我理解(我认为)堆栈分配的概念,但在示例中有一些我不理解的东西,如果这里有人可以提供帮助,我会很高兴。

书中以这个函数为例:

#ifdef __GNUC__
#include <alloca.h> // GCC
#else
#include <malloc.h> // MSVC
#endif
#include <stdio.h>
void f()
{
    char *buf=(char*)alloca (600);
#ifdef __GNUC__
    snprintf (buf, 600, "hi! %d, %d, %d\n", 1, 2, 3); // GCC
#else
    _snprintf (buf, 600, "hi! %d, %d, %d\n", 1, 2, 3); // MSVC
#endif
    puts (buf);
};

我了解 C 函数的作用。它在堆栈上分配 600 字节的内存,然后写入 space 字符串“hi!”使用 _snprintf 函数。然后函数打印出来。

目前一切都很好。之后书中给出了MSVC编译器生成的汇编实现,代码如下:

mov eax, 600 ; 00000258H
call __alloca_probe_16
mov esi, esp
push 3
push 2
push 1
push OFFSET $SG2672
push 600 ; 00000258H
push esi
call __snprintf
push esi
call _puts
add esp, 28 

这里我了解到 EAX 寄存器将包含 __alloca_probe_16 函数的参数。 但现在有些事情对我来说没有意义。据我了解,函数 __alloca_probe_16 基本上只是从 ESP.

中减去 EAX 值中的字节数

因此,例如,如果 ESP 指向 1000,现在它指向 400。然后我们将 400 存储到 ESI 并开始将 _snprintf 的参数压入堆栈,并且 ESI 指向函数需要开始写入数据的位置。 所以我的问题是,如果 ESPESI 寄存器都指向 400 并且我分配了 1000-400(600 字节)的内存,然后我开始将东西压入堆栈,他们不会进入从 400 开始递减的位置?我的意思是,如果不使用它们,为什么要减去 600 字节? 在我看来,这就是 push esi 行之后堆栈的样子。


|          400          |
|          600          |
|    adrr of string     |
|           1           |
|           2           |
|           3           | 400 , ESP , ESI
|                       | ...
|_______________________| 1000

我知道我可能错了并且没有理解正确的东西,因为我认为这本书没有错,如果有人能帮助我理解这段汇编代码中发生的事情,我会很高兴。

so for example if the ESP points to 1000 now it points to 400. then we store the 400 into ESI and start pushing arguments of the _snprintf to the stack and ESI is pointing to the location of where the function needs to start writing data to.

没错。

So my problem is this, if both the ESP and ESI registers point to 400 and I allocated memory from 1000-400 (600 bytes), and I start pushing thing into the stack, won't they go in to the position starting from 400 and decreasing?

是的,他们肯定会的。

I mean , we did I subtracted 600 byte if i am not using them?

您还没有使用 space ,但是您已经提供了它以便 snprintf 有一个地方可以写入字符串产生,并且您将地址 400(在 ESI 中)传递给它以告诉它这样做。当snprintfreturns时,字符串"hi! 1, 2, 3 \n"将从地址400开始存储。

当然,这么短的字符串并不需要 600 个字节;这只是一个例子。如果你愿意,你可以把它变小。