Chinaunix首页 | 论坛 | 博客
  • 博客访问: 10782
  • 博文数量: 6
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 70
  • 用 户 组: 普通用户
  • 注册时间: 2015-03-28 15:46
文章分类
文章存档

2015年(6)

我的朋友

分类: LINUX

2015-04-17 21:24:00

前言:这个星期的内容有点多。这次的内容关于操作系统如何与程序打交道,也关于从高级语言如何变成二进制代码。在C primer plus中,很多语言实现的细节都没有讲出来,这也成为了学习计算机科学之路上的一个坑,而这个星期的课程,正是用来填坑的。

署名部分:
作者:彭家进
原创作品转载请注明出处 
《Linux内核分析》MOOC课程 ”

可执行程序是如何诞生的呢?
    1,编译器的预处理:(网上有很多很死板的定义,我的理解是:1,把#include后面的东西全部放到你的文件中;2,把#define后面的东西全部帮你替换掉;3,把你的全部注释给删除掉)
    2,编译器将其编译成汇编代码(从高级语言转换成低级语言)
    3,编译器将其编译成目标代码(关于目标代码以及链接的意义,请看下图《C Primer Plus》
    4,编译器将目标代码进行链接
    5,可执行文件由操作系统加载到内存(这就是本次博客的重点,下面几乎全部篇幅都是关于这个话题)
    
    图:从C到程序被执行
    
    代码:使用gcc编译的时候使用不同的参数得到各阶段的文件(已添加注释)

点击(此处)折叠或打开

  1. shiyanlou:~/ $ cd Code [9:27:05]
  2. shiyanlou:Code/ $ vi hello.c [9:27:14]
  3. shiyanlou:Code/ $ gcc -E -o hello.cpp hello.c -m32 [9:34:55]    //预处理
  4. shiyanlou:Code/ $ vi hello.cpp [9:35:04]
  5. shiyanlou:Code/ $ gcc -x cpp-output -S -o hello.s hello.cpp -m32 [9:35:21]     //汇编
  6. shiyanlou:Code/ $ vi hello.s [9:35:28]
  7. shiyanlou:Code/ $ gcc -x assembler -c hello.s -o hello.o -m32 [9:35:58]     //编译成目标文件
  8. shiyanlou:Code/ $ vi hello.o [9:38:44]
  9. shiyanlou:Code/ $ gcc -o hello hello.o -m32 [9:39:37]    //链接
  10. shiyanlou:Code/ $ vi hello [9:39:44]
  11. shiyanlou:Code/ $ gcc -o hello.static hello.o -m32 -static [9:40:21] //如果把程序需要的库全部放到程序自身的话,程序将会变得极大
  12. shiyanlou:Code/ $ ls -l [9:41:13]
  13. -rwxrwxr-x 1 shiyanlou shiyanlou 7292 3\u6708 23 09:39 hello
  14. -rw-rw-r-- 1 shiyanlou shiyanlou 64 3\u6708 23 09:30 hello.c
  15. -rw-rw-r-- 1 shiyanlou shiyanlou 17302 3\u6708 23 09:35 hello.cpp
  16. -rw-rw-r-- 1 shiyanlou shiyanlou 1020 3\u6708 23 09:38 hello.o
  17. -rw-rw-r-- 1 shiyanlou shiyanlou 470 3\u6708 23 09:35 hello.s
  18. -rwxrwxr-x 1 shiyanlou shiyanlou 733254 3\u6708 23 09:41 hello.static //包含了所有需要的库文件的程序大小是不加库文件的程序的大概101倍
    
什么是目标文件呢?
在老师的超文本课件中,有这样一个PPT,里面有说到目标文件的定义,请看图。
    
在Linux中,有三种文件可以被称为目标文件,他们分别是:(你看到这篇博客的时候答题已经关闭了,所以这不算泄题~)


关于目标文件及链接的更多资料,老师已经给得很详细,需要了解更多请移步

进行了解,推荐看这个PPT,因为说得比较简洁直观。

ELF是个什么东西呢(总不会是精灵吧)?
ELF其实是Executable and Linkable Format,翻译过来就是“可执行的而且可链接的格式”,看起来很抽象。老师的PPT上面对此有个定义。

(wokanbudong)那ELF究竟是什么东西呢?
在我看来,ELF是告诉你(或者是操作系统)这个程序的身份信息的东西。有图:

这个ELF头告诉了我们很多东西,比如可运行在什么操作系统上,可运行在什么架构上,版本等等。还有一些和操作系统打交道的东西,譬如哪些部分是代码段啦,哪些部分是数据段啦,都要统统告诉操作系统,不然你想操作系统怎么看得懂你的文件呢?(下图又是一段抽象的定义)




那么ELF有什么用呢?
当用户要求运行某程序的时候,操作系统系统首先会读取ELF文件里面的信息,并且拷贝该文件的段到一个虚拟的内存段。图:

具体会怎么复制呢,老师的PPT有一个简化掉的代码模型:



操作系统如何加载可执行程序呢?
老师举了一个ls作为例子,ls其实只是一个可执行程序,shell(控制台)接受用户输入的命令,并将命令传递给被调用的命令。shell本身并不限制命令行参数的个数,命令含参数的个数受限于命令本身。

命令行参数和环境变量是如何保存和传递的呢?调用新程序的时候,所有的东西都是空白的,那些参数根本就没有出现,那么这究竟是怎么做到的呢?
答案是在初始化新进程堆栈时拷贝进去:
    
新的用户堆栈在建立时就会初始化好之前要传递的参数:
       

当用户要求调用创建新进程时,shell会调用sys_execve进行新进程的调用,进而调用do_execve,然后调用do_execve_common,再调用exec_binprm:
    
其中search_binary_handler会扫描文件的头部,了解程序的ELF,并分配相关资源。

这部分是被观察者:

这部分是观察者:


老师说这个start_thread比较有意思,有意思在哪里呢?

原来他把新进程的启动地址放到寄存器里面去了。程序的功能和程序的名字一样:start_thread,即为启动线程)


好了,前面说了那么多,这里不贴上整个代码的跟踪过程,是对不起各位看官的。下面是源代码的执行演示。
1,系统调用的入口:

2,do_execve:

3,do_execve_common:(这里不贴完整代码了,主要关注这里调用的两个函数就好)

4,exec_binprm:

5,search_binary_handler:


load_elf_binary会检测ELF格式的文件
ELF可执行文件会被默认映射到0x8048000这个地址。


这个是检测动态链接的程序:

这里要说明的是,对于静态链接的文件,elf_entry是新程序的执行起点。

好了,理论分析完毕,是时候实战了。

实验部分:
1,搭建环境


2,查看test.c(VIM新命令get:Shift+G)看到新增了一行代码:


3,去看Exec是怎样的呢?

这段代码与fork基本一样,但是增加了execlp。

4,打开hello.c,查看内容



5,打开Makefile,查看修改的地方(这里忘记截图了)。

6,编译并且运行。


7,执行exec命令


8,搭建调试环境,这里一笔带过。

9,使用exec命令,QEMU触碰到断点。


10,进入了SyS_execve 


11,使用list查看,这和之前贴出的代码是一样的。


12,很快就到了第二个断点,使用list查看代码:


13,到达start_thread并且使用po new_ip查看内容。这里拓展是new_ip是返回用户态的第一条指令的地址。


14,使用readelf -h hello查看hello程序的入口点,看到和new_ip是一样的。



15,进入到代码里头,发现程序正在修改寄存器。


实验到此结束。

后记:我找到了这张图!
阅读(485) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~