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. 三、【Docker笔记】Docker镜像

    镜像是Docker的三大核心概念之一.Docker在运行容器之前,本地需要存有镜像,若不存在则Docker会首先尝试从默认的镜像仓库中去下载,当然我们也可以去配置自己的仓库,如此就会从我们配置的仓库中 ...

  2. nginx代理图片上传以及访问

    nginx代理图片上传 首先需要利用nginx代理图片访问参考 https://www.cnblogs.com/TJ21/p/12609017.html 编写接受文件的controller @Post ...

  3. CVE-2020-2551复现过程

    项目地址 cnsimo/CVE-2020-2551 CVE-2020-2551 weblogic iiop 反序列化漏洞 该项目的搭建脚本在10.3.6版本经过测试,12版本未测试. 环境 kali+ ...

  4. Html,Css遇到的bug2020031601

    td,里面jquery设置height,设置成功也没有效果. div可以成功设置,并产生效果. 可能是td,div有一些隐藏的特性不同导致的.

  5. PTA数据结构与算法题目集(中文) 7-5

    PTA数据结构与算法题目集(中文)  7-5  堆中的路径 7-5 堆中的路径 (25 分)   将一系列给定数字插入一个初始为空的小顶堆H[].随后对任意给定的下标i,打印从H[i]到根结点的路径. ...

  6. 家庭记账本app进度之下拉框和数字转轮的相关应用

    这次主要是悬系的下拉框Spinner和数字转轮NumberPicker的使用.先分析相关的用到的知识点. 在Android中,用string-array是一种简单的提取XML资源文件数据的方法. 例子 ...

  7. 文本表格文件指定分隔符分列转Excel(java实现)

    我的需求: 嗯,实习中遇到,需要过滤数据然后以指定的列名输出为excel 我是这样解决的: 写出到一个文本或者表格文件然后指定分隔符分列的输出excel,因为要设计去重处理. 我需要做的: 写一个文本 ...

  8. modelform+代码发布系统前奏

    目录 注意点 form.html 添加编辑页面 ModelForm 添加 编辑 删除 代码优化 优化1 优化2 优化3 注意点 <form class="form-horizontal ...

  9. 8.4 StringBuilder的介绍及用法(String 和StringBuilder区别)

    * StringBuilder:是一个可变的字符串.字符串缓冲区类.** String和StringBuilder的区别:* String的内容是固定的.(方法区的内容)* StringBuilder ...

  10. EOS基础全家桶(七)合约表操作

    简介 本篇我们开始来为后续合约开发做准备了,先来说说EOS内置的系统合约的功能吧,本篇将侧重于合约表数据的查询,这将有利于我们理解EOS的功能,并可以进行必要的数据查询. EOS基础全家桶(七)合约表 ...