C/C++知识点之Linux下简单C语言小程序的反汇编分析
小标 2018-10-10 来源 : 阅读 1198 评论 0

摘要:本文主要向大家介绍了C/C++知识点之Linux下简单C语言小程序的反汇编分析,通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

本文主要向大家介绍了C/C++知识点之Linux下简单C语言小程序的反汇编分析,通过具体的内容向大家展示,希望对大家学习C/C++知识点有所帮助。

写在开始,本文为因为参加MOOC相关课程而写的作业,如有疏漏,还请指出。
选了一门Linux内核分析课程,因为阅读内核代码中或多或少要涉及到At&T汇编代码的阅读,所以这里写下一个对一个简单C命令行程序的反汇编分析过程,一方面完成作业,另一方面当作练手。下面开始:
 
1、编写我们的C语言小程序
这里我们使用简单的例子,代码如下:

 1 #include 
 2 
 3 int exG(int x)
 4 {
 5     return x + 5;
 6 }
 7 
 8 int exF(int x)
 9 {
10     return exG(x);
11 }
12 
13 int main(void)
14 {
15     return exF(10) + 2;
16 }

使用vim等编辑器写入上述代码,保存到main.c,然后使用下面命令生成汇编源文件:x86系统:$gcc -S -o main.s main.cx64系统:$gcc -m32 -S -o main.s main.c因为我们这里以32位平台为例子,所以在x64机器上要加上-m32来使GCC生成32位的汇编源文件。
2、处理源文件
执行完上述命令后,当前目录下就会有一个main.s的文件,使用vim打开,不需要的链接信息[以"."开头的行],得到如下汇编代码:

 1 exG:
 2     pushl    %ebp
 3     movl    %esp, %ebp
 4     movl    8(%ebp), %eax
 5     addl    $5, %eax
 6     popl    %ebp
 7     ret
 8 exF:
 9     pushl    %ebp
10     movl    %esp, %ebp
11     pushl    8(%ebp)
12     call    exG
13     addl    $4, %esp
14     leave
15     ret
16 main:
17     pushl    %ebp
18     movl    %esp, %ebp
19     pushl    $10
20     call    exF
21     addl    $4, %esp
22     addl    $2, %eax
23     leave
24     ret

可以看到这个文件里是GCC帮我们生成的汇编代码,这里需要说明下AT&T格式和intel格式,这两种格式GCC是都可以生成的,如果要生成intel格式的汇编代码,只需要加上 -masm=intel选项即可,但是Linux下默认是使用AT&T格式来书写汇编代码,Linux Kernel代码中也是AT&T格式,我们要慢慢习惯使用AT&T格式书写汇编代码。这里最需要注意的AT&T和intel汇编格式不同点是:
AT&T格式的汇编指令是“源操作数在前,目的操作数在后”,而intel格式是反过来的,即如下:AT&T格式:movl %eax, %edxIntel格式:mov edx, eax表示同一个意思,即把eax寄存器的内容放入edx寄存器。这里需要注意的是AT&T格式的movl里的l表示指令的操作数都是32位,类似的还是有movb,movw,movq,分别表示8位,16位和64位的操作数。更具体的AT&T汇编语法请执行Google或者查阅相关书籍。
3、汇编代码分析
下面开始分析汇编代码,运行程序后,C Runtime会在进行一系列准备工作后把我们让eip指向我们的main函数开始执行,所以这里从main开始分析:
首先进入gdb调试环境:在我们的机器上输入如下命令生成带有调试信息的elf文件,然后进入gdb进行调试:$gcc -m32 -g -o main main.c$gdb main -tui -q进入gdb后,输入layout asm切换到反汇编视图,同时在main函数处下断点:(gdb)layout asm(gdb)b main
然后我们使用(gdb)si来逐条指令执行并观察寄存器变化情况,如图:

对于main函数:
逐条指令执行(gdb)si

pushl    %ebp
movl    %esp, %ebp...

这两条是Prolog,其作用包含保存当前的栈环境,以确保函数能正确返回和为当前函数开辟新的栈空间。这两句的执行效果是把当前的ebp值入栈,再把ebp入栈后的esp中的值放入ebp。此时,esp和ebp都指向同一个内存地址。这里需要说明的是入栈和出栈操作,在intel的x86架构上,栈是从高地址向低地址增长,所以:
入栈等价于:1、esp先下移留出对应的空间;2、把相应数值放入刚刚留出的空间完成入栈
出栈等价于:1、从当前esp指向内存取出数值;2、esp向上移动,释放相应空间
此时栈中的情况如下如所示:[从这里开始,下图中每个空格皆表示4字节内存空间]
图1

继续逐条指令执行

1 pushl    $10
2 call    exF
3 addl    $4, %esp
4 ....

pushl $10,当前esp先减4,然后把宽度为4直接的数值10放入esp当前指向的内存中。
call  exF ,函数调用指令,首先把当前eip的值[当前eip指向第三条指令,即addl $4, %esp]入栈,然后跳转到exF函数的第一条指令开始执行。此时栈中的情况如下如所示:图2

对于exF函数:
逐条指令执行(gdb)si

1 pushl    %ebp
2 movl    %esp, %ebp
3 pushl    8(%ebp)
4 call    exG
5 addl $4, %esp
6 ....

这里前条指令和main函数的头两条指令作用相同,保存当前栈环境,为exF函数开辟新的栈空间 
pushl 8(%ebp),该指令把当前ebp中的数值加8后作为内存地址,并把该内存地址指向的内存空间内的数值"10"放入栈中。[参考图2可以发现其实就是把调用函数是传入的参数入栈]call exG,函数调用指令,当前eip入栈后,跳转到exG函数的第一条指令执行。
此时栈中的情况如下如所示:
图3
对于exG函数:
逐条指令执行(gdb)si

1 pushl    %ebp
2 movl    %esp, %ebp
3 movl    8(%ebp), %eax
4 addl    $5, %eax
5 popl    %ebp
6 ret

首先依然是函数前言(Prolog),保存栈环境,开辟新的栈空间
此时栈中的情况如下如所示:
图4
movl 8(%ebp),%eax 该指令把当前ebp中的数值加8后作为内存地址,并把该内存地址指向的内存空间内的数值“10”放入eax寄存器中。[参照图4可以发现就是把调用函数是传入的参数放入eax寄存器]addl $5, %eax AT&T汇编语言中$符号后面跟上数字表示一个立即数,这里即为把eax中的值加上5,再放回eax,此时eax的值为15.popl %ebp,从栈中获取旧的esp值,并放入ebp寄存器。[这里之所以没有再加上一条movl %ebp, %esp是因为函数中esp的值并没有改变,依然指向存放旧esp值的内存空间]ret 等价于pop eip,从当前栈顶,即esp所指内存处获取值,作为eip,然后跳转到eip中存放的地址继续执行。此时栈中情况如图:图5
到这里,函数exG已经返回,其返回值存储在eax寄存器中,即返回值为15
 
返回到函数exF中

1 ...
2 addl    $4, %esp
3 leave
4 ret

程序从上述指令开始继续执行,addl $4, %esp 回收栈空间,栈空间收缩4个字节,leave,等价于 如下两条指令    movl %ebp, %esp    pop %ebp即函数结语[EpiLog],释放exF函数使用的栈空间,此时栈中情况如图:图6
再接着是ret指令,该指令执行后,函数exF返回,程序回到main函数继续执行,此时栈中情况如图:
图7
此时eax中存放的是函数exF的返回值,即15
 
回到main函数继续执行

1 ...
2 addl    $4, %esp
3 addl    $2, %eax
4 leave
5 ret

addl $4, %esp 栈收缩4个字节,回收栈空间addl $2, %eax 此时eax中的值是main函数调用函数exF的得到的返回值,即15,本条指令将eax中的值加2后放回eax,执行后eax中的值为17leave 函数结语,本条指令执行后,ebp的值为图7中黑色Old EBP表示的值,esp指向图7中黑色Old ebp所在内存空间的上一个内存空间,该处存放的是指向CRT调用main函数后紧接的指令的所在的内存地址ret main函数返回
 
4、总结
计算机工作的过程实际上就是“取指令,执行指令”的循环,程序在执行时被装入内存,计算机从内存中某个位置开始读取指令按照一定逻辑顺序执行,直到程序结束。在执行过程中根据需要为程序中各个模块在内存中开辟一定的空间[如栈,堆]。计算机从内存的什么地方开始执行指令完全由cpu中指令指针寄存器[EIP]中的值决定,并不会区分内存中什么地方是代码段,什么地方是数据段。

本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标编程语言C/C+频道!

本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 1 不喜欢 | 0
看完这篇文章有何感觉?已经有1人表态,100%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程