试图了解主要拆卸的第一条指令

您好我已经拆卸了一些程序(Linux),我写了更好地了解它是如何工作的,我注意到,主要function始终始于:

lea ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of the main...why ? and esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ??? push DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ? push ebp mov ebp,esp push ecx ;why is ecx pushed too ?? 

所以我的问题是:为什么所有这些工作都完成了? 我只理解使用:

 push ebp mov ebp,esp 

剩下的对我来说似乎是无用的…

Solutions Collecting From Web of "试图了解主要拆卸的第一条指令"

我已经去了一下:

 ;# As you have already noticed, the compiler wants to align the stack ;# pointer on a 16 byte boundary before it pushes anything. That's ;# because certain instructions' memory access needs to be aligned ;# that way. ;# So in order to first save the original offset of esp (+4), it ;# executes the first instruction: lea ecx,[esp+0x4] ;# Now alignment can happen. Without the previous insn the next one ;# would have made the original esp unrecoverable: and esp,0xfffffff0 ;# Next it pushes the return addresss and creates a stack frame. I ;# assume it now wants to make the stack look like a normal ;# subroutine call: push DWORD PTR [ecx-0x4] push ebp mov ebp,esp ;# Remember that ecx is still the only value that can restore the ;# original esp. Since ecx may be garbled by any subroutine calls, ;# it has to save it somewhere: push ecx 

这样做是为了保持堆栈对齐到一个16字节的边界。 某些指令要求某些数据类型在16个字节的边界上对齐。 为了满足这个要求,GCC确保堆栈最初是16字节对齐的,并且以16字节的倍数分配堆栈空间。 这可以使用选项-mpreferred-stack-boundary = num进行控制 。 如果使用-mpreferred-stack-boundary = 2(对于2 2 = 4字节对齐),则不会生成此对齐码,因为堆栈始终至少与4字节对齐。 但是,如果程序使用任何需要更强对齐的数据类型,则可能会遇到麻烦。

根据gcc手册:

在Pentium和PentiumPro上,double和long double值应该对齐到一个8字节的边界(参见-malign-double),否则将遭受显着的运行时间性能损失。 在Pentium III上,如果不是16字节对齐,则数据流SIMD扩展(SSE)数据类型__m128可能无法正常工作。

为了确保在堆栈上正确对齐这个值,堆栈边界必须与存储在堆栈上的任何值所要求的堆栈边界一致。 此外,必须生成每个函数,以保持堆栈对齐。 因此,调用一个用优先级较低的堆栈边界编译的函数编译的函数将会使堆栈错位。 建议使用回调的库始终使用默认设置。

这额外的对齐消耗额外的堆栈空间,并且通常增加代码大小。 对栈空间使用敏感的代码(例如嵌入式系统和操作系统内核)可能希望将首选对齐方式减少到-mpreferred-stack-boundary = 2。

lea将原始堆栈指针(从调用main之前)加载到ecx ,因为堆栈指针将被修改。 这是用于两个目的:

  1. 访问main函数的参数,因为它们是相对于原始堆栈指针的
  2. main返回时将堆栈指针恢复到原来的值
 lea ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of the main...why ? and esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ??? push DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ? push ebp mov ebp,esp push ecx ;why is ecx pushed too ?? 

即使每条指令都完美无缺地工作,尽管任意对齐操作数,对齐仍然会提高性能。 设想一个引用16字节数量的循环,它只是重叠两个缓存行。 现在,要将这个小的wchar加载到缓存中,必须将两个完整的缓存行驱逐出来,如果你在同一个循环中需要它们呢? 高速缓存比RAM快得多,高速缓存的性能总是至关重要的。

而且,将未对齐的操作数转移到寄存器中通常会有速度惩罚。 鉴于堆栈正在重新排列,我们自然必须保存旧的对齐,以便遍历参数的堆栈帧并返回。

ecx是一个临时的注册表,所以它必须被保存。 另外,根据优化级别的不同,一些框架链接操作似乎并不是运行程序所必需的,这对于设置一个跟踪就绪的框架链很重要。