我的问题是
为什么这3个变量地址在分配时不连续?观看像
add esp, N
这样的子句,通常在例行程序的结尾?它与调用约定有关吗?在此示例中,为什么编译器不生成
add esp, 20h
? /> asm #include <stdio.h>
int main() {
int x;
printf ("Enter X:\n");
scanf ("%d", &x);
printf ("You entered %d...\n", x);
return 0;
};
#1 楼
您的函数中实际上只有一个局部变量:x。该变量位于您期望的堆栈上,位于ebp-4
。 IDA感到困惑,因为该特定函数不是在调用函数之前将变量推入堆栈,而是在移动它们。当它们实际上只是堆栈顶部的位置时,这会欺骗IDA认为它们是局部变量。这样做,但是我的猜测是您编译时没有优化。这种指令布局可能使调试更容易。
我认为您还将调用约定与局部变量清除混淆了。每个函数都需要清理自己的局部变量区域。您的main()函数正在执行
leave
指令。调用约定与清理传递给函数的参数有关。#2 楼
由于使用了请假指令(LEAVE PROCDURE)引用了ftom intel指令手册
,所以没有添加esp,20我想这是您的问题的另一部分,因为编译器未生成任何推入参数指令,而是利用顶部将args移入堆栈,而底部利用varargs存储。
#3 楼
在此函数中,实际上只有一个堆栈变量:var_4
在上面的反汇编中,IDA错误地将传递给
_puts()
,___isoc99_scanf()
和_printf()
的参数检测为局部堆栈变量。要看到这一点,让我们分析以下代码段:mov [esp+20h+var_20], offset aEnterX ; "Enter X:"
call _puts
var_20
在此函数的开头由IDA定义为-20h
,因此mov [esp+20h+var_20], offset aEnterX
的意思是mov [esp+20h+-20h], offset aEnterX
与mov [esp], offset aEnterX
相同。换句话说,代码只是在调用offset aEnterX
之前将_puts()
压入堆栈,而IDA不幸地将“替代压入”检测为本地堆栈变量。#4 楼
(我会更多地处理由Visual C ++生成的代码,因此该答案的某些部分只是有根据的猜测。)1.为什么这三个变量地址在分配时不连续? br />
变量的对齐方式,甚至在堆栈上的存在取决于编译器,并且可以根据您使用的特定编译器版本,使用的优化选项和其他因素而有所不同。
示例中的3个变量并不都是“真实”变量。
var_4
是与x
对应的变量,而var_1C
和var_20
只是GCC传递参数到更深层函数调用的方法的结果。当您编写scanf("%d", &x);
时,GCC知道它将需要将两个4字节变量传递给堆栈上的该函数,因此在进入该函数时,它会抢先为它们保留足够的空间。这样,它不需要将任何东西push
放入栈中(如果没有剩余的栈空间,这可能是有问题的...),它只需要mov
到该预分配空间的参数即可。但是,这并不能解释为什么两个分配之间存在差距。 GCC还喜欢将堆栈分配对齐为16个字节1,这就是我猜测“真正的局部变量”和“为更深的函数参数保留的空间”所需的大小在总和为的最终值之前独立对齐的地方“保留的堆栈空间”。1您可以使用
-mpreferred-stack-boundary=num
来控制此对齐方式。通过观察像add esp, N
这样的子句来堆栈,该子句通常位于例程的结尾?它与调用约定有关吗?如您在本示例中看到的那样,该指令并不总是生成。其对应的
sub esp, N
是一个更好的指标。由此您可以对局部变量的数量/大小进行有根据的猜测。调用约定与函数的局部变量无关,它控制参数传递给函数的方式以及其后清理参数的职责。
3.在此示例为什么编译器不生成
add esp, 20h
?示例中的函数以
push ebp; mov ebp, esp
开头,它保存了ebp
和esp
的原始值。最后的leave
指令执行相反的操作-它恢复了esp
和ebp
的保存值,因此无需计算任何内容。保存的
ebp
也称为帧指针。可以指示编译器不要生成它,在这种情况下,需要使用您提到的计算来恢复esp
的原始值。