Chinaunix

标题: main函数汇编原理 [打印本页]

作者: Anno_Domini    时间: 2010-12-06 17:00
标题: main函数汇编原理
main函数代码:
int main()
{return 0;}
虚拟机中反编译main函数源码:
0x08048354 <main+0>:    lea    0x4(%esp),%ecx
0x08048358 <main+4>:    and    $0xfffffff0,%esp
0x0804835b <main+7>:    pushl  -0x4(%ecx)
0x0804835e <main+10>:   push   %ebp
0x0804835f <main+11>:   mov    %esp,%ebp
0x08048361 <main+13>:   push   %ecx
0x08048362 <main+14>:   mov    $0x0,%eax
0x08048367 <main+19>:   pop    %ecx
0x08048368 <main+20>:   pop    %ebp
0x08048369 <main+21>:   lea    -0x4(%ecx),%esp
0x0804836c <main+24>:   ret  
请教高手分析下指令含义,指点迷津。
作者: liexusong    时间: 2010-12-06 17:12
学过一点汇编,但是分析就不会了~
作者: lonerwolf    时间: 2010-12-06 19:49
回复 1# Anno_Domini


   {:3_190:}在某个 ARM bootloader里发现main的参数用通用寄存器的1号和2号来传递,进去之后就是保存这2个参数.而且这里还有个标准XXX,就是一个编译器的规定,汇编调用C的规则,所以要先看看编译的规则才知道
作者: chenzhanyiczy    时间: 2010-12-06 20:50
压栈,运算,出栈,再压栈,再运算,再出栈。。。
作者: lengyuex    时间: 2010-12-06 21:12
楼主可以看看《程序员的自我修养》这本书里介绍得很好
作者: liwangli1983    时间: 2010-12-06 22:35
本帖最后由 liwangli1983 于 2010-12-07 14:30 编辑

0x08048354 <main+0>:    lea    0x4(%esp),%ecx
0x08048358 <main+4>:    and    $0xfffffff0,%esp            ##让栈指针按16字节对齐
0x0804835b <main+7>:    pushl  -0x4(%ecx)                  ##返回地址入栈
0x0804835e <main+10>:   push   %ebp                          ##保存栈帧指针值
0x0804835f <main+11>:   mov    %esp,%ebp                 ##栈帧指针指向栈指针指向的位置
0x08048361 <main+13>:   push   %ecx                           ##ecx入栈
0x08048362 <main+14>:   mov    $0x0,%eax                  ##将main的返回值放入eax
0x08048367 <main+19>:   pop    %ecx                            ##ecx出栈
0x08048368 <main+20>:   pop    %ebp                           ##恢复栈帧指针值
0x08048369 <main+21>:   lea    -0x4(%ecx),%esp          ##恢复原栈指针值
0x0804836c <main+24>:   ret                                           ##返回(ret会自动把esp指向的位置的内容复制到pc中.因为此时esp指向<main+7>时入栈的返回地址,所以此句返回)

大部分普通函数基本上都是从push %ebp开始的.这里两次把返回地址入栈(第一次是调用main的程序入的,就放在刚刚进入main过程时esp指向的位置),第二次大约是因为对栈指针取16字节对齐可能导致栈指针变化,所以重新入栈一次返回地址,再像正常子过程那样把ebp入栈.这样在程序末尾pop %ebp后,%esp就自然指向返回地址了,否则esp指向的内容是不确定的.

如果可能最好把<C语言标准与实现>啃下来,很硬的书,通篇汇编和各种类型数值的分析,看着很容易犯困。但内容确实很不错,认真看下来收获不小。
作者: Anno_Domini    时间: 2010-12-06 23:49
回复 6# liwangli1983

谢谢你的指点,但还是有一个不明白的地方:
0x08048369 <main+7>:    pushl  -0x4(%ecx) 这一句好像没有什么实际用处啊,并且为什么函数到最后也没有pop它?
作者: Anno_Domini    时间: 2010-12-06 23:52
回复 6# liwangli1983

哦,其实那一句不用pop,函数最后返回时就已经后退到原始栈帧了。但至于其用处还是不得而知,再请教一下c语言标准与实现,哪儿可以获得?
作者: liwangli1983    时间: 2010-12-07 08:35
回复  liwangli1983

谢谢你的指点,但还是有一个不明白的地方:
0x08048369 :    pushl  -0x4(%ecx) 这 ...
Anno_Domini 发表于 2010-12-06 23:49


嘛正常编译是有很多无用代码的,加上O2也许就优化掉了.
作者: liwangli1983    时间: 2010-12-07 08:36
回复  liwangli1983

哦,其实那一句不用pop,函数最后返回时就已经后退到原始栈帧了。但至于其用处还是 ...
Anno_Domini 发表于 2010-12-06 23:52



    C语言标准与实现到处可以下到啊,这个本来就只有电子版.
作者: rubylc_unix    时间: 2010-12-07 08:42
0x08048354 :    lea    0x4(%esp),%ecx
0x08048358 :    and    $0xfffffff0,%esp            ##让栈指针 ...
liwangli1983 发表于 2010-12-06 22:35



    学习了,不错。这个算是最简单的了吧{:3_189:}
作者: jhui66    时间: 2010-12-07 09:31
6楼很给力
作者: BJSH    时间: 2010-12-07 13:42
关于ECX的解释 有误。

ECX 保存了 传递给main函数的 ARGC的地址, 也相当于保存了原ESP, 不过要-0x4

pushl  -0x4(%ecx)   ; push到栈里的是调用函数的IP地址
作者: liwangli1983    时间: 2010-12-07 14:14
本帖最后由 liwangli1983 于 2010-12-07 14:33 编辑
关于ECX的解释 有误。

ECX 保存了 传递给main函数的 ARGC的地址, 也相当于保存了原ESP, 不过要-0x4

...
BJSH 发表于 2010-12-07 13:42



    没错确实搞乱了.如你所说push的是返回地址.这样最后一个pop后指针指向的正是返回地址,这时ret正好可以从esp中取出返回地址并回到上级.

    ecx保存的是argc的地址......昨晚写的时候手边没有纸笔,没画一下栈就写,果然错了不少地方
作者: Anno_Domini    时间: 2010-12-07 14:33
回复 13# BJSH
我想请教的是调用者指针的功能本身就由
lea    -0x4(%ecx),%esp
这一句指令来完成了,为什么还要pushl -0x4(%ecx) ?
作者: system888net    时间: 2010-12-07 18:07
本帖最后由 system888net 于 2010-12-07 18:11 编辑
回复  BJSH
我想请教的是调用者指针的功能本身就由
lea    -0x4(%ecx),%esp
这一句指令来完成了,为什么还要pushl -0x4(%ecx) ?Anno_Domini 发表于 2010-12-07 14:33


    lz 善于思考.
作者: system888net    时间: 2010-12-07 18:21
本帖最后由 system888net 于 2010-12-07 18:24 编辑

对于源代码到执行代码,编译器目的是可以实际正确的执行,因此方法上可以多种实现选择(目的上也有一些小分支), 对于lz说的这个例子,正常情况下没有  pushl -0x4(%ecx)  也没有影响.
参考另一个编译器的结果:
   注: 这个编译器参用了寄存器(edi,rsi)传递参数(argc,argv).

  1.         pushq        %rbp
  2.         movq        %rsp, %rbp
  3.         movl                %edi, -4(%rbp)
  4.         movq        %rsi, -16(%rbp)
  5.         movl                $0, %eax
  6.         leave
  7.         ret
复制代码

作者: system888net    时间: 2010-12-07 18:42
本帖最后由 system888net 于 2010-12-07 18:43 编辑
回复  BJSH
为什么还要pushl -0x4(%ecx) ?Anno_Domini 发表于 2010-12-07 14:33


对于lz这段特定的代码,lz 也可以理解为:  寄存器ecx也有可能用作其它用途(代码复杂的情况下),因此用  pushl -0x4(%ecx)  保存在一个固定的位置.
作者: BJSH    时间: 2010-12-07 19:45
回复  BJSH
我想请教的是调用者指针的功能本身就由
lea    -0x4(%ecx),%esp
这一句指令来完成了,为什么 ...
Anno_Domini 发表于 2010-12-07 14:33



我觉得没啥用处, 实际上也根本没用到。

gcc 4.4.3 的汇编:

.globl main
        .type   main, @function
main:
        pushl   %ebp
        movl    %esp, %ebp
        movl    $0, %eax
        popl    %ebp
        ret
        .size   main, .-main
        .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
        .section        .note.GNU-stack,"",@progbits
作者: Anno_Domini    时间: 2010-12-07 20:35
对于lz这段特定的代码,lz 也可以理解为:  寄存器ecx也有可能用作其它用途(代码复杂的情况下),因此用  p ...
system888net 发表于 2010-12-07 18:42

非常感谢你的解答,如果你能给出例子那就更好了。
我之前的编译环境是虚拟机中gcc 4.1.2,我重新在gcc 4.5.1(实体机)中编译代码的得:
0x08048394 <+0>:     push   %ebp
0x08048395 <+1>:     mov    %esp,%ebp
0x08048397 <+3>:     mov    $0x0,%eax
0x0804839c <+8>:     pop    %ebp
0x0804839d <+9>:     ret
或许这才是简洁的汇编码。




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2