第七周 可执行程序的装载

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

1.可执行程序是怎么来的

C代码—>预处理—>汇编代码—>目标代码—>可执行文件

.asm汇编代码

.o目标码

a.out可执行文件

(1)预处理:负责把include的文件包含进来及宏替换工作,即文字替换,.c变成.i  gcc -E -o main.i main.c

(2)编译:.i变成.asm,是ASCⅡ  gcc -S -o main.asm main.i

(3)汇编:  gcc -c -o main.o main.asm

objdump 或者 readelf -h main.o命令可以查看其格式

2.目标文件的格式ELF

(1)常见的ELF格式文件:

(2)ABI——应用程序二进制接口

在目标文件中,他已经是二进制兼容,即适应二进制指令。

(3)ELF中三种目标文件:

  1. 一个可重定位(relocatable)文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。(主要是.o文件)
  2. 一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。
  3. 一个共享object文件保存着代码和合适的数据,用来被下面的两个链接器链接。第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。(主要是.so文件)

(4)目标文件格式

左边是ELF格式,右边是执行时候的格式,其中,ELF头描述了该文件的组织情况,程序投标告诉系统如何创建一个进程的内存映像,section头表包含了描述文件sections的信息。

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

Text segment拷贝到进程中的起点,Data segment拷贝到虚拟地址的某段……

可执行文件格式和进程地址空间有一个影射关系。

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

一个ELF可执行文件加载到内存:

可执行文件加载到内存中开始执行的第一行代码,默认从0x8048000开始加载,由于头部大小不同,程序实际入口可能不同。

一般静态链接将会把所有代码放在同一个代码段。

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

1.装载可执行程序之前的工作

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

(1)$ ls -l /usr/bin 列出/usr/bin下的目录信息

ls是一个可执行程序

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

我们写的main函数是否愿意接收命令行

愿意接收命令行参数
int main(int argc, char *argv[])
还愿意接收shell相关环境变量
int main(int argc, char *argv[], char *envp[]) //char *envp[]是shell命令自动加的

(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 */  //避免原有的shell程序被覆盖掉
. pid = fork();  
. if (pid<)
. {
. /* error occurred */
. fprintf(stderr,"Fork Failed!");
. exit(-);
. }
. else if (pid==)
. {
. /* child process */
. execlp("/bin/ls","ls",NULL);  //以ls命令为例
. }
. else
. {
. /* parent process */
. /* parent will wait for the child to complete*/
. wait(NULL);
. printf("Child Complete!");
. exit();
. }
.}

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

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

shell程序—>execv—>sys_execve

然后在初始化新程序堆栈时拷贝进去

  • 先函数调用参数传递,再系统调用参数传递

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

大多数可执行程序依赖动态链接库。

举例:

动态链接分为可执行程序装载时动态链接运行时动态链接

  • 准备.so文件 (在Linux下动态链接文件)

  • main.c  (1.9 KB) - Main program

这里只提供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到默认路径下。
$ ./main
This is a Main program!
Calling SharedLibApi() function of libshlibexample.so!    //调用共享库
This is a shared libary!
Calling DynamicalLoadingLibApi() function of libdllibexample.so!    //调用动态装载库
This is a Dynamical Loading libary!

三、可执行程序的装载

1.可执行程序装载相关关键问题分析

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

  • 正常的系统调用:陷入到内核态,返回到用户态,执行系统调用的下一条指令。
  • fork:进入到内核态,两次返回:第一次返回到父进程的位置,继续执行。第二次,在子进程中从ret_from_fork开始执行然后返回用户态。
  • execve:当前的可执行程序执行到execve时,陷入到内核态,用execve加载的可执行文件将当前的可执行程序覆盖掉,当execve系统调用返回时,返回的不是原来的系统调用,而是新的可执行程序的执行起点,即main函数的位置。

(2)sys_execve内核处理过程

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

  • do_execve -> do_execve_common -> exec_binprm
  • search_binary_handler符合寻找文件格式对应的解析模块,如下:
    list_for_each_entry(fmt, &formats, lh) {        //在链表中寻找可以处理ELF格式的模块
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
bprm->recursion_depth++;
retval = fmt->load_binary(bprm);          //对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读
        read_lock(&binfmt_lock);

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

82static struct linux_binfmt elf_format = {      //elf_foemat结构体
.module = THIS_MODULE,
.load_binary = load_elf_binary,          //多态机制,观察者模式
.load_shlib = load_elf_library,
.core_dump = elf_core_dump,
.min_coredump = ELF_EXEC_PAGESIZE,
};
2198 static int __init init_elf_binfmt(void)
{
register_binfmt(&elf_format);      //把elf_format变量注册到fmt链表中
 return ; }

庄生梦蝶

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

  • 修改int 0x80压入内核堆栈的EIP

  • load_elf_binary ->  start_thread

2.sys_execve内部处理过程

  • 需要动态链接的可执行文件先加载连接器ld;否则直接把elf文件entry地址赋值给entry即可。
  • start_thread(regs, elf_entry, bprm->p)会将CPU控制权交给ld来加载依赖库并完成动态链接;对于静态链接的文件elf_entry是新程序执行的起点

3.使用gdb跟踪sys_execve内核函数的处理过程

1.更新menu

2.查看test.c文件,可以看到增加了exec系统调用,其源代码与之前的fork类似

3.查看Makefile,发现增加了gcc -o hello hello.c -m32 -static,并且依据视频补充上那两句代码。

4.make rootfs,发现多了exec功能,并且比fork多了Hello World!

5.冻结内核,开始gdb调试,加载符号表,target remote

6.设置三个断点,开始跟踪

7.开始执行exec,到这里停下,开始系统调用

8.列出来,跟踪

9.跑到load_elf_binary,看这部分的代码

10.对照hello可执行程序的入口点地址

11.进入后,逐步跟踪,发现在压栈

3.可执行程序与庄生梦蝶的故事

4.浅析动态链接的可执行程序的装载

(1)动态链接的过程中,内核做了什么?

  • 可执行程序需要依赖动态链接库,而这个动态链接库可能会依赖其他的库,这样形成了一个树形结构;
  • elf_interpreter:需要依赖动态链接器进行加载这些库(ld)并进行解析,entry返回动态链接器的入口,加载所有需要的动态链接库,即广度遍历树,然后ld将CPU的控制权交给可执行程序入口(头部起点位置)
  • 动态链接的过程主要是动态链接器来完成,而不是内核。

Linux内核分析——第七周学习笔记20135308的更多相关文章

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

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

  2. Linux内核分析——第三周学习笔记20135308

    第三周 构造一个简单的Linux系统MenuOS 计算机三个法宝: 1.存储程序计算机 2.函数调用堆栈 3.中断 操作系统两把宝剑: 1.中断上下文的切换:保存现场和恢复现场 2.进程上下文的切换 ...

  3. Linux内核分析——第八周学习笔记20135308

    第八周 进程的切换和系统的一般执行过程 一.进程切换的关键代码switch_to分析 1.进程调度与进程调度的时机分析 (1)进程分类 第一种分类 I/O-bound:等待I/O CPU-bound: ...

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

    第六周 进程的描述和进程的创建 一.进程描述符task_struct数据结构 1.操作系统三大功能 进程管理 内存管理 文件系统 2.进程控制块PCB——task_struct 也叫进程描述符,为了管 ...

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

    LINUX内核分析第七周学习总结:可执行程序的装载 韩玉琪 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/cours ...

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

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

  7. 《Linux内核分析》第一周学习笔记

    <Linux内核分析>第一周学习笔记 计算机是如何工作的 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/c ...

  8. 《Linux内核分析》第二周学习笔记

    <Linux内核分析>第二周学习笔记 操作系统是如何工作的 郭垚 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/ ...

  9. linux内核分析第五周学习笔记

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

随机推荐

  1. 日常工作生活中的做人做事道理[持续更新ing]

    1.凡是预则立,不预则废 2.不能用特殊案例说明事情本身的发展规律 3.任务不能拖,需主动出击,想方设法完成 4.工作要有细致化的沟通和安排 5.解决问题和安排任务可以逆向思维的去想 6.问题要举一反 ...

  2. JavaScript Patterns 4.3 Returning Functions

    Use closure to store some private data, which is accessible by the returned function but not to the ...

  3. InfluxDB Cli中查询结果中time格式显示设置

    InfluxDB Cli中,time默认显示为19位时间戳格式,平时查询起来特不方便,那么,如何设置成为我们人类能看懂的时间格式呢? 方法有二: 1.$ influx -precision rfc33 ...

  4. 利用mysql对特殊字符和超长字符会进行截断的特性 进行存储型XSS攻击——WordPress <4.1.2 & <=4.2 存储型xss

    转自:Baidu Security LabXteam http://xteam.baidu.com/?p=177 漏洞概述 本次漏洞出现两个使用不同方式截断来实现的存储型xss,一种为特殊字符截断,一 ...

  5. 以前写的关于Linux C/C++的博客

    以前在CU写的关于Linux C/C++的博客 http://blog.chinaunix.net/uid/25909722/cid-24318-list-1.html

  6. dubbo学习之服务提供者

    1.简介 这里主要记录如何搭建一个spring框架,提供一个dubbo服务,包括详细的步骤. 2.详细步骤 2.1 项目目录结构 2.2 创建maven项目 new --> Web Projec ...

  7. getElementsByName()以及获取checkbox对应文本text,

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. listview中OnItemClick方法各个参数的作用

    OnItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) 1.arg0,arg2 m_listview.setOnI ...

  9. cni 添加网络 流程分析

    cnitool: Add or remove network interfaces from a network namespace cnitool add <net> <netns ...

  10. LeetCode 2 Add Two Sum 解题报告

    LeetCode 2 Add Two Sum 解题报告 LeetCode第二题 Add Two Sum 首先我们看题目要求: You are given two linked lists repres ...