2017-2018-1 20179215《Linux内核原理与分析》第八周作业
实验:ELF文件格式与程序的编译链接
一、可执行文件的创建
从源代码到可执行程序所要经历的过程概述:
源代码(.c .cpp .h)经过c预处理器(cpp)后生成.i文件,编译器(cc1、cc1plus)编译.i文件后生成.s文件,汇编器(as)汇编.s文件后生成.o文件,链接器(ld)链接.o文件生成可执行文件。gcc是对cpp、cc1(cc1plus)、as、ld这些后台程序的包装,它会根据不同的参数要求去调用后台程序。
以helloworld程序为例:
gcc -E -o hello.cpp hello.c -m32 //生成预处理文件
hello.cpp //预处理负责把include的文件包含进来及宏替换等工作
gcc -x cpp-output -S -o hello.s hello.cpp -m32 编译成汇编代码hello.s
gcc -x assembler -c hello.s -o hello.o -m32 编译成目标代码,得到二进制文件hello.o
gcc -o hello hello.o -m32 链接成可执行文件hello
./hello 运行hello文件
二、可执行文件的组成
(1)以ELF为格式的主要有三种文件:
①可重定位文件:保持着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者一个共享文件。例如.o文件。
②可执行文件:可以运行的文件。该文件指出了exec(BA_OS)如何来创建进程映象。再来联想下程序和进程的区别。到底这种可执行文件是进程还是程序?我们发现它的段中只含.text和.data一类的段,而不含有堆栈段。所以可以确定它只是程序。当它被操作系统调入内存开始执行时才会真正的成为进程。例如.out文件。
③共享object文件:保存着代码和数据,被两个链接器链接。一个是连接编辑器,可以和其他可重定位和共享object文件来创建其他的object。第二个是动态链接器,联合一个可执行文件和其他共享object文件来创建一个进程映像。
(2)ELF文件的头部:使用命令 readelf -h hello 查看hello文件的头:
ELF的头保存的是元数据,也就是路线图,描述了文件的组织情况。比如程序头表(program header table)告诉系统如何来创建一个进程的内存映像。section头表(section header table)包含描述文件sections的信息。每个section在这个表中有一个入口;每个入口给出了该section的名字,大小等等信息。ELF的剩余部分是sections,包括代码段,数据段。这些在程序变成进程映像时加载到内存的虚拟地址空间中,从ELF头开始加载,所以从图中可以看出真正的代码从0x400430开始,这才是真正的程序入口。静态链接时程序所需要的代码全部在代码段中,而动态链接就不一样了,它会在运行时候去找内存中间部分加载的库函数。
三、可执行程序的加载
(1)装载:可执行程序的执行环境shell。shell就是用户键入命令,加载并执行可执行程序的控制台.shell的本质就是提供图形化的界面,将用户写入的字符串解析成真正执行的命令或者说可执行程序。
有两个问题:
①真正执行程序的是什么程序或指令?答案是execve系统调用(库函数exec*都是execve的封装例程)。
②如何给该系统调用传参?也就是传参的默认格式是什么?答:shell会传入execve的参数有两种,一种是程序本身参数,也就是main的参数argc,argv;第二种是shell环境变量的参数,envp字符串数组中。
来看下execve的参数类型:
int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
当然,有些程序的main函数是不处理环境变量参数的,例如常见的:
int main(int argc, char *argv[])。
但是有时候也支持,例如,所以这个时候传入的环境变量参数才会被解析使用。
int main(int argc, char *argv[], char *envp[])
(2)execve是如何从内核态将参数传给进程(假设该进程是用户态)的用户态堆栈的?
发现,仍是从用户态的数据段中复制到用户态堆栈中的。那么跟内核什么关系呢?执行execve的进程就是当前的shell,所以参数首先会被压在当前shell进程的内核堆栈中。关键在传入内核的参数是指针,所以内核(sys_ execve)要做的就是把指针的值复制回新进程的代码段,再复制到进程的用户堆栈段。初始化后的进程内存地址空间就是第二张图那样。也就解释了sys_ execve加载进程并初始化的作用、结果。shell每次都fork一个shell去执行命令,所以,当新进程起来后,启动它的shell结束,曾经保存的参数就不要了。
(3)两种动态链接
①装载时动态链接(Load-time Dynamic Linking):这种方法的前提是在编译之前已经明确知道要调用的动态库的哪些函数,编译时在目标文件中只保留必要的链接信息,而不含动态库函数代码;当程序执行时,调用函数的时候利用链接信息加载动态库函数代码并在内存中将其链接入调用程序的执行空间中(全部函数加载进内存),其主要目的是便于代码共享。(动态加载程序,处在加载阶段,主要为了共享代码,共享代码内存)
②运行时动态链接(Run-time Dynamic Linking):这种方式是指在编译之前并不知道将会调用哪些动态库函数,完全是在运行过程中根据需要决定应调用哪个函数,将其加载到内存中(只加载调用的函数进内存);并标识内存地址,其他程序也可以使用该程序,并获得动态库函数的入口地址。(动态库在内存中只存在一份,处在运行阶段)
四、使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve
(1)增加execve系统调用指令:
(2)在Makefile找到启动内核命令:
(3)启动内核后,找到增加的exec命令,执行exec——新加载的执行程序来输出的“hello world”:
(4)冻结后gdb跟踪,设置断点:
(5)进入do_execve内部
(6)继续执行,到了load_elf_binary
(7)list后,可以看出静态链接时elf_interp为空
(8)再执行,跟踪到start_thread
五、总结:
新的可执行程序是从new_ ip开始执行,start_ thread实际上是把返回到用户态的位置从Int 0x80的下一条指令,变成了规定的新加载的可执行文件的入口位置,即修改内核堆栈的EIP的值作为新程序的起点。当执行到execve系统调用时,陷入内核态,用execve加载的可执行文件覆盖当前进程的可执行程序,当execve系统调用返回时,返回新的可执行程序的执行起点(main函数位置),所以execve系统调用返回后新的可执行程序能顺利执行。对于静态链接的可执行程序和动态链接的可执行程序execve系统调用返回时,如果是静态链接,elf_ entry指向可执行文件规定的头部(main函数对应的位置0x8048***);如果需要依赖动态链接库,elf_entry指向动态链接器的起点。
2017-2018-1 20179215《Linux内核原理与分析》第八周作业的更多相关文章
- 2017-2018-1 20179215《Linux内核原理与分析》第二周作业
20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...
- 20169212《Linux内核原理与分析》第二周作业
<Linux内核原理与分析>第二周作业 这一周学习了MOOCLinux内核分析的第一讲,计算机是如何工作的?由于本科对相关知识的不熟悉,所以感觉有的知识理解起来了有一定的难度,不过多查查资 ...
- 20169210《Linux内核原理与分析》第二周作业
<Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...
- 2018-2019-1 20189221 《Linux内核原理与分析》第九周作业
2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...
- 2019-2020-1 20199329《Linux内核原理与分析》第九周作业
<Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...
- 2019-2020-1 20199329《Linux内核原理与分析》第二周作业
<Linux内核原理与分析>第二周作业 一.上周问题总结: 未能及时整理笔记 Linux还需要多用 markdown格式不熟练 发布博客时间超过规定期限 二.本周学习内容: <庖丁解 ...
- 2019-2020-1 20209313《Linux内核原理与分析》第二周作业
2019-2020-1 20209313<Linux内核原理与分析>第二周作业 零.总结 阐明自己对"计算机是如何工作的"理解. 一.myod 步骤 复习c文件处理内容 ...
- 2018-2019-1 20189221《Linux内核原理与分析》第一周作业
Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...
- 《Linux内核原理与分析》第一周作业 20189210
实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...
- 2018-2019-1 20189221《Linux内核原理与分析》第二周作业
读书报告 <庖丁解牛Linux内核分析> 第 1 章 计算工作原理 1.1 存储程序计算机工作模型 1.2 x86-32汇编基础 1.3汇编一个简单的C语言程序并分析其汇编指令执行过程 因 ...
随机推荐
- jQuery Easy UI Draggable(拖动)组件
上文已经提到过了 jQuery EasyUI插件引用一般我们经常使用的有两种方式(排除easyload载入方式),所以本篇要总结的Draggable组件相同有两种方式载入: (1).使用class载入 ...
- [Sdoi2013]直径(树的直径)
//36分 #include<cstdio> #include<cstdlib> #include<cstring> #include<ctime> # ...
- Android环境搭建 NDK+ADT(免cywgin)
JDK下载: 1下载地址 http://www.oracle.com/technetwork/java/javase/downloads/index.html 2配置环境变量 JAVA_HOME:创 ...
- Symfony 使用KnpTimeBundle
使用time_diff时出现:diff.ago.hour; 解决:1:引入"knplabs/knp-time-bundle": "^1.7",https://g ...
- R语言编写乘法表
for(i in 1:9){ for(j in 1:i){ m = j*i cat(i,'*',j,'=',m,' ') } cat('\n') } 1 * 1 = 1 2 * 1 = 2 2 * 2 ...
- C#线程使用学习
线程的入口函数可以不带输入参数,也可以带输入参数: form1.cs using System; using System.Collections.Generic; using System.Comp ...
- 10 个经典PHP函数
这篇文章主要介绍了php中的10个比较经典的函数,不太常见,可以满足有特殊需求的朋友 1. sys_getloadavg() sys_getloadavt()可以获得系 统负载情况.该函数返回一个包含 ...
- MyBatis:学习笔记(4)——动态SQL
MyBatis:学习笔记(4)——动态SQL 如果使用JDBC或者其他框架,很多时候需要你根据需求手动拼装SQL语句,这是一件非常麻烦的事情.MyBatis提供了对SQL语句动态的组装能力,而且他只有 ...
- iOS UIImage 图片局部拉伸的一些学习要点
之前 做纯色局部拉伸 通过 top bottom left right 相交的阴影拉伸 屡试不爽 实施方法: imageView.image = [[UIImage imageNamed: @&q ...
- Unity Json解析IPA
今天看到一个unity 自带的解析json的IPA,感觉比litjson好用很多,废话不多,上代码 using System.Collections; using System.Collections ...