实验一:反编译一个简单的C程序
第一步:
创建编辑一个 mian.c文件 ,内容图二所示。
第二步:
使用 gcc -S -o mian.s main.c 32命令将C程序编译成汇编代码。
打开main.s文件如图所示。
第三步:
将 . 开头的文件,删掉。因为他是链接产生的临时性文件,对程序无影响,留下来的就是纯汇编代码。
X86汇编指令
执行分析代码:
1、eip寄存器:指向指令。从main指令的第一条开始
2、ebp寄存器:指向堆栈的栈底(对某一条函数相对来说的栈底)
3、esp寄存器:总是指向堆栈的栈顶。
4、eax寄存器:将函数的默认返回值使用eax寄存器存储返回给上一级函数。
分析:eip寄存器的值,指向main()函数的命令,执行程序。注意压栈是往低地址走,出栈是往高地址走。
- pushl %ebp:ebp寄存器的值压入栈中,esp的值向下移动4个字节(地址位置-4)
- movl %esp,%ebp:ebp位置更新,将ebp位置移到esp位置。
- subl $4,%esp:esp寄存器中的内容-4( ‘$’表示立即数),即再将esp寄存器中的内容改为刚才内容(刚才记录的栈顶地址,也是刚才的基地址)的下面一个单元(低4字节)作为esp寄存器中心的内容。
- movl 8(%ebp),通过ebp寄存器变址寻址,ebp寄存器的值加8(注意是加,是向栈顶跑,而且实际不存,只是算一下,不改变ebp存入的值),当前ebp寄存器的内容为栈的第四个单元的地址,加8后ebp寄存器的内容为栈的第二个单元的地址,而此时那个地址里的内容是立即数“8”(也就是说"8(%ebp)"得到的结果是立即数“8”),然后把立即数"8"存入eax寄存器就好。
- call f :表示调用f函数。对应两条指令 push eip move f eip esp寄存器的值-4(存的内容是下一个单元的地址),将eip中的值“23”(eip中的值“23”是因为执行到“22”,在执行call命令时eip自动+1,eip里面始终存的是下一条指令的地址,如此一来函数f调用结束的时候,就可以找到回来的路,函数调用结束后从23条命令继续执行)存入第3个栈单元(第1个栈单元里存着栈顶地址,为ebp最初的值,第2个栈单元里存着立即数“8”),保存好之后,eip内存的值就可以更换成“9”了,因为“9”为f的第一条指令。所以,call指令改变了三个内容:1、栈空间(存入了回去的路,eip的旧值“23”);2、esp寄存器的值(由第二个栈单元的地址变为第三个栈单元的地址);3、eip寄存器的值(旧值“23”,新值为“9”,定位到函数f的第一条指令的位置)。
- 进入f函数,开始执行。pushl %ebp,此时也类似main第一句的情况,先让esp内容减4(指向栈的第四个单元),再把ebp的值(为栈顶地址-4,即第一个单元的位置,和开始压栈完后保持一致)放入栈的第4个单元中。movl %esp,%ebp就是同main的第二句,用栈的第四个单元的地址替换了ebp中原先存的栈的第一个单元的地址。subl $4,%esp同main的第三句,让esp里存的内容改为栈的第五个单元的地址。movl 8(%ebp),%eax中是说通过ebp寄存器变址寻址,ebp寄存器的值加8(向栈顶跑,不改变ebp存入的值),当前ebp寄存器的内容为栈的第四个单元的地址,加8后ebp寄存器的内容为栈的第二个单元的地址,而此时那个地址里的内容是立即数“8”(也就是说"8(%ebp)"得到的结果是立即数“8”),然后把立即数"8"存入eax寄存器就好。movl %eax,(%esp)就是说把eax中的立即数"8",放入esp中地址所指向的那个栈单元,那个栈单元显然是栈的第5个单元的地址,所以栈的第5个单元里的内容为立即数"8"。call g,调用G函数 。1、栈的第6个单元存入了回去的路,即eip旧值“15”;2、esp寄存器的值(由第5个栈单元的地址变为第6个栈单元的地址);3、eip寄存器的值(旧值为“15”,新值为“2”,定位到函数g的第一条指令的位置)
- 进入g函数,开始执行。g函数的第一句,esp-4(方便esp存的那个地址指向的那个栈的第七个单元去存ebp的值),把ebp的值(为第4个栈单元的地址存入第7个栈单元中,如此是为了保存现场,这也是从函数g“回去”函数f之后,需要使用的栈基址的值(为第四个栈单元的地址)第二句让ebp的值改为第7个栈单元的地址,第三句eax最后取出的为第五个栈单元的内容立即数"8"(第7个栈单元的地址加8,也即向上两个单元),第四句就是让立即数"8"和立即数"3"做加法,得到的结果"11"存入eax寄存器(暂存器)中。第五句和第六句popl %ebp,ret的作用就是拆除函数g的调用堆栈,并返回到调用函数f的位置。popl %ebp实际上就是把第7个栈单元的内容(ebp旧内容"第4个栈单元的地址")放回ebp寄存器,就是恢复函数f的函数调用堆栈基址ebp寄存器,效果是ebp寄存器的内容变为原来第4个栈单元的地址,同时esp寄存器也要加4个字节指向第6个栈单元的位置。 所以popl的两个作用和pushl的两个作用是对称的,popl先把ebp旧址还原,然后栈向上加4个字节(一个单元)。 ret实际上就是popl %eip,也就是把esp寄存器所指向的栈空间存储单元——第六个栈单元的内容(eip旧址“15”)放回到eip中,同时esp寄存器加4个字节到第5个栈单元的位置,总之ret的作用还原eip旧址,同时esp自动加4。
- 跳回到函数f的倒数第二句leave,leave指令的作用等价于movl %ebp,%esp和popl %ebp两条指令。它的目的是撤销函数的堆栈,把esp的值改为ebp的值(基址),再把ebp旧址还原,栈再向上加4个字节,此时的esp的内容为第3个栈单元的地址。
- 函数f的ret同之前ret的作用.
- 再返回到main函数的leave以及ret,同上。
过程展示