我是靠谱客的博主 懵懂宝贝,最近开发中收集的这篇文章主要介绍程序怎么跑?(1)--linux 课程学习笔记,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

要清楚程序怎么执行,首先有两个基础的过程需要了解,一个是函数怎么被调用,另一个是函数怎么返回,本文将通过一个简单的例子对这两个过程进行简单讲解。

以下是一段简单的c代码。
 
首先我们利用如下命令对程序进行编译
gcc –S –o main.s main.c -m32
对于命令中选项的解释
-S
以下是man gcc 中和-S有关的说明
-S  Stop after the stage of compilation proper; do not assemble. The
    output is in the form of an assembler code file for each non-assembler input file specified.
    -c
     -S
     -E  If any of these options is used, then the linker is not run, and
         object file names should not be used as arguments.

简而言之,-S选项要求gcc将代码编译生成汇编代码就停止,并不进行汇编代码转机器码的过程(assemble),连接器(linker)也不工作。

-o

output, 输出的意思。用户自定义输出文件的文件名。

-m32

以下是man gcc 中和-m32有关的说明
   The -m32 option sets "int", "long", and pointer types to 32 bits, and generates code that runs on any i386 system.
加入这个选项使得gcc生成运行在32位系统的代码

        上面命令输出main.s这个文件,这个文件是c代码对应的汇编代码。下面是main.s文件内容,里面的编译辅助信息已经全部删除。



下面我们手动模拟这段代码的执行过程,以研究函数调用和返回的过程。

假设这是代码运行前ebp,esp,eip,eax的状态,以及栈的内容。


1.进入到main函数后,先将ebp寄存器内容压栈,然后将esp内容送到ebp,这时ebp和esp同时指向地址为6的内存。内存6保存着main函数执行前ebp的内容。


2.然后执行两条指令,subl $4, %esp  movl $2, (%esp), 这两条指令的运行结果如下图,相当于push $2。对照c代码,main函数里面只有一个地方出现了数字2,也就是调用f函数的时候,2作为参数传递给f函数。而在这里,做完将2压栈操作的后一条就是call f,就是对f函数的调用,所以我们可以猜测,函数调用时,函数的参数是通过压栈的方式实现的,这个猜测我们可以通过f函数中对g函数的调用证实是正确的。


3.call指令做具体做的事情是将当前的eip压栈,然后将eip值设置为被调用的代码地址。在这里,被调用的f函数行数是9,所以eip变为9;在执行call f时,eip指向的是call f 下一条指令,也就是23,所以被压栈的eip的值是23。值得注意的是,在这里代码的地址我用了行号代替,这只是为了方便说明。实际上eip里面存放的应该是内存地址,每次执行指令有eip的自增并不是增加1,而是根据指令长度而变化,以保证eip总是指向下一条指令的地址。


4.保存main函数的ebp,并开辟自己的运行栈


5.栈定往下跑一格


6..把之前main压栈的2取出来放到eax寄存器中,证明当时main对2的压栈操作是在传参数


7.把eax的内容放到栈顶。这三条语句的执行效果就是 push 8(%ebp),对照相应的c代码,f函数直接


8.调用g函数,操作和main函数调用f函数过程一样。


9.准备执行g函数


10.保存f函数的ebp,设置自己的栈


11.取出f函数之前压栈的参数2


12.和1相加,结果放在eax


13.栈顶元素存放到ebp,对应的栈顶增加4


14.ret指令作用是 pop eip,这个eip是执行call指令时被压栈的,他指向call的下一条指令。在这里,他指向f函数里面的leave


15.leave指令作用为将esp设置为ebp,然后pop ebp。在这条指令执行前,ebp存放的是f函数的栈底,通过9,10 行汇编代码我们看到,f函数的ebp指向的内存单元存放的是main的ebp,所以leave指令做的事情其实是将ebp设置成main的ebp。那么为什么g函数返回前没有leave指令呢?这时因为g函数返回前esp就是ebp,所以不需要将esp设置成ebp的过程,可以直接pop ebp。


16.运行ret,操作同g函数的返回


17.这里对应了c代码中 return f(2)+4; 注意到在g函数中g函数将运行结果直接放到eax后就不再操作eax,f函数对eax也没有操作,所以这部分eax加4前eax内容就是f函数返回的内容,加4后,eax内容作为main的返回


18.同样是leave


19.同样是ret


20. 我们重新看一下这段代码运行前的寄存器和堆栈内容



至此,本文的两个问题有答案了

一,函数调用是个什么过程

答:函数调用前,函数参数由调用者通过压栈的方式存放;调用时,eip被压栈保存;被调用函数执行第一步是将当前的ebp保存,并将当前栈顶作为自己的设置为自己的栈底。

二,函数返回是个什么过程

答:函数返回时将ebp恢复为保存在栈中的ebp,返回结果保存在eax中给调用者使用,eip恢复到调用前保存的eip,使得程序从调用前的环境继续执行。



邱聪  原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000















最后

以上就是懵懂宝贝为你收集整理的程序怎么跑?(1)--linux 课程学习笔记的全部内容,希望文章能够帮你解决程序怎么跑?(1)--linux 课程学习笔记所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(45)

评论列表共有 0 条评论

立即
投稿
返回
顶部