Linux内核分析之可执行程序的装载和启动

一、内容分析

1.可执行文件的创建

(1)预处理阶段

  预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行相应的转换,预处理过程还会删除程序中的注释和多余的空白字符。其中预处理指令主要包括以下四个方面:

  宏定义指令——预处理过程会把源代码中出现的宏标识符替换成宏定义时的值,常用的两种宏定义:

//声明一个标识符,全部用大写字母来定义宏
#define MAX_NUM 10

//带参数的#define指令(宏函数)
#define Cube(x)  ((x)*(x))
int i,num=1;
i=Cube(num);

   条件编译指令——定义不同的宏来决定编译程序对哪些代码进行处理,条件编译指令将决定哪些代码被编译,而哪些是不被编译的。

  头文件包含指令——#include预处理指令的作用是在指令处展开被包含的文件。程序中包含头文件有两种格式:#include <my.h>和#include "my.h"

  特殊符号——预编译程序可以识别一些特殊的符号。预编译程序对于在源程序中出现的这些串将用合适的值进行替换,__FILE__,__LINE__,__TIME__ 等。

  上述阶段对应 gcc -E -o hello.cpp hello.c -m32

(2)编译阶段

  在这个阶段中,Gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,Gcc把代码翻译成汇编语言。

  对应gcc -x cpp-output -S -o hello.s hello.cpp -m32

(3) 汇编阶段

  把编译阶段生成的”.s”文件转成目标文件,得到一个二进制文件。

  对应 gcc -x assembler -c hello.s -o hello.o -m32

(4)链接阶段

  函数库一般分为静态库和动态库两种。静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。动态库在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库。动态库一般后缀名为”.so”,Gcc在编译时默认使用动态库。

  gcc -o hello hello.o -m32 (动态编译)

  gcc -o hello.static hello.o -m32 -static(静态编译)

2.ELF可执行文件

(1)目标文件有三类:

  可重定位文件——文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。

可执行文件:一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。

共享object文件:一个共享object文件保存着代码和合适的数据,用来被两个链接器链接,分别是连接编辑器[ld(SD_CMD)]和动态链接器。

(2)ELF文件格式

  用readelf -h hello看看elf文件内容。

ELF文件头

  ELF文件默认从0x8048000开始加载,上图的ELF头中Entry point address的内容为程序实际入口,当启动一个刚加载过可执行文件的进程时,就从此处执行。

3.可执行程序的执行环境,动态链接方式

  实验直接使用execve系统调用。$ ls -l /usr/bin 列出/usr/bin下的目录信息。

  Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身。例如,int main(int argc, char *argv[]);又如, int main(int argc, char *argv[], char *envp[])。

  Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数,int execve(const char * filename,char * const argv[ ],char * const envp[ ]),库函数exec*都是execve的封装例程。

  动态链接分为可执行程序装载时动态链接和运行时动态链接。装载时动态链接是在将功能模块读入内存时把动态库中调用到的相关模块的内容载入内存。运行时动态链接是在执行程序调用到模块内容时再将动态库中的相应模块载入到内存。

二、实验内容

  更新menu后用test.c覆盖test_exec.c

  然后打开test.c,可以看到添加了exec命令,执行一个程序的功能。其函数内容为

int Exec(int argc, char * argv[])
{
    int pid;
    /* fork another process */
    pid = fork();
    if (pid<0)
    {
        /* error occurred */
        fprintf(stderr,"Fork Failed!");
        exit(-1);
    }
    else if (pid==0)
    {
        /*   child process   */
        execlp("/bin/ls","ls",NULL);
    }
    else
    {
        /*     parent process  */
        /* parent will wait for the child to complete*/
        wait(NULL);
        printf("Child Complete!");
        exit(0);
    }
}

  在Makefile中做了修改。编译的时候执行了hello.c,并把init 和hello放到了rootfs.img目录下,所以在执行exec命令的时候就相当于自动了加载了hello这个程序。

  gdb调试前的准备可总结为:
$ cd LinuxKernel/

$ rm menu -rf

$ git clone https://github.com/mengning/menu.git

$ move test_exec.c test.c

//查看Makefile文件可知道实验采用的是静态编译
rootfs:
        gcc -o init linktable.c menu.c test.c -m32 -static -lpthread
        gcc -o hello hello.c -m32 -static
  之后通过gdb进行跟踪分析。    设置b sys_execve  ,b load_elf_binary 等断点后,在MenuOS中输入exec进行调试分析。gdb首先在sys_execve处停下来。  然后是start_thread函数。  此时可以通过po new_ip和新窗口的 readelf -h helloc查看入口地址是否一致。  可以看到hello的入口地址和new_ip的值都是0x8048d0a 。说明对hello程序链接到了执行程序中。  继续单步执行可以看到把new_ip复制给了regs-> ip,之后继续执行看可以看到MenuOS界面输出了相应的hello world。三、总结  可执行文件的创建包括预处理、编译、汇编、链接四个阶段。  学习并了解了ELF文件的格式,了解了ELF文件头、段头表、text节等各个组成部分。  通过对源码的追踪分析,理解了可执行程序加载的大致流程。当执行到execve系统调用时,陷入内核态,用execve加载的可执行文件覆盖当前进程的可执行程序,当execve系统调用返回时,返回新的可执行程序的执行起点。如果是静态链接,elf_entry指向可执行文件规定的头部(main函数对应的位置0x8048***)如果需要依赖动态链接库,elf_entry指向动态链接器的起点。动态链接主要是由动态链接器ld来完成的。  可执行文件是一个普通的文件,它描述了如何初始化一个新的执行上下文,也就是如何开始一个新的计算。可执行文件类别有很多,在内核中有一个链表,在init的时候会将支持的可执行程序解析程序注册添加到链表中,那么在对可执行文件进行解析时,就从链表头开始找,找到匹配的处理函数就可以对其进行解析。 在shell中启动一个可执行程序时,会创建一个新进程,它通过覆盖父进程(也就是shell进程)的进程环境,并将用户态堆栈清空,获得所需要的执行上下文环境。   命令行参数和环境变量会通过shell传递给execve,excve通过系统调用参数传递,传递给sys_execve,最后sys_execve在初始化新进程堆栈的时候拷贝进去。   load_elf_binary->start_thread(…)通过修改内核堆栈中EIP的值作为新程序的起点。 如果新程序的动态链接的,那么就需要加载所需要的库函数,动态连接器ld会负责加载过程,动态链接库的装载过程类似于一个图的广度优先遍历过程,装载完成后,ld将CPU控制权交给可执行程序,继续执行可执行程序。刘帅原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
时间: 2024-11-14 11:59:44

Linux内核分析之可执行程序的装载和启动的相关文章

LINUX内核分析第七周学习总结——可执行程序的装载

LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 (一)预处理.编译.链接和目标文件的格式 1.可执行程序是怎么得来的 2.目标文件的格式ELF 3.静态链接的ELF可执行文件和进程的地址空间 (二)可执行程序.共享库和动态加载 1.装载可执行程序之前的工作 2.装载时动态链接和运行时动态链接应用举例 (三)

《linux 内核分析》 第7周 可执行程序的装载

王一+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 一.理解编译链接的过程和ELF可执行文件格式: 1.编译链接过程 编译器预处理成cpp文件 gcc -E -o q.cpp q.c -m32 汇编器编译成汇编代码  -x是将编译cpp输出文件,输出汇编文件 gcc -x cpp-output -S -o hello.s hello.cpp -m32 汇编代码编译成二进制目标文件  -x将汇编编译为object

20135327郭皓--Linux内核分析第七周 可执行程序的装载

第七周 可执行程序的装载 郭皓 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.com/course/USTC-1000029000 一.预处理,编译,链接和目标文件格式 1.可执行程序是怎么得来的 c代码->预处理->汇编代码->汇编器->目标代码->链接成可执行文件->加载到内核执行 2.目标文件的格式ELF 符号修饰标准.变量内层布局.函数调用方式等这些跟可执行代码二进制兼容性相关的内容称为ABI

20135201李辰希 《Linux内核分析》第七周 可执行程序的装载

李辰希  原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.预处理.编译.链接和目标文件的格式 1.可执行程序是怎么得来的 编译链接的过程 1.预处理阶段 gcc -E -o XX.cpp XX.c -m32 XX.cpp是预处理文件 2.编译器生成汇编代码阶段 gcc -x cpp-output -S -o hello.s hello.cpp -m32 XX.s是汇编代码 3.汇编器

20135239 益西拉姆 linux内核分析 可执行程序的装载

益西拉姆 + 原创作品请勿转载 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ” week 7 可执行程序的装载 1.预处理.编译.链接和目标文件的格式 从c语言到可执行程序的由来过程 可执行文件的创建——预处理.编译和链接 以helloworld为例 -s assembler 汇编 gcc -o hello hello.o -m32 是把hello.o链接成可执行文件. ELF格式的文件是怎么回事? v

Linux内核分析:实验七--Linux内核如何装载和启动一个可执行程序

刘畅 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 写在前面 本次实验着力分析Linux内核装载和启动一个可执行程序的过程,其中包括可执行文件格式的分析.可执行文件的装载和链接的过程,并通过GDB跟踪execve系统调用来梳理Linux系统加载可执行程序的过程. 可执行文件的格式分析 相对于其它文件类型,可执行文件可能是一个操作系统中最重要的文件类型,因为它们是完成操作的真正执行者.可

可执行程序的装载和启动---linux内核学习笔记(七)

内容一:实验报告相关说明 真实姓名 谢润帮 原创作品转载请注明出处  所学课程:<Linux内核分析>MOOC课程   链接:http://mooc.study.163.com/course/USTC-1000029000 内容二:可执行文件的创建(自己本身对这块不熟,是通过查资料来学习的,篇幅有点多) 2.1 预处理阶段 预处理过程读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行相应的转换,预处理过程还会删除程序中的注释和多余的空白字符. 其中预处理指令主要包括以下四个方面: 2

《Linux内核分析》课程第七周学习总结

姓名:何伟钦 学号:20135223 ( *原创作品转载请注明出处*) ( 学习课程:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-100002900 学习内容:Linux内核如何装载和启动一个可执行程序 理解编译链接的过程和ELF可执行文件格式: 编程使用exec*库函数加载一个可执行文件,动态链接分为可执行程序装载时动态链接和运行时动态链接,编程练习动态链接库的这两种使用方式: 使用gdb跟踪分析一个execve系统调用内核处

Linux内核分析课程总结

blog链接: 计算机是如何工作的 操作系统是如何工作的 构造一个简单的linux系统MenuOS 系统调用的工作机制(上) 系统调用的工作机制(下) 进程的描述与创建 可执行程序的装载 进程的切换与系统的一般执行过程 学习总结: 收获: 通过这门课程,我系统性地.深入地学习了操作系统内核的相关结构和设计原理,与之前不同,<linux内核分析>带领我们从linux内核源代码着手,真正地从根本上去了解linux的工作机制.在这个过程中我尝试了自己去构造一个简单的linux系统并编写和执行了一些简