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

Posted

技术标签:

【中文标题】试图了解 x86 上 alloca() 函数的汇编实现【英文标题】:Trying to understand the Assembly implementation of the alloca() function on x86 【发布时间】:2020-12-05 20:53:45 【问题描述】:

我对汇编很陌生,我目前正在阅读一本名为面向初学者的逆向工程的书,我进入了讨论堆栈上的内存分配的部分。 我理解(我认为)堆栈分配的概念,但在示例中有些东西我不理解,如果有人能提供帮助,我会很高兴。

书中以这个函数为例:

#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 字节的内存,然后将字符串“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

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

【问题讨论】:

是的,这正是堆栈的样子。你认为这张照片有什么问题?你觉得它有什么令人惊讶的地方? 我的问题是如果我不使用它为什么要分配 600 字节? 您将指向这些字节的指针作为snprintf 的第一个参数传递。那是图片顶部的400snprintf 会将输出写入这些字节。 所以 snprintf 将数据写入 [ESI + 600] ,[ESI+601] 等,直到它到达 \0 字节?还是从 400 和 401 402 等开始? snprintf 会将数据写入400401 等,其中400 是它的第一个参数,即调用时位于堆栈顶部的值。但不超过400+600,其中600 是它的第二个参数。 【参考方案1】:

例如,如果 ESP 指向 1000 现在它指向 400。然后我们将 400 存储到 ESI 中并开始将 _snprintf 的参数推送到堆栈,ESI 指向函数需要开始写入的位置数据到。

没错。

所以我的问题是,如果 ESP 和 ESI 寄存器都指向 400,并且我分配了 1000-400(600 字节)的内存,并且我开始将东西推入堆栈,它们会不会进入该位置从 400 开始并减少?

是的,他们肯定会的。

我的意思是,如果我不使用它们,我们是否减去了 600 字节?

你还没有使用那个空间,但是你已经让它可用了,这样snprintf就有了一个地方来写它产生的字符串,你给它传递了地址400(这是在 ESI 中)告诉它这样做。当snprintf返回时,字符串"hi! 1, 2, 3 \n"将从地址400开始存储。

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

【讨论】:

以上是关于试图了解 x86 上 alloca() 函数的汇编实现的主要内容,如果未能解决你的问题,请参考以下文章

x86 汇编器:浮点比较

C alloca 函数 - 当试图分配太多内存时会发生啥

了解汇编、nasm、x86 中的 printf 函数。我不知道为啥这段代码没有打印出任何东西

禁用优化的 c alloca 函数的奇怪汇编代码 - gcc 使用 DIV 和 IMUL 为常数 16,并转换?

C:malloc/calloc/realloc/alloca内存分配函数

alloca() 如何在内存级别上工作?