林光伟 原创作品转载请注明出处

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

实验内容:

将下面的C程序反汇编,并分析其运行及堆栈变化过程

int g(int x)
{
  return x + 3;
}
 
int f(int x)
{
  return g(x);
}
 
int main(void)
{
  return f(8) + 1;
}

实验环境:

gcc version 4.4.7 (Ubuntu/Linaro 4.4.7-8ubuntu1) , ubuntu 14.04 64bit

实验步骤

1、输入 ctrl + t 打开命令终端, 输入gedit main.c,打开编辑器,输入上述程序, 并保存退出。

在命令行中输入

gcc -S -o main.s main.c -m32

由于实验平台是64位机,gcc选项"-m32"表示将程序编译成32位的指令格式,如果在32位机,"-m32"可以不用加上去。

于是就可以看到在当前的目录下便生成了一个main.s的汇编程序。

main.s 的内容如下

    .file    "main.c"
    .text
.globl g
    .type    g, @function
g:
    pushl    %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
    addl    $3, %eax
    popl    %ebp
    ret
    .size    g, .-g
.globl f
    .type    f, @function
f:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    g
    leave
    ret
    .size    f, .-f
.globl main
    .type    main, @function
main:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    $8, (%esp)
    call    f
    addl    $1, %eax
    leave
    ret
    .size    main, .-main
    .ident    "GCC: (Ubuntu/Linaro 4.4.7-8ubuntu1) 4.4.7"
    .section    .note.GNU-stack,"",@progbits

为了方便阅读,去掉以"."开头的内容,就变成

g:
    pushl    %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %eax
    addl    $3, %eax
    popl    %ebp
    ret
f:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    8(%ebp), %eax
    movl    %eax, (%esp)
    call    g
    leave
    ret
main:
    pushl    %ebp
    movl    %esp, %ebp
    subl    $4, %esp
    movl    $8, (%esp)
    call    f
    addl    $1, %eax
    leave
    ret

可以看到main.s 的3个函数与main.c里的3个函数是一一对应的。程序从main处开始执行

int main(void)对应着:

    pushl    %ebp
    movl    %esp, %ebp

 ESP就是一直指向栈顶的指针,而EBP只是存取某时刻的栈顶指针,以方便对栈的操作,如获取函数参数、局部变量等。执行pushl,esp会自动减4,而执行popl,esp则会自动加4,上面两条指命执行之前的情况是是

esp->

ebp->

             
   
   
   
   
   

 (栈顶)

 

 

 

 

 

(表1) 

pushl    %ebp,ebp的值压入栈顶,esp -= 4,栈顶下移。 movl %esp, %ebp,
   

esp->

ebp->

old ebp 
   
   
   
   

 

(栈顶)

 

 

 

 


(表2)
return f(8) + 1;先将立即8当成参数传入函数f
subl    $4, %esp;栈顶下移

movl $8, (%esp);将立即数8压入栈顶
    
ebp-> old ebp 
esp-> 8
   
   
   

 

 (栈顶)

 

 

(表3)

然手调用f函数

call f

call 函数可以分解成


A. 将EIP寄存器的当前值压入到栈中供返回时使用

B. 将被调用过程的偏移(相对于当前段)加载到EIP寄存器中

C. 开始执行被调用过程

    
ebp->  old epb
  8
esp-> old epi
   
   

 

 

 (栈顶)

 

(表4)

然后程序就进入到f函数

f:
    pushl    %ebp         ;位置1
    movl    %esp, %ebp      ;位置2
    subl    $4, %esp         ;位置3
    movl    8(%ebp), %eax    ;位置4
    movl    %eax, (%esp)     ;位置5
    call    g
    leave                    ;位置A
    ret

执行到f函数位置3的地方后,

   
ebp-> old epb
  8
  old epi

ebp->

old2 epb
esp->  

 

 

 

 

(栈顶)

(表5)

 

位置4, movl 8(%ebp), %eax  ,变址寻址,将ebp-8地址的数存入eax ,这个地址上存数值为8。

位置5将eax的值(8)送入栈顶。f函数就是将收到的参数8传给g,再将g返回的参数返回,实际上是什么也没有做。

然后进入了g函数:

g: 
   pushl    %ebp          
    movl    %esp, %ebp
    movl    8(%ebp), %eax ;位置6
    addl    $3, %eax      ;位置7
    popl    %ebp          ;位置8
    ret            ;位置9

位置6, 将f函数传入的参数8移到eax

位置7,将立即数3与eax进行运算,实际eax就得到3+8的值

位置8,pupl %epb,从表5上分析,将esp的值加4,esp所指向old2 epb,将old2 epb传给epb,于是epb的指向又回到了old epb。

位置9,ret 相当于pop %eip,于是esp值又加4, esp指向 old eip,将old eip传给eip,也就是说,程序反回到了f  leave这条指令上。

leave 
ret

相当于

movl %ebp, %esp 
pob %ebp
pop %eip

cpu就回到了main的 addl $1, %eax,eax 保存着上次运算的结果,然后与立即数加1,完成运算。

 

 

 

posted on 2015-03-08 23:02  林德伟  阅读(102)  评论(0)    收藏  举报