Linux如何启动并装载程序

理解编译链接的过程和ELF可执行文件格式



第一步:先编辑一个hello.c

第二步:生成预处理文件hello.cpp

gcc -E -o hello.cpp hello.c -m32

vi hello.cpp (

第三步:编译成汇编代码hello.s

gcc -x cpp-output -S -o hello.s hello.cpp -m32

vi hello.s

第四步:编译成目标代码,得到二进制文件hello.o,

gcc -x assembler -c hello.s -o hello.o -m32

vi hello.o

第五步:链接成可执行文件hello,

gcc -o hello hello.o -m32

vi hello

第六步:运行一下

./hello

gcc -o hello hello.c

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

hello.static 也是ELF格式文件

运行一下hello.static

./hello.static

ELF三种主要的目标文件:

ELF文件格式是一个开放标准,各种UNIX系统的可执行文件都采用ELF格式,它有三种不同的类型:

可重定位的目标文件(Relocatable,或者Object File):保存代码和适当数据,和其它Object文件一起创建可执行文件或共享文件。

可执行文件(Executable):文件保存一个用来执行的程序,该文件指出exec如何来程序进程映像。

共享库(Shared Object,或者Shared Library):保存代码和合适的数据,用来被连接编辑器和动态链接器进行链接。

ELF目标文件参与程序的链接和执行。ELF头文件里保存了文件的组织情况,告诉系统如何创建一个进程的内存映象。

当通过用父进程调用fork创建一个新进程时,系统实际上是拷贝了父进程的一个文件段和虚拟了一个内存段。虚拟内存是假想的内存,它其实是不存在的,而仅仅是由一些硬件和软件管理的一种“系统”。他提供了三个重要的能力:1,它将主存看成一个存储在磁盘上的地址空间的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据(这里存在“交换空间”以及“页面调度”等概念),通过这种方式,高效地利用主存;2,它为每个进程提供了统一的地址空间(以虚拟地址编址),从而简化了存储器管理;3,操作系统会为每个进程提供独立的地址空间,从而保护了每个进程的地址空间不被其他进程破坏。

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



ELF的格式如下:

装载可执行文件

LINUX 一般通过shell程序为执行环境来启动一个可执行程序。Shell本身不限制命令行参数的个数,它受限于命令自身;Shell会调用一个系统调用exece将命令参数和环境参数传递给可执行程序的main函数。

命令行参数与环境变量的保存与传递:

当fork一个子进程时,是先复制父进程,再调用exece,会把原来的进程环境覆盖掉,用户态堆栈也被清空。用户态堆栈以start_stack作为main函数的起点,把argv[ ]命令行参数 和envp[ ]环境变量的内容通过指针的方式传递到系统调用exeve(内核处理函数);exeve创建一个新的用户态堆栈时把上面的命令行参数与环境变量拷贝到新的用户态堆栈里,从而初始化新的可执行程序的上下文环境。exece在内核态下装载可执行程序,再返回用户态。所以它先进行函数调用参数传递,然后系统调用参数传递,最后又进行函数调用参数传递。

动态链接

动态链接是相对于共享对象而言的。动态链接器将程序所需要的所有共享库装载到进程的地址空间,并且将程序汇总所有为决议的符号绑定到相应的动态链接库(共享库)中,并进行重定位工作。

动态连接有两种形式:可执行程序装载时动态连接和运行时动态链接

实验过程

删除menu目录,git克隆一个新的menu目录,并使用test_exec.c覆盖test.c文件。



makerootfs,启动内核



关闭内核,重新执行qemu,并增加参数-s,-S冻结内核执行状态,打开gdb,连接端口1234





开始执行,在内核中执行exec命令,发现会卡住



查看hello的EIF信息



execve 处理过程:

linux/fs/exec.c

SYSCALL_DEFINE3(execve,
const char __user *, filename,
const char __user *const __user *, argv,
const char __user *const __user *, envp)
{
return do_execve(getname(filename), argv, envp);
} int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp) //__user 用户态指针
{
struct user_arg_ptr argv = { .ptr.native = __argv };//命令行参数变成结构
struct user_arg_ptr envp = { .ptr.native = __envp };
return do_execve_common(filename, argv, envp);
} static int do_execve_common(struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp)

{

struct linux_binprm *bprm; //保存要执行的文件相关的信息(include/linux/binfmts.h)
...
file = do_open_exec(filename);//打开执行的可执行文件 //填充bprm结构
bprm->file = file;
bprm->filename = bprm->interp = filename->name;
...
retval = copy_strings(bprm->argc, argv, bprm);//命令行参数和环境变量copy到结构体里 retval = exec_binprm(bprm);//
} static int exec_binprm(struct linux_binprm *bprm)
{
pid_t old_pid, old_vpid;
int ret; /* Need to fetch pid before load_binary changes it */
old_pid = current->pid;
rcu_read_lock();
old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
rcu_read_unlock(); ret = search_binary_handler(bprm);//寻找可执行文件的处理函数
if (ret >= 0) {
audit_bprm(bprm);
trace_sched_process_exec(current, old_pid, bprm);
ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
proc_exec_connector(current);
} return ret;
} /*
* cycle the list of binary formats handler, until one recognizes the image
*/
int search_binary_handler(struct linux_binprm *bprm)
{
struct linux_binfmt *fmt;
...
//循环寻找能够解析当前可执行文件的代码
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);//加载可执行文件的处理函数,函数指针,实际调用load_elf_binary(linux/fs/binfmt_elf.c)
read_lock(&binfmt_lock);
put_binfmt(fmt);
bprm->recursion_depth--;
if (retval < 0 && !bprm->mm) {
/* we got to flush_old_exec() and failed after it */
read_unlock(&binfmt_lock);
force_sigsegv(SIGSEGV, current);
return retval;
}
if (retval != -ENOEXEC || !bprm->file) {
read_unlock(&binfmt_lock);
return retval;
}
}
read_unlock(&binfmt_lock); }

2019-2020-1 20199303《Linux内核原理与分析》第八周作业的更多相关文章

  1. 2019-2020-1 20199329《Linux内核原理与分析》第九周作业

    <Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...

  2. 2019-2020-1 20199329《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 一.上周问题总结: 未能及时整理笔记 Linux还需要多用 markdown格式不熟练 发布博客时间超过规定期限 二.本周学习内容: <庖丁解 ...

  3. 20169212《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 这一周学习了MOOCLinux内核分析的第一讲,计算机是如何工作的?由于本科对相关知识的不熟悉,所以感觉有的知识理解起来了有一定的难度,不过多查查资 ...

  4. 20169210《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...

  5. 2018-2019-1 20189221 《Linux内核原理与分析》第九周作业

    2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...

  6. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  7. 2019-2020-1 20209313《Linux内核原理与分析》第二周作业

    2019-2020-1 20209313<Linux内核原理与分析>第二周作业 零.总结 阐明自己对"计算机是如何工作的"理解. 一.myod 步骤 复习c文件处理内容 ...

  8. 2018-2019-1 20189221《Linux内核原理与分析》第一周作业

    Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...

  9. 《Linux内核原理与分析》第一周作业 20189210

    实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...

  10. 2018-2019-1 20189221《Linux内核原理与分析》第二周作业

    读书报告 <庖丁解牛Linux内核分析> 第 1 章 计算工作原理 1.1 存储程序计算机工作模型 1.2 x86-32汇编基础 1.3汇编一个简单的C语言程序并分析其汇编指令执行过程 因 ...

随机推荐

  1. MATLAB 大数相乘溢出显示

    解一道面试题——华为社招现场面试1:请使用代码计算1234567891011121314151617181920*2019181716151413121110987654321 . 乘积是逐位相乘,也 ...

  2. 【Jenkins】使用 Jenkins REST API 配合清华大学镜像站更新 Jenkins 插件

    自从去年用上了 Jenkins 进行 CI/CD 之后,工作效率高了不少,摸鱼的时间更多了.不过 Jenkins 好是好,但在功夫网的影响下,插件就是经常更新不成功的,就像下面这样子: 查了不少资料, ...

  3. Kafka监控:主要性能指标

    Kafka是什么? Kafka是一个分布式,有分区的,有副本的日志服务系统,由LinkedIn公司开发,并于2011年开源.从本质上来说,Kafka拥有一套可扩展的发布/订阅消息队列架构,并组成了一套 ...

  4. js 实现浏览器全屏效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  5. EntityFramework Core 3.x上下文构造函数可以注入实例呢?

    前言 今天讨论的话题来自一位微信好友遇到问题后请求我的帮助,当然他的意图并不是本文标题,只是我将其根本原因进行了一个概括,接下来我们一起来探索标题的问号最终的答案是怎样的呢? 上下文构造函数是否可以注 ...

  6. Go gRPC教程-客户端流式RPC(四)

    前言 上一篇介绍了服务端流式RPC,客户端发送请求到服务器,拿到一个流去读取返回的消息序列. 客户端读取返回的流的数据.本篇将介绍客户端流式RPC. 客户端流式RPC:与服务端流式RPC相反,客户端不 ...

  7. 【Debug记录】Exeption thrown by glCreateVertexArrays

    继在机场丢失笔记本后又一大灾难--小组项目无法在老电脑上运行. 位置:glCreateVertexArrays函数 报错:Exception thrown at 0x00000000 in Clien ...

  8. 正整数的二进制表示中1的个数计算(使用移位或者n&(n-1))

    第一种:使用n&(n-1)表示来计算有多少个1 int n=127; int count=0; while (n!=0){ count++; n=n&(n-1); } 第二种:使用移位 ...

  9. Weblogic-SSRF 漏洞复现

    0x01 环境搭建 我这里使用的是vulhub,它几乎包含了所有的漏洞环境.(建议安装在ubuntu上) 有需要的小伙伴来企鹅群自取. 安装好vulhub之后需要cd 到weblogic ssrf 目 ...

  10. HashMap实现原理(JDK1.8)

    概述HashMap在底层数据结构上采用了数组+链表+红黑树,通过散列映射来存储键值对数据因为在查询上使用散列码(通过键生成一个数字作为数组下标,这个数字就是hash code)所以在查询上的访问速度比 ...