试图了解 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
指向的位置该函数需要开始写入数据。
所以我的问题是,如果ESP
和ESI
寄存器都指向400,并且我分配了1000-400(600字节)的内存,并且我开始将东西压入堆栈,它们不会进入位置从 400 开始并减少?我的意思是,如果不使用它们,为什么要减去 600 字节?
在我看来,这就是堆栈在push esi
行之后的样子。
| 400 |
| 600 |
| adrr of string |
| 1 |
| 2 |
| 3 | 400 , ESP , ESI
| | ...
|_______________________| 1000
我知道我可能是错的并且没有理解正确的东西,因为我认为这本书没有错,如果有人能帮助我理解这段汇编代码中发生的事情,我会很高兴。
【问题讨论】:
是的,这正是堆栈的样子。你认为这张照片有什么问题?你觉得它有什么令人惊讶的地方? 我的问题是如果我不使用它为什么要分配 600 字节? 您将指向这些字节的指针作为snprintf
的第一个参数传递。那是图片顶部的400
。 snprintf
会将输出写入这些字节。
所以 snprintf 将数据写入 [ESI + 600] ,[ESI+601] 等,直到它到达 \0 字节?还是从 400 和 401 402 等开始?
snprintf
会将数据写入400
、401
等,其中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() 函数的汇编实现的主要内容,如果未能解决你的问题,请参考以下文章
了解汇编、nasm、x86 中的 printf 函数。我不知道为啥这段代码没有打印出任何东西
禁用优化的 c alloca 函数的奇怪汇编代码 - gcc 使用 DIV 和 IMUL 为常数 16,并转换?