高级语言程序的汇编解析在高级语言中,如C和PASCAL等等,我们不再直接对硬件资源进行操作,而是面向于 问题的解决,这主要体现在数据抽象化和程序的结构化例如我们用变量名来存取数据,而 不再关心这个数据究竟在内存的什么地方这样,对硬件资源的使用方式完全交给了编译器 去处理不过,一些基本的规则还是存在的,而且大多数编译器都遵循一些规范,这使得我 们在阅读反汇编代码的时候日子好过一点这里主要讲讲汇编代码中一些和高级语言对应的 地方1. 普通变量通常声明的变量是存放在内存中的编译器把变量名和一个内存地址联 系起来(这里要注意的是,所谓的“确定的地址”是对编译器而言在编译阶段算出的一个临 时的地址在连接成可执行文件并加载到内存中执行的时候要进行重定位等一系列调整,才 生成一个实时的内存地址,不过这并不影响程序的逻辑,所以先不必太在意这些细节,只要 知道所有的函数名字和变量名字都对应一个内存的地址就行了),所以变量名在汇编代码中 就表现为一个有效地址,就是放在方括号中的操作数例如,在C文件中声明:int my_age;这个整型的变量就存在一个特定的内存位置语句my_age= 32;在反汇编代码中可能 表现为:mov word ptr [007E85DA], 20所以在方括号中的有效地址对应的是变量名。
又如:char my_name[11] = 〃lianzi2000〃;这样的说明也确定了一个地址,对应于my_name.假设地址是007E85DC,则内存中[007 E85DC]=T',[007E85DD]='i', etc.对my_name的访问也就是对这地址处的数据访问指针变量其本身也同样对应一个地址,因为它本身也是一个变量如:char *your_name;这时也确定变量〃your_name〃对应一个内存地址,假设为007E85F0.语句your_name=m y_name;很可能表现为:mov [007E85F0], 007E85DC ;your_name 的内容是 my_name 的地址2. 寄存器变量在C和C++中允许说明寄存器变量.register int i;指明i是寄存器存放的整型变量通常,编译器都把寄存器变量放在esi和edi中寄存器是在cpu内部的结构,对它的访问 要比内存快得多,所以把频繁使用的变量放在寄存器中可以提高程序执行速度3. 数组不管是多少维的数组,在内存中总是把所有的元素都连续存放,所以在内存中总是一维 的例如,int i_array[2][3];在内存确定了一个地址,从该地址开始的12个字节用来存 贮该数组的元素。
所以变量名i_array对应着该数组的起始地址,也即是指向数组的第一个 元素存放的顺序一般是 i_array[0][0],[0][1],[0][2],[1][0],[1][1],[1][2]即最右边 的下标变化最快当需要访问某个元素时,程序就会从多维索引值换算成一维索引,如访问 i_array[1][1],换算成内存中的一维索引值就是1*3+1=4.这种换算可能在编译的时候就可 以确定,也可能要到运行时才可以确定无论如何,如果我们把i_array对应的地址装入一 个通用寄存器作为基址,则对数组元素的访问就是一个计算有效地址的问题:;i_array[1][1]=0x16lea ebx,xxxxxxxx ;i_array 对应的地址装入 ebxmov edx,04 ;访问i_array[1][1],编译时就已经确定mov word ptr [ebx+edx*2], 16 ;当然,取决于不同的编译器和程序上下文,具体实现可能不同,但这种基本的形式是确 定的从这里也可以看到比例因子的作用(还记得比例因子的取值为1,2, 4或8吗?), 因为在目前的系统中简单变量总是占据1,2,4或者8个字节的长度,所以比例因子的存在为 在内存中的查表操作提供了极大方便。
4. 结构和对象结构和对象的成员在内存中也都连续存放,但有时为了在字边界或双字边界对齐,可能 有些微调整,所以要确定对象的大小应该用sizeof操作符而不应该把成员的大小相加来计 算当我们声明一个结构变量或初始化一个对象时,这个结构变量和对象的名字也对应一个 内存地址举例说明:struct tag_info_struct{int age;int sex;float height;float weight;} marry;变量marry就对应一个内存地址在这个地址开始,有足够多的字节(sizeof(marry)) 容纳所有的成员每一个成员则对应一个相对于这个地址的偏移量这里假设此结构中所有 的成员都连续存放,则age的相对地址为0,sex为2, height为4,weight为8marry.sex=0;lea ebx,xxxxxxxx ;marry对应的内存地址mov word ptr [ebx+2], 0对象的情况基本相同注意成员函数具体的实现在代码段中,在对象中存放的是一个指 向该函数的指针5. 函数调用一个函数在被定义时,也确定一个内存地址对应于函数名字如:long comb(int m, int n){long temp;return temp;}这样,函数comb就对应一个内存地址。
对它的调用表现为:CALL xxxxxxxx ;comb对应的地址这个函数需要两个整型参数,就通过堆栈来传递:;lresult=comb(2,3);push 3push 2call xxxxxxxxmov dword ptr [yyyyyyyy], eax ;yyyyyyyy 是长整型变量lresult的地址这里请注意两点第一,在C语言中,参数的压栈顺序是和参数顺序相反的,即后面的 参数先压栈,所以先执行push 3.第二,在我们讨论的32位系统中,如果不指明参数类型, 缺省的情况就是压入32位双字因此,两个push指令总共压入了两个双字,即8个字节的 数据然后执行call指令call指令又把返回地址,即下一条指令(mov dword ptr....) 的32位地址压入,然后跳转到xxxxxxxx去执行在comb子程序入口处(xxxxxxxx),堆栈的状态是这样的:03000000 (请回忆 small endian 格式)02000000yyyyyyyy <--ESP指向返回地址前面讲过,子程序的标准起始代码是这样的:push ebp ;保存原先的ebpmov ebp, esp;建立框架指针sub esp, XXX;给临时变量预留空间执行push ebp之后,堆栈如下:0300000002000000yyyyyyyyold ebp < esp指向原来的ebp执行mov ebp,esp之后,ebp和esp都指向原来的ebp.然后sub esp, xxx给临时变 量留空间。
这里,只有一个临时变量temp,是一个长整数,需要4个字节,所以xxx=4这 样就建立了这个子程序的框架:0300000002000000yyyyyyyyold ebp <-----当前ebp指向这里temp所以子程序可以用[ebp+8]取得第一参数(m),用[ebp+C]来取得第二参数(n),以此类推 临时变量则都在ebp下面,如这里的temp就对应于[ebp-4].子程序执行到最后,要返回temp的值:mov eax,[ebp-04]然后执行相反的操作以撤销框架:mov esp,ebp ;这时esp和ebp都指向old ebp,临时变量已经被撤销pop ebp ;撤销框架指针,恢复原ebp.这是esp指向返回地址紧接的retn指令返回主程序:retn 4该指令从堆栈弹出返回地址装入EIP,从而返回到主程序去执行call后面的指令同时调整esp(esp=esp+4*2),从而撤销参数,使堆栈恢复到调用子程序以前的状态,这就是堆栈 的平衡调用子程序前后总是应该维持堆栈的平衡从这里也可以看到,临时变量temp已 经随着子程序的返回而消失,所以试图返回一个指向临时变量的指针是非法的为了更好地支持高级语言,INTEL还提供了指令Enter和Leave来自动完成框架的建 立和撤销。
Enter接受两个操作数,第一个指明给临时变量预留的字节数,第二个是子程序 嵌套调用层数,一般都为0enter xxx,0相当于:push ebpmov ebp,espsub esp,xxxleave则相当于:mov esp,ebppop ebp=============================================================好啦,我的学习心得讲完了,谢谢各位的抬举教程是不敢当的,因为我也是个大菜鸟 如果这些东东能使你们的学习轻松一些,进步快一些,本菜鸟就很开心了。