透过汇编学习C语言
1.变量
全局变量和局部变量
全局变量:在声明并编译后,该变量的内存地址和宽度就被确定,如果不重新编译,则在整个程序运行时,该变量的地址就不会被修改,但是,全局变量中的值可以根据修改其对应地址的值而进行修改。(任何程序或函数都可修改他的值)
局部变量:在声明并编译后,程序并不会直接为其分配地址,只有在运行到该局部变量所在的函数被调用时才会被分配地址和宽度,同时当这个函数执行结束后,该局部变量也会被“清空”
实例学习
源码如下:
#include<stdio.h>
int x;
3: void change(int i){
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h]这里是创建缓冲区
0040102C mov ecx,11h//程序提升栈指针而预留一部分内存空间
00401031 mov eax,0CCCCCCCCh///同时将这部分空间以cc字符串进行填充
00401036 rep stos dword ptr [edi]//
4: int y=i;
00401038 mov eax,dword ptr [ebp+8]
0040103B mov dword ptr [ebp-4],eax
5: x=2;
0040103E mov dword ptr [x (00427c48)],2
6: }
00401048 pop edi
00401049 pop esi
0040104A pop ebx
0040104B mov esp,ebp
0040104D pop ebp
0040104E ret
7: int main(){
00401060 push ebp
00401061 mov ebp,esp
00401063 sub esp,40h
00401066 push ebx
00401067 push esi
00401068 push edi
00401069 lea edi,[ebp-40h]
0040106C mov ecx,10h
00401071 mov eax,0CCCCCCCCh
00401076 rep stos dword ptr [edi]
8: change(2); //在此设置断点
00401078 push 2
0040107A call @ILT+0(change) (00401005)
0040107F add esp,4
9: printf("%d\n",x);
00401082 mov eax,[x (00427c48)]
00401087 push eax
00401088 push offset string "%d" (0042201c)
0040108D call printf (004010c0)
00401092 add esp,8
10: return 0;
00401095 xor eax,eax
11: }
开始分析程序(主要从局部变量开始分析)
///从change(2);跳到这里
/*
00401078 push 2 //这里有一个传参,将2压入到堆栈中
0040107A call @ILT+0(change) (00401005)
0040107F add esp,4
*/
void change(int i){
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,44h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-44h] //这里是创建缓冲区
0040102C mov ecx,11h //程序提升栈指针而预留一部分内存空间
00401031 mov eax,0CCCCCCCCh //同时将这部分空间以cc字符串进行填充
00401036 rep stos dword ptr [edi]
4: int y=i;
00401038 mov eax,dword ptr [ebp+8] /这里的ebp+8就是传入的参数‘2’
0040103B mov dword ptr [ebp-4],eax ebp-4所在的内存,是缓冲区,同时也是局部变量的地址,也就是说缓冲区是用来存放局部变量的
5: x=2;
0040103E mov dword ptr [x (00427c48)],2 这里00427c48是一个常见的地址,就是全局变量x的地址,在整个程序中不会改变,但存储的值可修改
6: }
00401048 pop edi
00401049 pop esi
0040104A pop ebx
0040104B mov esp,ebp
0040104D pop ebp
0040104E ret
该程序的堆栈分析图如下:
整段程序分析:(根据工作室的哥哥建议分析写的,对上面的分析有修改)
#include<stdio.h>
int x;
3: void change(int i){
00401020 push ebp //保存main函数的ebp地址,将ebp压入到change函数的堆栈底部
00401021 mov ebp,esp //准备开辟本函数的堆栈,将当前ebp指向当前的esp
00401023 sub esp,44h //开辟44H的堆栈空间,通过提升esp=esp-44H
00401026 push ebx
00401027 push esi
00401028 push edi
保存将使用的寄存器,保存现场,通过将个寄存器的值压入堆栈的形式
00401029 lea edi,[ebp-44h]lea命令就是将传地址到寄存器(其他内存单元也行),和mov指令类似
0040102C mov ecx,11h//预存rep命令的执行次数(由ecx的值指定)
00401031 mov eax,0CCCCCCCCh///预先设定stos命令复制的内容(由eax中储存的内容决定)
///其中0xcc是int 3;的机械码(断点)防止该区域内的内容被执行。
00401036 rep stos dword ptr [edi]//
/*
rep指令的目的是重复其上面的指令.ECX的值是重复的次数.ecx=11h
STOS指令的作用是将eax中的值拷贝到ES:EDI指向的地址.拷贝过后,会对edi中的值做减法操作,减去的值看eax中存储的大小
eax=0CCCCCCCCh;4字节大小,所以每次执行后edi-4
edi=ebp-44h 就是指向缓冲区(也是局部变量的内存空间)
*/
4: int y=i;
00401038 mov eax,dword ptr [ebp+8] //这里eax用来存储变量的值,该变量是change()函数外传入的参数,在change函数的堆栈外
//但也挨着ebp,ebp+8就是传参的变量的内存地址假如还有一个参,那么就是ebp+c
0040103B mov dword ptr [ebp-4],eax //这ebp-4就是y这个局部变量的地址,在缓冲区之内
5: x=2;
0040103E mov dword ptr [x (00427c48)],2
6: }
00401048 pop edi
00401049 pop esi
0040104A pop ebx
0040104B mov esp,ebp
0040104D pop ebp
0040104E ret
//恢复main()函数现场将edi,esi,ebx,esp以及ebp这几个寄存器的值恢复成change()函数执行前的样子
7: int main(){
00401060 push ebp
00401061 mov ebp,esp
00401063 sub esp,40h
00401066 push ebx
00401067 push esi
00401068 push edi
00401069 lea edi,[ebp-40h]
0040106C mov ecx,10h
00401071 mov eax,0CCCCCCCCh
00401076 rep stos dword ptr [edi]
//这部分同样是创建main()函数的堆栈区域
8: change(2); //在此设置断点
00401078 push 2
0040107A call @ILT+0(change) (00401005)
0040107F add esp,4 //栈外平栈
9: printf("%d\n",x);
00401082 mov eax,[x (00427c48)]
00401087 push eax
00401088 push offset string "%d" (0042201c)
0040108D call printf (004010c0)
00401092 add esp,8
10: return 0;
00401095 xor eax,eax
11: }