第七节 可执行程序的装载

By 20135203齐岳

本周的主要内容:

  1. 可执行程序是如何得到的以及可执行程序的目标文件格式
  2. 动态库 &动态链接库
  3. 系统调用sys_exec函数的执行过程

预处理、编译、链接和目标文件的格式

可执行程序是如何得来的

预处理:gcc –E –o hello.cpp hello.c -m32 //负责把include的文件包含进来,宏替换
编 译:gcc -x cpp-output –S hello.s –o hello.cpp -m32 //gcc –S调用ccl,编译成汇编代码
汇 编:gcc -x assembler –c hello.s –o hello.o; //gcc -c 调用as,得到二进制文件,不可
链 接:gcc –o hello hello.o ;gcc -o //调用ld形成目标可执行文件

/*ELF格式文件使用共享库,如果静态编译,把所有需要的依赖的文件全部放在程序内部*/
静态编译:gcc –o hello.static hello.o -m32 -static

目标文件的格式ELF

可执行文件格式的发展过程:

ELF:可执行&可链接的文件格式,是一个文件格式的标准。

ABI:应用程序二进制接口,目标文件中已经是二进制兼容的格式。

ELF中的三种主要的目标文件
  • 可重定位文件:保存代码和适当的数据,用来和其他object文件一起创建一个可执行文件或一个共享文件。主要是.o文件。
  • 可执行文件:保存一个用来执行的程序,指出了exec(BA_OS)如何来创建程序进程映象,怎么把文件加载出来以及从哪里开始执行。
  • 共享文件:保存着代码和数据用来被以下两个链接器链接。一是链接编译器,可以和其他的可重定位和共享文件创建其他的object文件;二是动态链接器,联合一个可执行文件和其他 共享文件来创建一个进程映象。主要是.so文件。
文件格式

Object文件参与程序的联接(创建一个文件)和程序的执行(运行一个文件)

查看ELF文件的头部
$ readelf -h hello

当创建或增加一个进程映象的时候,系统在理论上将拷贝一个文件的段到一个虚拟的内存段。

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

Entry point address:入口地址为0x8048X00(不唯一)

其原因是:32位x86的系统有4G的进程地址空间(前面的1G供内核用;之后的3G用户态可访问)当一个ELF可执行文件要加载到内存中时,先把代码段和数据段加载到当中(默认从0x8048000位置开始加载)。开始加载时,前面的都是ELF格式的头部信息,大小不尽相同,根据头部大小可确定程序的实际入口,当启动一个刚加载过可执行文件的进程时,就可从这个位置开始执行。

一般静态链接会将所有的代码放在同一个代码段;动态链接的进程会有多个代码段。

可执行程序、共享库和动态链接

可执行程序的执行环境

命令行参数和shell环境,一般我们执行一个程序的Shell环境,我们的实验直接使用execve系统调用。

Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身

int main(int argc, char *argv[])
int main(int argc, char *argv[], char *envp[])//用户输入的参数1、参数2、shell的环境变量

Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

int execve(const char *filename,char *const argv[],char *const envp[]);

库函数exec*都是execve的封装例程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char * argv[])
{int pid;/* fork another process */pid = fork();//执行shell之前先创建一个新的子进程if (pid<0) { /* error occurred */fprintf(stderr,"Fork Failed!");//如果fork不成功,则不能执行新的程序exit(-1);} else if (pid==0) //成功fork一个子进程{/*   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);}
}

命令行参数和环境变量是如何进入新程序的堆栈的?

在创建一个新的用户态堆栈的时候,实际上是把命令行和环境变量参数的内容通过指针的方式传递到系统调用的内核处理函数,函数在创建可执行程序新的堆栈初始化时候再拷贝进去。先函数调用参数传递,再系统调用参数传递。

装载时动态链接和运行时动态链接应用举例

共享库和动态加载共享库相关范例代码

动态链接分为可执行程序装载时动态链接和运行时动态链接,如下代码演示了这两种动态链接。

  • 共享库

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

      dllibexample.h (1.3 KB) - Interface of Dynamical Loading Lib Exampledllibexample.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(); //调用已声明函数

编译main,注意这里只提供shlibexample的-L(库对应的接口头文件所在目录)和-l(库名,如libshlibexample.so去掉lib和.so的部分),并没有提供dllibexample的相关信息,只是指明了-ldl

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

可执行程序的装载

可执行程序的装载关键问题的分析

execve和fork都是特殊的系统调用

  • fork两次返回,第一次返回到父进程继续向下执行,第二次是子进程返回到ret_from_fork然后正常返回到用户态。

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

sys_execve内核处理过程:

对于ELF格式的可执行文件fmt->load _ binary(bprm);执行的应该是load _ elf _ binary。其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读。

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)
{nregister_binfmt(&elf_format);//把变量注册进内核链表,在链表里查找文件的格式return 0;
}

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

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

sys_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 动态链接库动态链接文件),动态链接器的起点。如果它是一个静态链接,可直接将文件地址入口进行赋值。

发现在start_thread处会有两种可能:

  • 如果是静态链接,elf _ entry就指向了可执行文件中规定的头部,即main函数对应的位置,是新程序执行的起点;
  • 如果是需要依赖其他动态库的动态链接,elf _ entry是指向动态链接器的起点。将CPU控制权交给ld来加载依赖库并完成动态链接。

实验——使用gdb跟踪分析execve系统调用内核处理函数sys_execve

前期操作与上周实验相同,代码部分除了增加了exec函数之外还在Makefile中编译了hello.c,然后在生成根文件系统的时候把init和hello都放到rootfs.img中了,在这个实验中hello就是一个加载进来的可执行文件。

(1)exec函数运行结果:

(2)设置断点到sys_exec

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

(4)继续执行到load _ elf _ binary处的断点,此时调用这个函数进行对可执行文件格式的解析(load _ elf _ binary函数在do _ execve _ common的内部,具体调用关系可参照上文的流程图)

(5)继续执行到start_thread处的断点。

因为是静态链接,elf _ entry指向了可执行文件中规定的头部。使用po new _ ip指令打印其指向的地址,new _ ip是返回到用户态的第一条指令的地址

(6)查看hello的elf头部,发现与new _ ip所指向的地址是一致的。

(7)继续单步执行,可以看到加载新可执行程序的一系列数据,并构造新的代码段。

参考资料

【原创作品转载请注明出处】

  1. 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
  2. 《深入理解计算机系统》https://www.shiyanlou.com/courses/413

转载于:https://www.cnblogs.com/July0207/p/5361392.html

第七节 可执行程序的装载——20135203齐岳相关推荐

  1. 第五节 系统调用的三个层次(下) ——20135203齐岳

    第五节 系统调用的三个层次(下) By 20135203齐岳 本周的课程主要内容有三点: 在MenuOS中通过添加代码增加自定义的系统调用命令 使用gdb跟踪调试内核 简单分析system_call代 ...

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

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

  3. 20135203齐岳 信息安全系统设计基础期中总结

    20135203齐岳 信息安全系统设计基础期中总结 一.学习目标 复习前面Linux 命令,Linux 编程基础,教材前七章内容 二.学习资源 教材 答案解析见http://group.cnblogs ...

  4. linux更换进程执行码,第八节 进程的切换和系统的一般执行过程—— 20135203齐岳...

    第八节 进程的切换和系统的一般执行过程 By 20135203齐岳 本周的主要内容: Linux中进程调度的基本概念与相关知识 schedule函数如何实现进程调度 Linux进程的执行过程(一般情况 ...

  5. 20135203齐岳 信息安全系统设计基础第十三周学习总结

    20135203齐岳 信息安全系统设计基础第十三周学习总结 学习计时:8/9共小时(计划/实际) 读书:4/5 代码:1/1 作业:1/1 博客:2/2 第十二章 并发编程 一.学习目标 掌握三种并发 ...

  6. 20135203齐岳 信息安全系统设计基础第五周学习总结

    20135203齐岳 信息安全系统设计基础第五周学习总结 学习计时:共10小时 读书:5 代码:1 作业:2 博客:2 第四章 处理器体系结构 一.学习目标 本章内容是处理器体系结构,重点掌握ISA, ...

  7. 20135203齐岳 信息安全系统设计基础第四周学习总结

    20135203齐岳 信息安全系统设计基础第四周学习总结 学习计时:共17小时 读书:10 代码:1 作业:3 博客:3 一.学习目标 理解逆向的概念以及 掌握X86汇编基础,能够阅读(反)汇编代码 ...

  8. 20135203齐岳 信息安全系统设计基础期末总结

    20135203齐岳 信息安全系统设计基础期末总结 知识点总结 课本内容 每周学习笔记中摘抄出课本重点内容,见文章最后学习内容汇总. 课堂内容 密码系统设计基础--软件体系结构 软件体系结构导论 体系 ...

  9. 《深入理解计算机系统》课本第七章自学笔记——20135203齐岳

    <深入理解计算机系统>课本自学笔记 第七章 链接 By20135203齐岳 链接:将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或拷贝)到存储器并执行. 现代 ...

最新文章

  1. flowvisor 命令_mininet+FlowVisor+OpenDayLight环境搭建及实验一
  2. 【flask学习笔记】flask与HTTP,flask与mongodb交互,用手机输入局域网ip访问flask界面
  3. java excel添加公式_JAVA实现EXCEL公式专题(四)——字符串函数
  4. web性能测试基础 知识(引用)
  5. 20 图|Nacos 手摸手教程
  6. java实现 支付宝支付
  7. [JavaScript] 函数同名问题
  8. fastdfs配置-跟踪服务器
  9. 各种排序算法总结及C#代码实现
  10. ASP.NET之Ajax系列(三)
  11. NPM 简单实用说明
  12. 字符,字符集,字符编码
  13. GPUGraphicsGame tools
  14. 数学建模算法与应用(目录)
  15. 基于multisim14的函数信号发生器仿真
  16. vue中el-calendar自定义日历控件
  17. Powershell添加英文输入法
  18. 中国智能家居企业出海,亚马逊云科技为其提供“GPS锦囊”
  19. 项目管理IPD产品开发
  20. SLAM的数学基础(3):几种常见的概率分布的实现及验证

热门文章

  1. 2013,让你正能量无限的话
  2. 北欧“双城记”——爱立信和诺基亚一季度财报分析
  3. ORACLE 触发器控制用户登录之权限限制
  4. nodejs---console.log()控制台打印
  5. HTTP 协议之Gzip压缩原理
  6. MySQL数据库开发规范
  7. pdf大小如何压缩?
  8. 倍福BECKHOFF PLC:FileWirte/Puts,FileRead/Gets File Function Blocks文件操作功能块
  9. 【Axure教程】拖动排序——扣款顺序
  10. 腾讯视频投屏显示无法连接服务器,腾讯视频投屏不能快进