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

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

内容提要

一、得到一个可执行程序

1. 预处理、编译、链接

gcc hello.c -o hello.exe
  • gcc编译源代码生成最终可执行的二进制程序,GCC后台隐含执行了四个阶段步骤。

      预处理 => 编译 => 汇编 => 链接
  • 预处理:编译器将C源代码中包含的头文件编译进来和执行宏替换等工作。

      gcc -E hello.c -o hello.i
  • 编译:gcc首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言。

      gcc –S hello.i –o hello.s
    -S:该选项只进行编译而不进行汇编,生成汇编代码。
  • 汇编:把编译阶段生成的.s文件转成二进制目标代码.

      gcc –c hello.s –o hello.o
  • 链接:将编译输出.o文件链接成最终的可执行文件。

      gcc hello.o –o hello
  • 运行:若链接没有-o指明,则生成可执行文件默认为a.out

      ./hello

2. 目标文件格式

(1)文件格式

  • a.out是最早的可执行文件格式

注:ABI——应用程序二进制接口

(2)ELF分类

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

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

  • 共享文件:保存着代码和合适的数据,用来被下面的两个链接器链接。

    • 第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。
    • 第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。
  • object文件参与程序的链接(创建)和执行。

(3)ELF头

  • 查看ELF文件的头部:readelf

  • 在文件开始保存了:

      - 路线图:描述该文件组织情况
    - 程序头表:告诉系统如何创建一个进程的内存映像
    - section头表:描述文件的section信息。(每个section在这个表中有一个入口,给出该section信息)
  • 当创建或增加一个进程映像时,系统在理论上将拷贝一个文件的段到一个虚拟的内存段。

3. 静态链接的ELF可执行文件和进程的地址空间

  • 入口点:程序从0x804800开始。
  • 可执行文件加载到内存中开始执行的第一行代码。
  • 一般静态链接将会把所有代码放在同一个代码段。
  • 动态连接的进程会有多个代码段。

二、可执行程序的执行环境

1. 命令行参数和shell环境

  • 列出/usr/bin下的目录信息

      $ ls -l /usr/bin
  • Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身

      int main(int argc, char *argv[], char *envp[])
  • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

      int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
  • 库函数exec*都是execve的封装例程

2. 命令行参数和shell环境变量的保存与传递

shell程序 => execve => sys_execve

3. 可执行程序动态链接

(1)动态链接

  • 关注:load_elf_binary

      load_elf_binary(...)
    {
    ...
    kernel_read();//其实就是文件解析
    ...
    //映射到进程空间 0x804 8000地址
    elf_map();//
    ...
    if(elf_interpreter) //依赖动态库的话
    {
    ...
    //装载ld的起点 #获得动态连接器的程序起点
    elf_entry=load_elf_interp(...);
    ...
    }
    else //静态链接
    {
    ...
    elf_entry = loc->elf_ex.e_entry;
    ...
    }
    ...
    //static exe: elf_entry: 0x804 8000
    //exe with dyanmic lib: elf_entry: ld.so addr
    start_thread(regs,elf_entry,bprm->p);
    }
  • 实际上,装载过程是一个广度遍历,遍历的对象是“依赖树”。

  • 主要过程是动态链接器完成、用户态完成。

(2)装载时动态链接

/*准备.so文件*/
shlibexample.h (1.3 KB) - Interface of Shared Lib Example
shlibexample.c (1.2 KB) - Implement of Shared Lib Example /*编译成libshlibexample.so文件*/
$ gcc -shared shlibexample.c -o libshlibexample.so -m32 /*使用库文件(因为已经包含了头文件所以可以直接调用函数)*/
SharedLibApi();

(3)运行时动态链接

dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Example
dllibexample.c (1.3 KB) - Implement of Dynamical Loading Lib Example /*编译成libdllibexample.so文件*/
$ gcc -shared dllibexample.c -o libdllibexample.so -m32 /*使用库文件*/
void * handle = dlopen("libdllibexample.so",RTLD_NOW);//先加载进来
int (*func)(void);//声明一个函数指针
func = dlsym(handle,"DynamicalLoadingLibApi");//根据名称找到函数指针
func(); //调用已声明函数

(4)运行

$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH=$PWD
/*将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。*/

三、可执行程序的装载

1. sys_execve内核处理过程

(1)新的可执行程序起点

  • 一般是地址空间为0x8048000或0x8048300

(2)execve与fork

execve和fork都是特殊一点的系统调用:一般的都是陷入到内核态再返回到用户态。
  • fork两次返回,第一次返回到父进程继续向下执行,第二次是子进程返回到ret_from_fork然后正常返回到用户态。

  • execve执行的时候陷入到内核态,用execve中加载的程序把当前正在执行的程序覆盖掉,当系统调用返回的时候也就返回到新的可执行程序起点。

execve
- 执行到可执行程序 -> 陷入内核
- 构造新的可执行文件 -> 覆盖掉原可执行程序
- 返回到新的可执行程序,作为起点(也就是main函数)
- 需要构造其执行环境;
  • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数,先函数调用参数传递,再系统调用参数传递。

(3)静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时不同

  • 静态链接:elf_entry指向可执行文件的头部,一般是main函数,是新程序执行的起点。
  • 动态链接:elf_entry指向ld(动态链接器)的起点,加载load_elf_interp

2. 庄周梦蝶

庄周(调用execve的可执行程序)入睡(调用execve陷入内核),醒来(系统调用execve返回用户态)发现自己是蝴蝶(被execve加载的可执行程序)。

3. 动态链接的可执行程序的装载

(1)可执行文件开始执行的起点在哪里?如何才能让execve系统调用返回到用户态时执行新程序?

  • 修改int 0x80压入内核堆栈的EIP,通过修改内核堆栈中EIP的值作为新程序的起点。

(2)Linux内核是如何支持多种不同的可执行文件格式

static struct linux_binfmt elf_format//声明一个全局变量 = {
.module = THIS_MODULE,
.load_binary = load_elf_binary,//观察者自动执行
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
}; static int __iit init_elf_binfmt(void)
{n
register_binfmt(&elf_format);//把变量注册进内核链表,在链表里查找文件的格式
return 0;
}

(3)动态链接

  • 可执行程序需要依赖动态链接库,而这个动态链接库可能会依赖其他的库,这样形成了一个关系图——动态链接库会生成依赖树。
  • 依赖动态链接器进行加载库并进行解析(这就是一个图的遍历),装载所有需要的动态链接库;之后ld将CPU的控制权交给可执行程序
  • 动态链接的过程主要是动态链接器在起作用,而不是内核完成的。

四、实验部分

1. GDB跟踪sys_execve内核函数处理过程

  • 准备内核

  • exec函数运行

  • test.c文件,更新命令部分

  • 在exec函数中执行了动态链接代码

  • makefile中添加对hello程序的处理代码

  • gdb调试,设置断点到sys_exec

  • 进入函数内部发现调用了do_execve()函数

  • gdb调试过程中,执行到start_ thread,查看new_ ip的内容

  • readelf -h hello.c查看入口地址为0x8048d0a与new_ ip查看到的相同。

2. sys_execve的内部处理过程

  • 系统调用的入口:do_execve

      return do_execve(getname(filename), argv, envp);
  • 转到do _ execve _ common函数

      return do_execve_common(filename, argv, envp);
    
      file = do_ open_exec(filename);//打开要加载的可执行文件,加载它的文件头部。
    
      bprm->file = file;
    bprm->filename = bprm->interp = filename->name;
    //创建了一个结构体bprm,把环境变量和命令行参数都copy到结构体中;
  • exec_binprm:

      ret = search_binary_handler(bprm);//寻找此可执行文件的处理函数
    
      在其中关键的代码:
    list_for_each_entry(fmt, &formats, lh);
    retval = fmt->load_binary(bprm);
    //在这个循环中寻找能够解析当前可执行文件的代码并加载出来
    //实际调用的是load_elf_binary函数
  • 文件解析相关模块:核心的工作就是把文件映射到进程的空间,对于ELF可执行文件会被默认映射到0x8048000。

  • 需要动态链接的可执行文件先加载链接器ld​(load _ elf _ interp 动态链接库动态链接文件),动态链接器的起点。如果它是一个静态链接,可直接将文件地址入口进行赋值。

LINUX内核分析第七周学习总结:可执行程序的装载的更多相关文章

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

    LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...

  2. LINUX内核分析第七周学习总结

    LINUX内核分析第七周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...

  3. Linux内核分析第七周学习笔记——Linux内核如何装载和启动一个可执行程序

    Linux内核分析第七周学习笔记--Linux内核如何装载和启动一个可执行程序 zl + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study. ...

  4. Linux内核分析——第七周学习笔记20135308

    第七周 可执行程序的装载 一.预处理.编译.链接和目标文件的格式 1.可执行程序是怎么来的 C代码—>预处理—>汇编代码—>目标代码—>可执行文件 .asm汇编代码 .o目标码 ...

  5. LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程

    LINUX内核分析第八周学习总结——进程的切换和系统的一般执行过程 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/c ...

  6. Linux内核分析 第七周 可执行程序的装载

    张嘉琪 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 Linux内核分析 第七 ...

  7. LINUX内核分析第六周学习总结——进程的描述与创建

    LINUX内核分析第六周学习总结--进程的描述与创建 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc ...

  8. LINUX内核分析第五周学习总结——扒开系统调用的“三层皮”(下)

    LINUX内核分析第五周学习总结--扒开系统调用的"三层皮"(下) 标签(空格分隔): 20135321余佳源 余佳源 原创作品转载请注明出处 <Linux内核分析>M ...

  9. LINUX内核分析第八周学习总结

    LINUX内核分析第八周学习总结 标签(空格分隔): 20135328陈都 陈都 原创作品转载请注明出处 <Linux内核分析>MOOC课程 http://mooc.study.163.c ...

随机推荐

  1. C++ Primer : 第十四章 : 重载运算与类型转换之重载运算符

    重载前须知 重载运算符是特殊的函数,它们的名字由operator和其后要重载的运算符号共同组成. 因为重载运算符时函数, 因此它包含返回值.参数列表和函数体. 对于重载运算符是成员函数时, 它的第一个 ...

  2. 强大的wget

    转载自:http://www.cnblogs.com/lidp/archive/2010/03/02/1696447.html 需要下载某个目录下面的所有文件.命令如下 wget -c -r -np ...

  3. 论文阅读之:Photo-Realistic Single Image Super-Resolution Using a Generative Adversarial Network

    Photo-Realistic Single Image Super-Resolution Using a Generative Adversarial Network  2016.10.23 摘要: ...

  4. PHP、JAVA、C#、Object-C 通用的DES加密

    PHP.JAVA.C#.Object-C 通用的DES加密 PHP: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ...

  5. How to Allow MySQL Client to Connect to Remote MySql

    How to Allow MySQL Client to Connect to Remote MySQ By default, MySQL does not allow remote clients ...

  6. ios实现屏幕旋转的方法

    1.屏蔽AppDelegate下面的屏幕旋转方法 #pragma mark - 屏幕旋转的 //- (UIInterfaceOrientationMask)application:(UIApplica ...

  7. AttributeTargets 枚举

    AttributeUsage AttributeTargets 在C#的类中,有的类加上了[AttributeUsage(AttributeTargets.Property)]这个是起什么作用的呢?A ...

  8. C++中默认构造函数中数据成员的初始化

    构造函数的任务是初始化数据成员的,在类中,如果没有显示定义任何构造函数,编译器将为我们创建一个构造函数,称为合成的默认构造函数,合成的默认构造函数使用与变量初始化相同的规则来初始化成员.即当类中的数据 ...

  9. SQL Server 2008 数据库镜像部署实例之一 数据库准备

    SQL Server 2008 数据库镜像部署实例之一 数据库准备 一.目标 利用Sql Server 2008 enterprise X64,建立异步(高性能)镜像数据库,同时建立见证服务器实现自动 ...

  10. RPC远程过程调用协议

    最近学习Hadoop.Hbase.Spark及Storm原理,经常会出现RPC这样的传输术语,为了更好地理解,将知识点详细的整理下吧~ RPC-----它是一种通过网络从远程计算机程序上请求服务,而不 ...