汇编夜读 第一篇

来源:互联网 发布:店铺淘宝客软件有哪些 编辑:程序博客网 时间:2024/09/21 09:27

好多东西不太清楚,于是就想窥探一下程序内部运行的究竟,于是有了以下的文章

一、从空函数探究
vim learn.c
什么也不写

gcc -S learn.c
查看learn.s中内容如下:
----------------------------------------------------------------------------------
  1     .file   "learn.c"
  2     .ident  "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)"
  3     .section    .note.GNU-stack,"",@progbits
----------------------------------------------------------------------------------
写了如下代码
int main(){}

gcc -S learn.c
查看learn.s中内容如下:
----------------------------------------------------------------------------------
  1     .file   "learn.c"
  2     .text
  3 .globl main
  4     .type   main, @function
  5 main:
  6     leal    4(%esp), %ecx
  7     andl    $-16, %esp
  8     pushl   -4(%ecx)
  9     pushl   %ebp
 10     movl    %esp, %ebp
 11     pushl   %ecx
 12     popl    %ecx
 13     popl    %ebp
 14     leal    -4(%ecx), %esp
 15     ret
 16     .size   main, .-main
 17     .ident  "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)"
 18     .section    .note.GNU-stack,"",@progbits
----------------------------------------------------------------------------------
增加了从行2-16的代码。
可以看出11-12 有一个pushl %ecx,并接着popl %ecx的过程

类似的,又将learn.c中内容改成如下:
int min(){}

gcc -S learn.c
查看learn.s中内容如下:
----------------------------------------------------------------------------------
  1     .file   "learn.c"
  2     .text
  3 .globl min
  4     .type   min, @function
  5 min:
  6     pushl   %ebp
  7     movl    %esp, %ebp
  8     popl    %ebp
  9     ret
 10     .size   min, .-min
 11     .ident  "GCC: (GNU) 4.1.2 20070925 (Red Hat 4.1.2-33)"
 12     .section    .note.GNU-stack,"",@progbits
----------------------------------------------------------------------------------
同上例相比,learn.s中的内容少了好多。同样的都是定义一个空函数,main()函数汇编后的代码要比普通函数多,这也说明了main函数的特殊性

若将learn.c中内容改为如下
void min(){}
gcc -S learn.c
可以发现learn.s中的内容没有变化
改为
double min(){}后learn.s中的内容倒是变化不少,增加了
----------------------------------------------------------------------------------
  4 .LC0:
  5     .long   2143289344
  ...
 11     movl    %esp, %ebp
 12     flds    .LC0
 13     popl    %ebp
----------------------------------------------------------------------------------
具体意义暂时不懂。

从上面3段.s代码中可以看到第1行,和最后两行都是完全相同的,倒数第3行和函数名称有关。
2-4行的代码格式也基本类似

对于main()函数,在压榨push %ebp之前,有如下操作:
  6     leal    4(%esp), %ecx
  7     andl    $-16, %esp
  8     pushl   -4(%ecx)
其意义尚未理解。

二、变量声明
将learn.c中内容改为如下
int min(){int a;}

gcc -S learn.c
可以发现learn.s中的内容如下:
----------------------------------------------------------------------------------
  5 min:
  6     pushl   %ebp
  7     movl    %esp, %ebp
  8     subl    $16, %esp
  9     leave
 10     ret
----------------------------------------------------------------------------------
即定义了变量a后,汇编代码中对应的片段为第8行,而且此时不再执行pop %ebp,而是用一条leave指令代替。
将learn.c中内容改为如下
int main(){int a;}
可以发现learn.s中的内容相比原来的空main()函数在11行之后多了两行代码:
----------------------------------------------------------------------------------
 10     movl    %esp, %ebp
 11     pushl   %ecx
 12     subl    $16, %esp
 13     addl    $16, %esp
 14     popl    %ecx
  ...
----------------------------------------------------------------------------------
进一步修改main()为
int main(){int a,b;}
可以发现learn.s中的内容没有变化。这是什么意思?
继续修改
int main(){int a,b,c;}
发现learn.s中的内容还是没有变化,但是,subl %16,%esp是将栈顶下移了16个字节,估计我们只要声明4个以上的整形变量就会不一样吧
int main(){int a,b,c,d,e;}
果然,此时learn.s中的代码变为
----------------------------------------------------------------------------------
 12     subl    $32, %esp
 13     addl    $32, %esp
----------------------------------------------------------------------------------
这个发现还是比较有价值滴,暂且记下。

继续修改
int main(){double a;}
此时learn.s中的代码变为
----------------------------------------------------------------------------------
 12     subl    $20, %esp
 13     addl    $20, %esp
----------------------------------------------------------------------------------
即声明double类型变量时,栈顶位置会向下移动20个字节,我们知道double是8个字节,那么生命
超过3个double变量,20个字节就不够用了,所以
继续修改
int main(){double a,b,c;}
汇编后learn.s如下:
----------------------------------------------------------------------------------
 12     subl    $36, %esp
 13     addl    $36, %esp
----------------------------------------------------------------------------------
呵呵,变成36了,挺有意思的。突然想起来,这是不是就是用C语言写代码时变量生命必须放在函数罪
前面的原因呢,因为栈顶会根据定义变量的总和以决定下移合适的位置。我想应该是这个原因吧,呵呵
,貌似又发现了一个小秘密。

继续修改
int main(){double a;int b;}这个仔细想象也觉得应该是首先指定20个字节,验证后果然如此

三、变量定义
将learn.c中内容改为如下
int main(){int a = 3;}
汇编后learn.s如下:
----------------------------------------------------------------------------------
 12     subl    $16, %esp
 13     movl    $3, -8(%ebp)
 14     addl    $16, %esp
----------------------------------------------------------------------------------
就13行发生了变化,其他行不变。$3,很容易理解,就是立即数3的意思,-8(%ebp)呢,为什么用原来
栈顶位置-8呢,既然分配了16个字节,-8是不是跟数立即数的大小有关,将3改为256,发现没有变化,哎,失败,看来不是这个原因,暂且记下这个问题。
继续修改
int main(){int a = 3;int b = 4;}
汇编后learn.s如下:
----------------------------------------------------------------------------------
 12     subl    $16, %esp
 13     movl    $3, -12(%ebp)
 14     movl    $4, -8(%ebp)
 15     addl    $16, %esp
----------------------------------------------------------------------------------
这样跟上面的一对比,好像明白了。
继续修改
int main(){int a = 3;int b = 4;int c = 5;}
汇编后learn.s如下:
----------------------------------------------------------------------------------
 12     subl    $16, %esp
 13     movl    $3, -16(%ebp)
 14     movl    $4, -12(%ebp)
 15     movl    $5, -8(%ebp)
 16     addl    $16, %esp
----------------------------------------------------------------------------------
可见-8(%ebp)到%ebp的8个位置是备用的。具体用作什么,估计慢慢就明白了。
继续修改
int main(){int a = 3;int b = 4;b = a;}
汇编后learn.s如下:
----------------------------------------------------------------------------------
 13     movl    $3, -12(%ebp)
 14     movl    $4, -8(%ebp)
 15     movl    -12(%ebp), %eax
 16     movl    %eax, -8(%ebp)
----------------------------------------------------------------------------------
15,16行是对应的赋值语句。

玩了半天,感觉挺有意思的,自己不明白什么就去研究一下,应该能逐渐找到答案的。