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.汇编器生成目标代码阶段

gcc -x assembler -c hello.s -o hello.o -m32 
XX.o是目标代码

4.链接器生成可执行文件阶段

gcc -o hello.static hello.c -m32 -static

5.查看hello和hello.static的区别:

ls -l

6.补充:

  1. hello和hello.o都是ELF文件
  2. .static文件会将所有用到C库文件都放到这一个可执行程序中(所以占用空间比较多)

二.目标文件的格式ELF

1.常见的目标文件格式:

  • A.out -> COFF -> PE(windows)/ELF(linux)

2.目标文件也叫ABI:应用程序二进制接口

3.ABI是二进制兼容:目标文件已经适应某种CPU体系结构上的二进制指令

4.ELF格式中3种目标文件:

  • 可重定位目标文件。包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件
  • 可执行目标文件:包含二进制代码和数据,其形式可以被直接拷贝到存储器并执行
  • 共享目标文件:一种特殊类型的可重定位目标文件,可以在加载或运行时被动态地加载到存储器并链接

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

ELF可执行文件加载到进程的地址空间时:

  • 默认加载起始地址是0x8048000
  • ELF头部大小不同,程序入口点也将不同,程序入口在头部有定义:Entry point address,即是可执行文件加载到内存中开始执行的第一行代码
  • 一般静态链接会将所有代码放在一个代码段
  • 动态链接的进程会有多个代码段,更复杂

三.可执行文件、共享库和动态链接

  1. 可执行程序的执行环境

    • 一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。
    • Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身
      • 例如,int main(int argc, char *argv[])
      • 又如, int main(int argc, char argv[], char envp[])//envp是shell的执行环境
    • Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
      • int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

2.命令行参数和环境串都放在用户态堆栈中

3.命令行参数和环境变量是如何保存和传递的?

创建的子进程完全复制父进程,调用execve时加载的可执行程序把子进程环境覆盖掉了,子进程用户态堆栈也被清空了,命令行参数和环境变量是如何保存在新的可执行程序的用户态堆栈呢?

参数传递过程:shell程序 -> execve系统调用 -> sys_execve 内核处理函数在初始化新程序堆栈时拷贝进去

先函数调用参数传递,再系统调用参数传递:调用execve时,参数压在shell程序当前进程的堆栈上,加载完新的可执行程序时被清空了,内核又创建了新的进程的用户态堆栈,把参数拷贝进去。

4.装载时动态链接和运行时动态链接应用

  1. 动态链接分为可执行程序装载时动态链接和运行时动态链接(大部分使用前者);
  2. 举例1(共享库的动态链接)
    • 准备.so文件(在Linux下动态链接文件格式,在Windows中是.dll)

      #ifndef _SH_LIB_EXAMPLE_H_
      #define _SH_LIB_EXAMPLE_H_
      
      #define SUCCESS 0
      #define FAILURE (-1)
      
      #ifdef __cplusplus
      extern "C" {
      #endif
      /*
      * Shared Lib API Example
      * input : none
      * output    : none
      * return    : SUCCESS(0)/FAILURE(-1)
      *
      */
      int SharedLibApi();//内容只有一个函数头定义
      
      #ifdef __cplusplus
      }
      #endif
      #endif /* _SH_LIB_EXAMPLE_H_ */
      /*------------------------------------------------------*/
      
      #include <stdio.h>
      #include "shlibexample.h"
      
      int SharedLibApi()
      {
          printf("This is a shared libary!\n");
          return SUCCESS;
      }/* _SH_LIB_EXAMPLE_C_ */
    • 编译成.so文件
      $ gcc -shared shlibexample.c -o libshlibexample.so -m32

5.编译

1.$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32  #这里只提供shlibexample的-L(库对应的接口头文件所在目录,也就是path to your dir)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并没有提供dllibexample的相关信息,只是指明了-ldl
2.$ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件copy到默认路径下。
3.$ ./main
4.This is a Main program!
5.Calling SharedLibApi() function of libshlibexample.so!
6.This is a shared libary!
7.Calling DynamicalLoadingLibApi() function of libdllibexample.so!
8.This is a Dynamical Loading libary!

四.可执行程序的装载

1.特殊系统调用:

  • fork:两次返回,第一次父进程返回,子进程也返回,到特定的点:从ret_ from_fork开始执行然后返回用户态。
  • execve:陷入到内核态调用execve加载可执行程序,把当前的程序覆盖掉了,返回的是一个新的可执行程序。返回的是新程序的执行起点,一般是main函数,要构建好执行环境。

2.sys_execve内部会解析可执行文件格式:

do_ execve -> do_ execve_ common -> exec_binprm

3.sys_execve的内部处理过程

do_ execve(getname(filename),argv,envp) -> do_ execve_ common(filename,argv,envp) 中:

  • 1、do_ open_ exec 打开要加载的可执行文件
  • 2、创建结构体bprm,把参数copy进结构体
  • 3、exec_ binprm:对可执行文件的处理过程,关键代码search_ binary_ handler寻找可执行文件,这里面关键list_ for_ each_ entry(fmt, &formats, lh)在链表中寻找能够解析ELF格式的内核模块,fmt->load_ binary(bprm)找到fmt这个链表结点,指向能够解析ELF格式的模块,加载处理函数,执行的应该是load_ elf_binary,见上

load_ elf_binary:严格的解析ELF格式文件,核心工作:把ELF可执行文件映射到进程的地址空间(默认加载起始地址是0x8048000)

start_ thread(regs,elf_ entry,bprm->p):

  • 对于静态链接的文件elf_entry是新程序执行的起点(可执行文件头部定义的)
  • 需要动态链接的可执行文件先加载连接器ld,装载动态库elf_entry = load_elf_interp,指向动态链接器的起点。

五.实验

1.搭建环境:

cd LinuxKernel
rm menu -rf
git clone https://github.com/mengning/menu.git
cd menu
mv test_exec.c test.c
vi test.c   //增加了exec
vi MakeFile
make rootfs

2.更新menu内核

查看test.c文件

直接e hello.c切换到hello.c

查看Makefile

启动内核并验证execv函数

冻结内核,启动GDB调试

进行调试

    1. 先停在sys_execve处,再设置其它断点;按c一路运行下去直到断点sys_execve
    2. 按s跳入函数内单步执行
    3. new_ip是返回到用户态的第一条指令

退出调试状态,输入redelf -h hello可以查看hello的EIF头部

六.总结

分析exec*函数对应的系统调用处理过程:

1.关于动态链接的可执行程序的装载:ELF格式文件依赖动态链接库,动态链接库.so也可能依赖其他动态链接库,实际上动态链接库的依赖关系会形成一个图。需要动态链接的可执行文件先加载连接器ld,elf_entry指向动态链接器的起点,再看这个动态链接库是否还依赖与其他动态链接库,其实这整个过程是对一个图的遍历,把所有依赖的动态链接库都装载起来之后,ld将CPU的控制权交给可执行程序,主要由ld完成,不是内核。

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

时间: 2024-10-12 20:51:19

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

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

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

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

一.得到一个可执行程序 1. 预处理.编译.链接 gcc hello.c -o hello.exe gcc编译源代码生成最终可执行的二进制程序,GCC后台隐含执行了四个阶段步骤. 预处理 => 编译 => 汇编 => 链接 预处理:编译器将C源代码中包含的头文件编译进来和执行宏替换等工作. gcc -E hello.c -o hello.i 编译:gcc首先要检查代码的规范性.是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言. gcc –S hell

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

第七周 可执行程序的装载 一.预处理.编译.链接和目标文件的格式 1.可执行程序是怎么来的 C代码—>预处理—>汇编代码—>目标代码—>可执行文件 .asm汇编代码 .o目标码 a.out可执行文件 预处理负责把include的文件包含进来及宏替换工作. 2.目标文件的格式ELF (1)常见的ELF格式文件: (2)ABI——应用程序二进制接口 在目标文件中,他已经是二进制兼容,即适应二进制指令. (3)ELF中三种目标文件: 一个可重定位(relocatable)文件保存着代码和

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

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

&quot;Linux内核分析&quot;第七周

可执行程序的装载 张文俊+原创作品转载请注明出处+<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.预处理.编译.链接和目标文件的格式 可执行程序是怎么得来的? 首先,编译器预处理:1.将头文件加载进来:2.将宏替换 gcc -E -o hello.cpp hello.c (-m32)//c预处理成cpp文件 第二步,将cpp(预处理后的文件)编译成汇编代码 gcc -x cpp-output -S -o h

Linux内核分析第七周作业

Linux内核如何装载和启动一个可执行程序 有了上次的教训,这次直接用vmware完成 (- ̄3 ̄)- 先观察MenuOS新增的函数 1 int Exec(int argc, char *argv[]) 2 { 3 int pid; 4 /* fork another process */ 5 pid = fork(); 6 if (pid < 0) 7 { 8 /* error occurred */ 9 fprintf(stderr,"Fork Failed!"); 10 e

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

一.得到一个可执行程序 1. 预处理.编译.链接 gcc hello.c -o hello.exegcc编译源代码生成最终可执行的二进制程序,GCC后台隐含执行了四个阶段步骤.预处理 => 编译 => 汇编 => 链接预处理:编译器将C源代码中包含的头文件编译进来和执行宏替换等工作.gcc -E hello.c -o hello.i编译:gcc首先要检查代码的规范性.是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc把代码翻译成汇编语言.gcc –S hello.i –o

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

实验步骤 1. 更新menu,用test.c覆盖test_exec.c 2. 把init 和 hello 放到了rootfs.img目录下,执行exec命令的时候自动加载了hello程序 3. 执行exec 4. 运行stopped的menu 5.  gdb进行跟踪分析 总结 1. 创建新进程 2. 新进程调用execve()系统调用执行指定的ELF文件 3. 调用内核的入口函数sys_execve(),sys_execve()服务例程修改当前进程的执行上下文: 当ELF被load_elf_bi

20135327郭皓--Linux内核分析第三周 构造一个简单的Linux系统MenuOS

Linux内核分析第三周  构造一个简单的Linux系统MenuOS 前提回顾 1.计算机是如何工作的三个法宝 1.存储程序计算机 2.函数调用堆栈 3.中断 2.操作系统的两把宝剑 中断上下文的切换 进程上下文的切换 第一讲  Linux内核源代码介绍 arch目录包括了所有和体系结构相关的核心代码.它下面的每一个子目录都代表一种Linux支持的体系结构,例如i386就是Intel CPU及与之相兼容体系结构的子目录.PC机一般都基于此目录. init目录包含核心的初始化代码(不是系统的引导代

Linux内核分析(七)----并发与竞态

Linux内核分析(七) 这两天家里的事好多,我们今天继续接着上一次的内容学习,上次我们完善了字符设备控制方法,并深入分析了系统调用的实质,今天我们主要来了解一下并发和竞态. 今天我们会分析到以下内容: 1.      并发和竞态简介 2.      竞态解决办法 3.      为我们的虚拟设备增加并发控制 在前几次博文我们已经实现了简单的字符设备,看似完美但我们忽视了一个很严重的问题,即并发问题,那么什么是并发,又如何解决并发呢,我们下面进行分析. l  并发和竞态简介 1.       并