1.c语言转汇编
现在有一段c语言代码。
void print1() {
int a = 0x10;
if (a != 0) {
a = 0x100;
}
a = 0x1000;
}
int main()
{
int b = 0x200;
print1();
b = 0x300;
}
在main函数中调用了print1函数。 查看print1函数的汇编可以看到。
void print1() {
push ebp ; 函数开头,保存当前函数的栈帧指针
mov ebp, esp ; 设置当前栈帧指针为栈顶指针
sub esp, 0CCh ; 在栈上分配 204 bytes 的空间(用于局部变量和临时存储)
push ebx ; 保存寄存器 ebx,因为它是被调用者保存的寄存器
push esi ; 保存寄存器 esi,因为它是被调用者保存的寄存器
push edi ; 保存寄存器 edi,因为它是被调用者保存的寄存器
lea edi, [ebp-0Ch] ; 将 edi 设置为局部变量 a 的内存地址
mov ecx, 3 ; 将 ecx 设置为需要填充的字节数(12 bytes,即 3 个 int)
mov eax, 0CCCCCCCCh ; 将 eax 设置为用于填充的特殊值(通常用于调试)
rep stos dword ptr es:[edi] ; 用 eax 的值填充 edi 指向的 12 bytes 内存(初始化局部变量 a)
; 下面是一些额外的代码,用于调试和检查调试器
mov ecx, offset _7535E707_ConsoleApplication3@cpp ; 将 ecx 设置为某个地址
call @__CheckForDebuggerJustMyCode@4 ; 调用某个函数
; 以下是原始 C 代码的实际汇编代码
mov dword ptr [a], 10h ; 将 a 的值设置为 0x10
cmp dword ptr [a], 0 ; 比较 a 的值是否等于 0
je __$EncStackInitStart+2Dh ; 如果 a 等于 0,跳转到 __$EncStackInitStart+2Dh(未显示完整代码)
mov dword ptr [a], 100h ; 如果 a 不等于 0,将 a 的值设置为 0x100
mov dword ptr [a], 1000h ; 将 a 的值设置为 0x1000
; 函数结尾
}
2.开辟栈针
前三行指令会初始化栈针 esp ebp。并分配空间。
High Address
+------------------------+
| Previous Stack Frames |
+------------------------+
| Unused | <-- Higher address, padding if needed
+------------------------+
| Local Variables & |
| Temporary Data |
| (204 bytes) |
+------------------------+
| Saved Registers | <-- Lower address, such as ebx, esi, edi (if saved)
+------------------------+
| Return Address | <-- Lowest address, points to the instruction after the function call
Low Address
3.接下来会保存寄存器和设置栈空间
4.然后会设置局部变量
现在的内存分布如下。
High Address
+-------------------+
| Saved edi | <-- Restored value of edi (popped from the stack)
+-------------------+
| Saved esi | <-- Restored value of esi (popped from the stack)
+-------------------+
| Saved ebx | <-- Restored value of ebx (popped from the stack)
+-------------------+
| Local Variables | <-- Start of local variables and temporary data (allocated space)
| a (4 bytes) | <-- Space for 'int a' (local variable)
| b (4 bytes) | <-- Space for 'int b' (local variable)
| |
| |
| Temporary Data |
| | <-- End of local variables and temporary data
+-------------------+
| Saved Base Ptr | <-- Restored value of ebp (popped from the stack)
+-------------------+
| Return Address | <-- Return address (address of the instruction after the function call)
Low Address
5.执行业务代码
业务代码是函数中实现具体功能的部分,通常不会直接涉及栈的显式操作。
6.函数结束恢复现场
- 恢复被调用者保存的寄存器值,即从栈上弹出之前保存的寄存器值。
- 清理栈空间,即将栈顶指针(esp)恢复到函数调用前的位置,销毁当前函数的栈帧。
- 恢复上一级函数的栈帧指针(ebp)。
总结
执行完业务代码后,需要按照相反的顺序进行操作,以恢复之前保存的寄存器和清理栈空间。回顾整个过程:
-
函数开头保存现场:
- 将被调用者保存的寄存器值(例如:ebx、esi、edi等)保存到栈上。
- 设置当前栈帧指针(ebp)为栈顶指针(esp)。
-
开辟栈空间分配局部变量和临时存储空间。
-
执行业务代码(对局部变量和临时数据进行计算等)。
-
函数结束时恢复现场:
- 恢复被调用者保存的寄存器值,即从栈上弹出之前保存的寄存器值。
- 清理栈空间,即将栈顶指针(esp)恢复到函数调用前的位置,销毁当前函数的栈帧。
- 恢复上一级函数的栈帧指针(ebp)。
-
执行 ret 指令:从当前函数返回到调用者,并跳转到返回地址。
整个过程确保了函数执行期间的寄存器值和栈的正确管理。函数调用结束后,调用者的现场得到正确恢复,不受被调用函数的影响。