刘子健
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
对一下代码进行反汇编分析:
int g(int x)
{
return x + 42;
}
int f(int x)
{
return g(x);
}
int main(void)
{
return f(42) + 42;
}
我的主机是64位的Linux,所以使用的反汇编代码也是64-bits的.
.file "2015_03_01.c"
.text
.globl g
.type g, @function
g:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
addl $42, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size g, .-g
.globl f
.type f, @function
f:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $8, %rsp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %edi
call g
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size f, .-f
.globl main
.type main, @function
main:
.LFB2:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $42, %edi
call f
addl $42, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE2:
.size main, .-main
.ident "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
反汇编得到的代码里面有很多提示信息,提示信息以 . 开头,程序执行时这些提示信息不是指令,我们在这个反汇编样例里面可以精简代码,把这些提示信息删除.有些信息不能剔除,这些信息是编译器必须的,否则你过不了编译链接.
下面是精简后的反汇编代码:以下代码可以通过 gcc ./2015_03_01.s -o ./a.out
.text
.globl g
.type g, @function
g:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
addl $42, %eax
popq %rbp
ret
.size g, .-g
.globl f
.type f, @function
f:
pushq %rbp
movq %rsp, %rbp
subq $8, %rsp
movl %edi, -4(%rbp)
movl -4(%rbp), %eax
movl %eax, %edi
call g
leave
ret
.size f, .-f
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp
movl $42, %edi
call f
addl $42, %eax
popq %rbp
ret
.size main, .-main
关于基本汇编指令的分析,我之前有笔记,可以去看这里:
http://blog.csdn.net/cinmyheart/article/details/25558911
我们这里着重分析反汇编代码:
g: , f:, main: 均用来指示函数的入口.
对于函数main.
首先压栈,pushq 指令将rsp寄存器的值减去一个指针长度,在64-bits机器上即8byte,然后将 rbp寄存器的值写入到rsp指向的地址处.
movq %rsp, %ebp指令则将rsp寄存器的值赋值给rbp寄存器.这样一来,属于main函数的栈区域便构建好了.
接着movl 把立即数42赋值给寄存器edi, 然后call指令调用函数f.函数f的返回值会储存在eax寄存器中,等待f调用完之后,会把eax寄存器的值和立即数42相加,并储存在eax寄存器中.最后把rbp寄存器处的值弹栈.然后ret指令返回.
---------------------------------------------------------------------------------
call f
指令就相当于
push %eip #把当前指令指针寄存器压栈,然后跳转到f处
jump f
---------------------------------------------------------------------------------
ret 指令就相当于
popl %eip #把当前esp寄存器指向地址处的值,赋值给eip
然后把esp寄存器的值减去一个指针长度,即8-byte
---------------------------------------------------------------------------------
看看函数f都干了神马.
还是和上面介绍main函数一样的"老规矩",构建函数f的堆栈,
pushq %rbp
movq %rsp, %rbp
接着使用subq $8, %rsp把rsp寄存器的值减去8.
接着把edi寄存器的值赋值给rbp寄存器指向地址处减去4byte的地址处
紧接着,把这个地址处的值赋值给eax寄存器.
把eax寄存器的值又赋值给edi寄存器(其实我想说,这不是吓折腾么...这编译器啊..这期间edi寄存器的值没变)
然后调用函数g
一句话概括就是把edi寄存器的值加上42赋值给eax寄存器,然后返回.(不改变edi寄存器的值)
阐明自己对“计算机是如何工作的”理解:
对于规范化后的程序指令,逐一的对程序指令进行"解释处理".不同的CPU,可能有不同的汇编指令集,比方说Intel -- X86 /X64平台,ARM平台,PowerPC等等,但是他们最基本的的思想都是近似的--冯诺依曼体系结构.
数字计算机的数制采用二进制;计算机应该按照程序顺序执行
-----------------------