反汇编一个简单的C程序
1、实验要求
使用:
gcc –S –o test.s test.c -m32
命令编译成汇编代码,对汇编代码进行分析总结。其中test.c的具体内容如下:
int g(int x)
{
return x + 3;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(8) + 1;
}
2、实验过程
- 使用vim对test.c进行编辑,将上述代码键入
- 使用gcc对test.c编译,产生test.s文件
gcc -S -o test.s test.c -m32
如下图所示:
- 使用vim查看test.s文件
查看test.s文件,如下图所示:
“.”开通的所有代码为链接时候的辅助信息,不会被实际执行,我们可以把这些代码删除,删除后的代码即为纯净的汇编代码,如下图所示:
3、实验分析
-
寄存器&寻址方式
- 通用寄存器:
- AX:累加器
- BX:基地址寄存器
- CX:计数寄存器
- DX:数据寄存器
- BP:堆栈基址针
- SI、DI:变址寄存器
- SP:堆栈顶指针
- 段寄存器
- CS:代码段寄存器,指向包含程序指令的段。
- SS:栈段寄存器,指向包含当前程序栈的段。
- DS:数据段寄存器,指向包含静态数据或者全局数据段。
- ES:附加寄存器,指向附加数据段。
- 寻址方式
- movl %eax,%edx <-> edx=eax 寄存器寻址
- movl $0x123,%edx <-> edx=0x123 立即寻址
- movl 0x123,%edx <-> edx=*(int32_t*)0x123 直接寻址
- movl (%ebx),%edx <-> edx=*(int32_t*)ebx 间接寻址
- movl 4(%ebx),%edx <-> edx=*(int32_t*)(ebx+4) 变址寻址
- 通用寄存器:
-
汇编代码流程分析
-
汇编代码依然从main函数读起
- eip寄存器从main函数起自+1,指向当前执行的指令语句(未遇到特殊指令时)
- ebp寄存器始终指向栈底,esp寄存器始终指向栈顶,eax寄存器用来暂存一些数值(如函数的返回值)
- ebp入栈
- 把ebp值赋予给esp
- 将esp值减4,指向下一栈存储空间
- 将数值2018存在esp所指的存储空间中
-
调用f函数
- ebp入栈
- 把ebp值赋予给esp
- 将esp减4,指向下一栈地址空间
- 将ebp变址寻址8,即向上退两个栈空间,并将此空间值赋给eax
- 将eax的内容赋予给esp
-
调用g函数
- 对ebp进行压栈
- 把ebp值赋予给esp
- 将ebp变址寻址8,即向上退两个栈空间,并将此空间值赋给eax
- 将eax储存的数值加9228
- 出栈
- 返回f函数call语句的下一句(leave)
-
返回main函数
- 返回main的call指令下一句(addl)
- 将eax储存的数加9228
- esp指向ebp位置,ebp指向栈中指向的位置(leave作用)
- 返回
-
-
堆栈变化分析
初始化状态esp和ebp都在栈底:
后指令代码从main函数顺序执行:
执行到call f指令,eip记录call指令的下一条语句位置,此时代码跳转至f函数执行:
执行到call g指令,eip记录call指令下一条语句位置,此时代码跳转至g函数执行:
此时,g函数执行到ret,返回f函数call的下一条语句leave:
至此,整个指令运行完成,堆栈为空。
4、实验总结
汇编语言是计算机的低级语言,真正的深入了解汇编语言对于我们深入理解计算机系统有着极大的帮助,更是为我们未来linux内核分析的深入学习打下坚实基础。本次使用C语言反编译成汇编语言,利用我们熟悉的C语言代码来对照理解汇编指令代码、分析堆栈的变化让我们更好的理解计算机在执行程序的时候是如何工作的。具体来说:
- 加深了esp、ebp、eip以及eax各自作用的印象
- 更好的理解了变址寻址的具体过程
- 更加熟悉了常用的汇编指令
- 了解了计算机在执行程序时是如何工作的
其他问题
在进行makefile的测试时,在vim中写好了Makefile,在进行make时,shell提示没有找到make指令,我意识到应该是没有内置此指令,便使用sudo apt-get install make获取make指令,但shell提示:
sudo: /usr/bin/sudo must be owned by uid 0 and have the setuid bit set
应该是账户的权限不够,但我想要切换到系统内置的root账户,要求我输入密码,但我好像并不知道root的密码……
经过一系列的资料查询,参考了此篇博文的方法,进入recovery模式更改root账户密码。重启后,使用root账户,sudo apt-get install make执行成功,make命令成功安装。
出现这样的小插曲,也算是在本次作业之外的一个小收获。