😊点此到文末惊喜↩︎
概述基础知识存放函数体
编译后的二进制指令,并且是只读的。存放常量
,并且是只读的,如字符串、数字、const修饰的全局变量···存放编译时即可确定存储大小的静态变量和全局变量
,可读可写存储malloc动态分配的内存(new底层也是调用malloc)
,内存由低地址向高地址生长,由程序员进行内存的分配和释放,系统维护开销大,速度比栈慢存储函数调用过程
,由操作系统进行自动的分配和释放存放常量
,并且是只读的,如字符串、数字、const修饰的全局变量···使用局部变量时,尽量进行初始化。
编译器不会初始化局部变量,所以如果使用未初始化的局部变量,内部的值是垃圾值。但是debug调试模式下,运行时机制会将栈空间全部初始化为0mainCRTStartup()
,而main函数由启动码函数中的invoke_main()
调用call 子函数名
,即主函数调用子函数push ebp
,将主函数的栈底指针压入栈中mov ebp esp
,使栈底指针指向栈顶,即构造子函数的栈底mov esp ebp
,用子函数的ebp给esp赋值,将栈顶指针指向栈底pop ebp
将栈顶的上一个函数的ebp值弹出到ebp寄存器中ret
将栈顶的值(主函数的调用断点的下一条指令的地址)弹出到EIP中,EIP会执行下一条指令,即返回主函数。编译环境:vs2022&win10
编译器问题:编译器越高级,其中处理的越会繁琐。在不同的编译器下,函数的调用过程中的栈帧的创建是略有差异的,具体细节取决于编译器的实现
前置知识
32位下反汇编(下面从0开始)
// 被调用的子函数
int sum(int a, int b) {// 5. 构造子函数栈帧
00451740 push ebp //把ebp寄存器的值入栈,此时的ebp中存放的是主调函数的栈基址
00451741 mov ebp,esp// 将当前栈顶指针esp赋值给栈基址寄存器ebp,即现在为子函数栈帧
00451743 sub esp,0CCh// 栈顶指针esp下移(栈由高向低生长),即由esp和ebp共同维护这一段子函数栈帧
00451749 push ebx //将寄存器ebx的值压栈,esp-4
0045174A push esi //将寄存器esi的值压栈,esp-4
0045174B push edi //将寄存器edi的值压栈,esp-4
0045174C lea edi,[ebp-0Ch] //先把ebp-0E4h的地址,放在edi中
// 下三行将ebp指向的内存到值为ebx之间初始化成0CCCCCCCCh
0045174F mov ecx,3
00451754 mov eax,0CCCCCCCCh
00451759 rep stos dword ptr es:[edi]
// 下两行为编译器debug模式下调试用的cookie
0045175B mov ecx,offset _01833B24_TestCpp@cpp (045C008h)
00451760 call @__CheckForDebuggerJustMyCode@4 (045130Ch)
// 6. 执行子函数功能
int c = a + b;
00451765 mov eax,dword ptr [a]
00451768 add eax,dword ptr [b]
0045176B mov dword ptr [c],eax
return c;
// 返回值放入eax中,函数调用返回时不会被覆盖
0045176E mov eax,dword ptr [c]
}
00451771 pop edi //在栈顶弹出一个值,存放到edi中,esp+4
00451772 pop esi //在栈顶弹出一个值,存放到esi中,esp+4
00451773 pop ebx //在栈顶弹出一个值,存放到ebx中,esp+4
// 下三行为debug模式下的cookie
00451774 add esp,0CCh
0045177A cmp ebp,esp
0045177C call __RTC_CheckEsp (0451235h)
// 7. 将栈帧恢复为主函数的栈帧(ebp和esp)
00451781 mov esp,ebp // 将子函数的栈底指针赋值给栈顶指针esp,相当于回收栈,但是子函数栈帧仍然存在栈中
00451783 pop ebp //弹出栈顶的值存放到ebp,栈顶此时的值恰好就是main函数的ebp,esp+4,此时恢复了main函数的栈帧维护,esp指向main函数栈帧的栈顶,ebp指向了main函数栈帧的栈底。
// 8. 返回到主函数的调用点的下一条指令的地址
00451784 ret //ret指令的执行,首先是从栈顶弹出一个值,此时栈顶的值就是call指令下一条指令的地址,此时esp+4,然后直接跳转到call指令下一条指令的地址处,继续往下执行
···
// 主函数
int main() {// 0. 构造主函数栈帧(配合堆栈结构图片看更容易理解)
004517B0 push ebp //把ebp寄存器的值入栈,此时的ebp中存放的是invoke_main栈基址
004517B1 mov ebp,esp // 将当前栈顶指针esp赋值给栈基址寄存器ebp,即现在为mian函数栈帧
004517B3 sub esp,0D8h// 栈顶指针esp下移(栈由高向低生长),即由esp和ebp共同维护这一段mian栈帧
004517B9 push ebx //将寄存器ebx的值压栈,esp-4,这三个应该是main的三个形参变量
004517BA push esi //将寄存器esi的值压栈,esp-4
004517BB push edi //将寄存器edi的值压栈,esp-4
004517BC lea edi,[ebp-18h] //先把ebp-18h的地址,放在edi中
// 下三行将ebp指向的内存到值为ebx之间初始化成0CCCCCCCCh
004517BF mov ecx,6
004517C4 mov eax,0CCCCCCCCh
004517C9 rep stos dword ptr es:[edi]
// 下两行为编译器debug模式下调试用的cookie
004517CB mov ecx,offset _01833B24_TestCpp@cpp (045C008h)
004517D0 call @__CheckForDebuggerJustMyCode@4 (045130Ch)
// 函数功能实现
// 1. 为main函数栈帧中的局部变量赋值(系统啥时候把a和b初始化成main堆栈的?)
int a = 1;
004517D5 mov dword ptr [a],1 // a相当于一个指针,将1赋值到a指向的内存中,实际在mian函数的栈帧中
int b = 2;
004517DC mov dword ptr [b],2 // 同上
sum(a, b);
// 2. 将被调用函数的参数从右向左依次用通用寄存器保存
004517E3 mov eax,dword ptr [b]
004517E6 push eax
004517E7 mov ecx,dword ptr [a]
004517EA push ecx
// 3. call子函数,分成两步,1.将程序下一条指令地址压入栈中2.转移到调用的子函数(最后一行)
004517EB call sum (045116Dh)
004517F0 add esp,8
return 0;
004517F3 xor eax,eax
}
004517F5 pop edi
004517F6 pop esi
004517F7 pop ebx
004517F8 add esp,0D8h
004517FE cmp ebp,esp
00451800 call __RTC_CheckEsp (0451235h)
00451805 mov esp,ebp
00451807 pop ebp
00451808 ret
// 以下是函数表,只是一个中转作用
···
// 4. 执行跳转指令到子函数的执行(第一行)
0045116D jmp sum (0451740h)
···
5. 64位下反汇编,代码没问题,但是我的注释可能有问题,有的地方没理解,等我神功大成
#include// 主函数调用的子函数
int sum(int a, int b) {// 4. 将子函数的参数自右向左依次压入栈中
00007FF7F5631740 mov dword ptr [rsp+10h],edx // 栈由高地址向低地址生长
00007FF7F5631744 mov dword ptr [rsp+8],ecx // 这是低地址
// 5. 压入主调函数的栈基址,即上一个栈帧的开始地址
00007FF7F5631748 push rbp
// 6.将主调函数的函数调用点的下一条指针地址压入栈中
00007FF7F5631749 push rdi
00007FF7F563174A sub rsp,108h
00007FF7F5631751 lea rbp,[rsp+20h]
00007FF7F5631756 lea rcx,[__01833B24_TestCpp@cpp (07FF7F5641008h)]
00007FF7F563175D call __CheckForDebuggerJustMyCode (07FF7F5631343h)
// 7. 执行函数体内的功能语句
int c = a + b;
00007FF7F5631762 mov eax,dword ptr [b]
00007FF7F5631768 mov ecx,dword ptr [a]
00007FF7F563176E add ecx,eax
00007FF7F5631770 mov eax,ecx
00007FF7F5631772 mov dword ptr [c],eax
return c;
00007FF7F5631775 mov eax,dword ptr [c] // 将返回值赋值到eax中
}
00007FF7F5631778 lea rsp,[rbp+0E8h]
// 8. 注意此时栈顶依次为rdi rbp。所以逆序pop到相应的寄存器中
00007FF7F563177F pop rdi
00007FF7F5631780 pop rbp
// 9. ret是子函数返回指令,与call搭配使用,修改pc(存储下一条将要执行的指令地址),并恢复主函数堆栈
00007FF7F5631781 ret
// main函数
int main() {// 0. debug模式下的插入的cookie?
00007FF7F56317A0 push rbp
00007FF7F56317A2 push rdi
00007FF7F56317A3 sub rsp,128h
00007FF7F56317AA lea rbp,[rsp+20h]
00007FF7F56317AF lea rcx,[__01833B24_TestCpp@cpp (07FF7F5641008h)]
00007FF7F56317B6 call __CheckForDebuggerJustMyCode (07FF7F5631343h)
// 1. 分别开辟4个字节大小的双字内存并将值移入
int a = 1;
00007FF7F56317BB mov dword ptr [a],1
int b = 2;
00007FF7F56317C2 mov dword ptr [b],2
// 2. 将参数压入寄存器中,调用子函数
sum(a, b);
00007FF7F56317C9 mov edx,dword ptr [b] // 将内存地址为a的双字类型的数据赋值给edx
00007FF7F56317CC mov ecx,dword ptr [a]
00007FF7F56317CF call sum (07FF7F56313A2h) // 调用子函数(最后一行)
return 0;
00007FF7F56317D4 xor eax,eax
}
00007FF7F56317D6 lea rsp,[rbp+108h]
00007FF7F56317DD pop rdi
00007FF7F56317DE pop rbp
00007FF7F56317DF ret
// 从函数表中截取的子函数跳转指令
// 3. 跳转到子函数
00007FF7F56313A2 jmp sum (07FF7F5631740h) // 第一行
···
栈溢出实验🚩点此跳转到首行↩︎
参考博客你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧