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

linux中主要的可执行文件为ELF文件,我们可以将它装载到自己的程序中,这次我们就将分析linux装载可执行程序的过程。

首先明确一点,装载可执行程序有两种方式:静态链接与动态链接。所谓静态链接,就是在程序执行之前完成所有链接工作,组成一个可执行文件,放到内存执行。这样做的缺点是,当有多个文件要链接同一份可执行文件时,内存中会有多份这个可执行文件的拷贝,这在一定程度上就是一种对内存的浪费。因此,人们又发明了动态链接的概念,它指的是程序执行前并不将所有的模块组装在一起,而是在需要用到这个模块的时候再完成链接工作,这样相比静态链接就更加灵活,也节省了内存。

动态链接分为装载时动态链接和运行时动态链接,大家可有兴趣可以进一步了解一下。

基础知识普及完毕,接着我们来分析linux具体是如何装载可执行程序的。

linux装载可执行程序的系统调用是execve,它和fork函数一样,在执行的过程中会更改执行完毕后返回的代码段。

它的工作是首先读入传入的文件名、参数和环境变量,然后调用解析链表寻找解析该可执行文件的结构:

list_for_each_entry(fmt, &formats, lh) {
if (!try_module_get(fmt->module))
continue;
read_unlock(&binfmt_lock);
bprm->recursion_depth++;
retval = fmt->load_binary(bprm);
read_lock(&binfmt_lock);

比如我们读入了ELF文件,那它就要在链表中寻找ELF文件的解析器。这里注意运用了观察者模式:linux会将各种解析器预先注册,当添加了新的解析器后,就会更改解析的链表。比如ELF文件中是这样的:

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,
};

 在这里,它定义load_lef_binary为解析器load_binary的具体实现(其实就是一种多态),之后将该结构体注册到解析器的链表中,从此再遇到ELF文件,搜索解析器链表,就可以找到专门解析这种文件的解析器了。

static int __init init_elf_binfmt(void)
{
register_binfmt(&elf_format);
return 0;
}

在ELF自己的解析函数load_elf_binary中,对于静态链接和动态链接,处理过程是不一样的。

if (elf_interpreter) {
unsigned long interp_map_addr = 0; elf_entry = load_elf_interp(&loc->interp_elf_ex,
interpreter,
&interp_map_addr,
load_bias);
if (!IS_ERR((void *)elf_entry)) {
/*
* load_elf_interp() returns relocation
* adjustment
*/
interp_load_addr = elf_entry;
elf_entry += loc->interp_elf_ex.e_entry;
}
if (BAD_ADDR(elf_entry)) {
retval = IS_ERR((void *)elf_entry) ?
(int)elf_entry : -EINVAL;
goto out_free_dentry;
}
reloc_func_desc = interp_load_addr; allow_write_access(interpreter);
fput(interpreter);
kfree(elf_interpreter);
} else {
elf_entry = loc->elf_ex.e_entry;
if (BAD_ADDR(elf_entry)) {
retval = -EINVAL;
goto out_free_dentry;
}
}
...
start_thread(regs,elf_entry,bprm->p);

对于动态链接,这段代码直接执行elf_interpreter的部分,此时会装载一个动态链接器,由它再进行具体的内存管理,这里暂且不讨论。

对于静态链接,则直接执行else的部分,此时会将ELF代码段的入口地址付给elf_entry变量。

之后会执行start_thread函数,该函数将进程上下文压栈,同时将elf_entry赋给ip,对于静态链接来说,也就是使代码跳出内核态后执行的第一条代码就是ELF的入口处代码。

这样一来,就可以实现装载可执行程序的功能了。

总结:

  本博客讨论了linux装载可执行程序的过程,装载的可执行程序分为静态和动态链接两种方式。在解析可执行文件时,linux利用了多态机制和观察者模式,并在解析过程中改变内核堆栈的EIP地址,从而实现将执行的下一条代码更改到可执行程序的作用。

实验过程是基于实验楼的系统,这里显示几张截图:

设置sys_execve、load_elf_bianry、start_thread等系统调用作为断点进行调试,从第三张图可以发现,传入start_thread的elf_entry地址和我们链接的可执行文件hello的地址是一样的,这就说明,通过start_thread,系统将hello的程序入口地址设置为系统堆栈EIP的指向,从而使得重新回到用户态之后首先执行hello。

今天的分析就到这里,谢谢大家。

linux装载可执行程序简析的更多相关文章

  1. Linux VFS机制简析(二)

    Linux VFS机制简析(二) 接上一篇Linux VFS机制简析(一),本篇继续介绍有关Address space和address operations.file和file operations. ...

  2. Linux VFS机制简析(一)

    Linux VFS机制简析(一) 本文主要基于Linux内核文档,简单分析Linux VFS机制,以期对编写新的内核文件系统(通常是给分布式文件系统编写内核客户端)的场景有所帮助. 个人渊源 切入正文 ...

  3. Linux目录结构简析

    Linux目录结构简析 Linux继承了unix操作系统结构清晰的特点.在linux下的文件结构非常有条理.但是,上述的优点只有在对linux相当熟悉时,才能体会到.现在,虫虫就把linux下的目录结 ...

  4. linux共享内存简析

    共享内存是IPC的一种机制,允许两个不相关的进程共享同一块内存 //共享内存可以双向通信,但其本身没有相应机制,需要程序编写者设计,本例为单向通信(分为读端和写端). 共享内存读端: #include ...

  5. Linux 目录结构学习与简析 Part1

    linux目录结构学习与简析 by:授客 QQ:1033553122 说明: /             linux系统目录树的起点 =============== /bin      User Bi ...

  6. Linux驱动之中断处理体系结构简析

    S3C2440中的中断处理最终是通过IRQ实现的,在Linux驱动之异常处理体系结构简析已经介绍了IRQ异常的处理过程,最终分析到了一个C函数asm_do_IRQ,接下来继续分析asm_do_IRQ, ...

  7. Linux 目录结构学习与简析 Part2

    linux目录结构学习与简析 by:授客 QQ:1033553122 ---------------接Part 1-------------- #1.查看CPU信息 #cat /proc/cpuinf ...

  8. Linux 磁盘分区方案简析

    Linux 磁盘分区方案简析 by:授客 QQ:1033553122   磁盘分区 任何硬盘在使用前都要进行分区.硬盘的分区有两种类型:主分区和扩展分区.一个硬盘上最多只能有4个主分区,其中一个主分区 ...

  9. Linux内存管理机制简析

    Linux内存管理机制简析 本文对Linux内存管理机制做一个简单的分析,试图让你快速理解Linux一些内存管理的概念并有效的利用一些管理方法. NUMA Linux 2.6开始支持NUMA( Non ...

随机推荐

  1. 在 Linux 中用 nmcli 命令绑定多块网卡

    今天,我们来学习一下在 CentOS 7.x 中如何用 nmcli(Network Manager Command Line Interface:网络管理命令行接口)进行网卡绑定. 网卡(接口)绑定是 ...

  2. 简单研究Android View绘制一 测量过程

    2015-07-27 16:52:58 一.如何通过继承ViewGroup来实现自定义View?首先得搞清楚Android时如何绘制View的,参考Android官方文档:How Android Dr ...

  3. Node.js GET/POST请求

    在很多场景中,我们的服务器都需要跟用户的浏览器打交道,如表单提交. 表单提交到服务器一般都使用GET/POST请求. 我将为大家介绍 Node.js GET/POST请求. 获取GET请求内容 由于G ...

  4. 在Eclipse上建立hbase 0.98.3/0.96.2源代码阅读环境

    2.1. 切换到源代码目录,执行: mvn 黄色部分作用为设置代理.由于本人的编译环境在公司内网,所以需要设置代理 2.2. 生成eclipse项目环境: mvn eclipse:eclipse -D ...

  5. 《大象-Think In UML》读书笔记2

    什么是UML? UML本身并没有包含软件方法,而仅仅是一种语言,一种建模用的语言,而所有的语言都是基本词汇和语法两部分构成的,UML也不例外.UML中定义了一些建立模型所需要的.表达某种特定含义的基本 ...

  6. [蟒蛇菜谱]Python获取任意xml节点的值

    # -*- coding: utf-8 -*- import xml.dom.minidom ELEMENT_NODE = xml.dom.Node.ELEMENT_NODE class Simple ...

  7. 关于ASP.NET页面打印技术的总结【转】

    B/S结构导致了Web应用程序中打印的特殊性. • 程序运行在浏览器中,打印机在本地,而文件确可能在服务器上,导致了打印控制不是很灵活. • 格式如何控制和定制等,是我们开发中可能会面对的问题. 打印 ...

  8. BZOJ 4034 BIT & Dfs序

    调了恒久突然发现输出优化忘记带负号了.. 就是差分树状数组维护Dfs序即可. #include <iostream> #include <cstring> #include & ...

  9. java 代码的细节优化

    前言 代码优化,一个很重要的课题.可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑 的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用 ...

  10. 怎样禁用或启用"最近使用的项目"

    1.右击“任务栏”——属性——‘开始’菜单——自定义——高级——去掉“列出我最近打开的文档”的勾选——确定.2.通过“组策略”来修改开始——运行——gpedit.msc——用户配置——管理模板——任务 ...