可执行程序的装载

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

  1. 可执行程序是怎么来的?通过以下这个图来呈现过程:

以我们常写的helloworld为例。我们编写了一个helloworld的.c文件,我们来把它进行预处理,预处理就是gcc –e –o hello.cpp(预处理的中间命令)hello.c –m32,这里就把.c的文件预处理了一下。我们可以看到hello.cpp里面有前面的include.h文件,如果里面有宏定义的话,把宏定义都替换一下,实际就把字符串做一个处理。hello.cpp我们把它编译成汇编代码hello.s。然后我们再将汇编代码编译成目标代码hello.o。这时候我们就都得到了一个二进制文件hello.o。之前的文本文件都是可读的,这里的hello.o是乱码。他是二进制文件,里面有一些机器指令,不是可执行文件。把它链接成可执行文件,这个命令大家就比较熟悉了,gcc –o hello hello.o –m32。hello.o与hello都是elf格式的文件。

处理过程的命令如下

gcc –e –o hello.cpp hello.c –m32
gcc -x cpp-output -S -o hello.s hello.cpp -m32
gcc -x assembler -c hello.s -o hello.o -m32
gcc -o hello hello.o -m32
gcc -o hello.static hello.o -m32 -static
  1. 接下来看一下目标文件的格式,有哪些是目标文件?常见的文件格式,一个是A.out最古老的目标文件格式,然后后来发展成coff,现在我们常用的pe、elf(是一个文件格式的标准)。但是目标文件我们一般也叫它ABI(应用程序二进制接口),实际上在目标文件里面它已经是二进制兼容的格式了,什么叫二进制兼容的,也就是说它这个目标文件已经是适应到某一种cpu体系结构上的二进制指令。比如说我们在一个32位 x86编译出来的目标文件链接成arm上的可执行文件肯定是不可以的。这地方就一个二进制兼容的叫ABI。如图

  2. elf文件格式中的三种主要目标文件:

  3. 一个可重定位(relocatable)文件保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。(主要是.o文件)

  4. 一个可执行(executable)文件保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。

  5. 一个共享object文件保存着代码和合适的数据,用来被下面的两个链接器链接。第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。(主要是.so文件)

  6. elf文件与linux进程虚拟空间内存对应关系:

当elf文件加载到内存的时候,他把代码的数据加载到一块内存中来,其中有很多段代码。加载进来之后默认从0x8048000开始加载,前面是elf头部的一些信息,一般头部的大小会有不同,加载的入口点的位置可能是0x8048300,即程序的实际入口。当启动一个刚加载过可执行文件的进程的时候,开始执行的入口点。文件是一个elf的静态连接文件,链接的时候已经链接好了。从这(0x8048300)开始执行,压栈出栈,从main函数到结束,所有的链接在静态链接时候已经设定好了。正常需要用到共享库或动态链接的时候,情况会复杂一些。

二、可执行程序、共享库和动态加载

使用如下几个文件来举例说明:

shlibexample.h如下

shlibexample.c如下

gcc -shared shlibexaple.c -o libshlibexample.so -m32

生成了一个共享库文件

dllibexample.h如下



dllibexample.c如下



编译成.so文件

gcc -shared dllibexample.c -o dllibexample.so -m32

main.c如下



最后编译main,注意这里只提供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!

三、使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve

首先更改实验环境中的代码,先进入menu目录下,如图

其中hello.c的代码如下

test.c中增加的代码如下:

int Exec(int argc, char *argv[])
{
int pid;
/* fork another process */
pid = fork();
if (pid < 0)
{
/* error occurred */
fprintf(stderr,"Fork Failed!");
exit(-1);
}
else if (pid == 0)
{
/* child process */
printf("This is Child Process!\n");
execlp("/hello","hello",NULL);
}
else
{
/* parent process */
printf("This is Parent Process!\n");
/* parent will wait for the child to complete*/
wait(NULL);
printf("Child Complete!\n");
}
} int main()
{
PrintMenuOS();
SetPrompt("MenuOS>>");
MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);
MenuConfig("quit","Quit from MenuOS",Quit);
MenuConfig("time","Show System Time",Time);
MenuConfig("time-asm","Show System Time(asm)",TimeAsm);
MenuConfig("fork","Fork a new process",Fork);
MenuConfig("exec","Execute a program",Exec);
ExecuteMenu();
}

然后我们需要改一下Makefile(关于Makefile)的文件内容,使用命令vi Makefile,添加静态编译hello.c,

gcc -o hello hello.c -m32 -static
cp hello ../rootfs/
find init hello | cpio -o -Hnewc |gzip -9 > ../rootfs.img



在更改Makefile之后,便输入命令make rootfs来编译了,不过第一次提示我权限不够,我想既然是权限不够,那么就加了个sudo,再编译,果然成功了,如图

接下来要使用gdb开始跟踪调试了,因为我们当前处于menu这个目录下,所以先cd ..跳到上级目录再使用命令qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S来启动虚拟机,并在最开始的时候停住(-S),-s表示gdb服务器使用默认端口号1234来连接

并使用gdb跟踪调试



在如下几个地方设置断点

先连续按3个c执行之后,出现menuos界面,如图

我们在MenuOS里面输入exec,会弹出两条消息,并停住,即遇到了第一个断点sys_execve,如图

按s进行跟踪,会看到do_execve,如图



我们按c让它继续跑,回跑到第二个断点load_elf_binary处停下来,输入list查看附近代码



继续按c跑起来,停在第三个断点start_thread处,这里有个new_ip,那么这个new_ip到底是指向哪里的?



使用po new_ip可以看到如下



再水平分割一个控制台出来,使用命令readelf -h hello,可以看到入口地址跟上面的po new_ip所显示的地址一样



接下里按s,可以看到在进行修改内核堆栈的操作。



可以看到之前的压栈的ip和sp都被改成了新的ip(程序hello的入口点地址)和新的sp,这样在返回到用户态的时候程序就有一个新的可执行上下文环境了。此时再按一下c之后,就结束了,exec的执行就完毕了。

书上内容

  1. 虚拟文件系统VFS作为内核子系统,为用户空间程序提供了文件和文件系统相关的接口。之所以可以使用通用接口对所有类型的文件系统进行操作,是因为内核在它的底层文件系统接口上建立了一个抽象层。VFS抽象层之所以能衔接各种各样的文件系统,是因为它定义了所有文件系统都支持的、基本的、概念上的接口和数据结构。
  2. Unix文件系统四要素:文件、目录、索引节点、安装点。VFS把目录当作文件看待,所以可以对目录执行和文件相同的操作。索引结点:存储访问权限、大小、拥有者、创建时间等信息。超级块:存储文件系统的控制信息。
  3. VFS四个主要的对象类型(1)超级块对象:代表一个已安装的文件系统。(2)索引节点对象:代表一个文件。(3)目录项对象:代表一个目录项,是路径的一个组成部分。(4)文件对象:代表由进程打开的文件。VFS将目录作为文件来处理,目录项不同于目录。
  4. 超级块对象:各种文件系统必须实现超级块对象,用于存储特定文件系统信息,通常对应与存放在磁盘特定扇区的文件系统超级块或文件系统控制块。对于sysfs等非磁盘文件系统,超级块存在内存中;索引节点对象:索引节点对象包含内核在操作文件或目录时需要的全部信息;目录项对象(dentry):路径中的每个组成部分都由一个索引节点对象表示。目录项缓存包括三个主要部分:”被使用的”目录项链表、“最近被使用的”双向链表、散列表和相应的散列函数用来快速地将给定路径解析为相关目录项对象。文件对象:表示进程已打开的文件,是打开文件在内存中的表示。
  5. 设备:块设备,随机访问固定大小数据片的设备。字符设备,按字符流方式有序被访问,和块设备的区别在于随机访问。扇区,最小可寻址单元(物理属性)。块,文件系统的最小逻辑可寻址单元。Linux电梯调度算法:相邻合并;驻留时间过长的请求放入队列尾部;保证扇区的磁盘访问顺序;不合适的请求插入位置被放入列队尾部。

20169212《Linux内核原理与分析》 第九周作业的更多相关文章

  1. 2019-2020-1 20199303<Linux内核原理与分析>第二周作业

    2019-2020-1 20199303第二周作业 1.汇编与寄存器的学习 寄存器是中央处理器内的组成部份.寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令.数据和位址.在中央处理器的控制部件中 ...

  2. 20169219 linux内核原理与分析第二周作业

    "linux内核分析"的第一讲主要讲了计算机的体系结构,和各寄存器之间对数据的处理过程. 通用寄存器 AX:累加器 BX:基地址寄存器 CX:计数寄存器 DX:数据寄存器 BP:堆 ...

  3. 2019-2020-1 20199314 <Linux内核原理与分析>第二周作业

    1.基础学习内容 1.1 冯诺依曼体系结构 计算机由控制器.运算器.存储器.输入设备.输出设备五部分组成. 1.1.1 冯诺依曼计算机特点 (1)采用存储程序方式,指令和数据不加区别混合存储在同一个存 ...

  4. Linux内核原理与分析-第一周作业

    本科期间,学校开设过linux相关的课程,当时的学习方式主要以课堂听授为主.虽然老师也提供了相关的学习教材跟参考材料,但是整体学下来感觉收获并不是太大,现在回想起来,主要还是由于自己课下没有及时动手实 ...

  5. 2019-2020-1 20199314 <Linux内核原理与分析>第一周作业

    前言 本周对实验楼的Linux基础入门进行了学习,目前学习到实验九完成到挑战二. 学习和实验内容 快速学习了Linux系统的发展历程及其简介,学习了下的变量.用户权限管理.文件打包及压缩.常用命令的和 ...

  6. Linux内核原理与分析-第二周作业

    写之前回看了一遍秒速五厘米:如果

  7. 2020-2021-1 20209307 《Linux内核原理与分析》第九周作业

    这个作业属于哪个课程 <2020-2021-1Linux内核原理与分析)> 这个作业要求在哪里 <2020-2021-1Linux内核原理与分析第九周作业> 这个作业的目标 & ...

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

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

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

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

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

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

随机推荐

  1. Java线程中带有返回值的线程Callable

    在Java5之前,线程是没有返回值的,常常为了“有”返回值,破费周折,而且代码很不好写.或者干脆绕过这道坎,走别的路了.现在Java终于有可返回值的任务(也可以叫做线程)了. 可返回值的任务必须实现C ...

  2. php 中文繁简体转换

    <?php $testC = new TestC (); $testC->index (); class TestC { private $utf8_gb2312; private $ut ...

  3. Python企业级开发之一:基础

    Python企业级开发相关内容.这里涉及到Python开发过程中的问题以及解决办法.还提供新的开发思路. 脚本开发的一些共同的问题.如:1.对OO的支持不完善,2.问题定位方式给出的信息过于晦涩,3. ...

  4. spring中用到哪些设计模式

    1.工厂模式,这个很明显,在各种BeanFactory以及ApplicationContext创建中都用到了: 2.模版模式,这个也很明显,在各种BeanFactory以及ApplicationCon ...

  5. ubuntu下安装JDK并搭建activeMQ

    1.安装JDK,网上有人说activeMQ支持持JDK1.7及以上版本,未实际测试,保险起见我这里直接安装JDK1.7. #apt-get install openjdk--jdk 2.设置环境变量 ...

  6. php 笔试题

    1.用PHP打印出前一天的时间格式是2006-5-10 22:21:21 解:echo date(‘Y-m-d H:i:s’, strtotime(‘-1 day’)); 原因: format 字符说 ...

  7. Bootstrap Affix(附加导航(Affix)插件的用法)

    原文网址:http://www.runoob.com/bootstrap/bootstrap-affix-plugin.html Bootstrap 附加导航(Affix)插件 附加导航(Affix) ...

  8. SuiteScript > RecordType internalID采集步骤与结果

    当你碰到一个有很多sub Fields的id需要map到js文件的时候,是不是想到一个个复制到js文件中?建立成Object,library起来,方便不同的module中共享. 一个复制,很烦,很浪费 ...

  9. CSS使用

    CSS介绍 css是英文Cascading Style Sheets的缩写,称为层叠样式表,用于对页面进行美化. 语法:style = 'key1:value1;key2:value2;' 存在方式有 ...

  10. python 学习笔记 redis操作

    Redis redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorte ...