课本:第七章 可执行程序工作原理

  • ELF目标文件格式

    • 目标文件:编译器生成的文件。
    • 目标文件的格式:out格式、COFF格式、PE(windows)格式、ELF(Linux)格式。
    • ELF(Executable and Linkable Format)即可执行和可链接的格式,是一个目标文件格式的标准。ELF格式的文件用于存储Linux程序。
    • ELF文件的3钟类型:可重定位文件、可执行文件、共享目标文件。
      • 可重定位文件:这种一般是中间文件,还需要继续处理。由汇编器和编译器创建,一个源代码文件会生成一个可重定位文件。文件中保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件、静态库文件或者共享目标文件(即动态库文件)。如Linux下.c文件都会生成一个同名的.o文件,这就是可重定位目标文件。
      • 可执行文件:一般由多个可重定位文件结合生成,是完成了所有重定位工作和符号解析(除了运行时解析的共享库符号)的文件,文件中保存着一个用来执行的程序。
      • 共享目标文件:共享库,是指可以被可执行文件或其他库文件使用的目标文件,例如标准C的库文件libc.so。可以简单理解为没有主函数main的“可执行”文件,只有一堆函数可供其他可执行文件调用。Linux下共享库后缀为.so文件,代表shard object。
    • ELF文件作用:ELF文件参与程序的链接和程序的执行。
      • 如果用于编译和链接(可重定位文件),编译器和链接器将把ELF文件看作节的集合,所有节由节头表描述,程序头表可选。
      • 如果用于加载执行(可执行文件),加载器将把ELF文件看作程序头表描述的段的集合,一个段可能包含多个节和节头表可选。
      • 如果是共享文件,则两者都含有。
    • ELF格式
      • 主体是各种节,还有描述这些节属性的信息(Program header table和Section header table),以及ELF文件的整体描述信息(ELF header)。整体如下图所示:
      • ELF Header
      • Section Header
      • Program Header
    • 对ELF进行研究的相关操作指令
      • man elf:详细格式定义。
      • readelf:用于显示一个或多个elf格式的目标文件的信息,后面可以使用多个参数,如-a、-h、-S、-l等。
      • objdump:显示二进制文件信息,有-f、-h、-r等一系列参数。
      • hexdump:用十六进制的数字来显示elf的内容。
  • 程序编译
    • 预处理:gcc -E hello.c -o hello.i
    • 编译:gcc -S hello.i -o hello.s -m32
    • 汇编:gcc -c hello.s -o hello.o -m32
    • 链接:gcc hello.o -o hello -m32 -static
  • 链接与库
    • 链接

      • 从过程上讲:符号解析、重定位。
      • 根据链接时机:静态链接、动态链接。
  • 程序装载
    • exec函数:sys_execve()系统调用
    • 调用关系:sys_execve() -> do_execve() -> do_execve_common() -> exec_binprm() -> search_binary_handler() -> load_elf_binary() -> start_thread()
    • fork与execve的区别和联系
      • 都是比较特殊的系统调用
      • fork在陷入内核态后有两次返回,第一次返回到原来父进程的位置继续向下执行,第二次是在子进程返回,这次会返回到ret_from_fork,之后正常返回用户态。
      • execve在执行时陷入内核态,在内核中调用execve加载的可执行文件把当前进程的可执行程序给覆盖了,当其返回时,返回的已经不是原来的那个可执行程序了,而是新的程序,返回的是新的可执行程序执行的起点,即main函数的大致位置(一般地址为0x8048xxx,由编译器设定)
  • 内核支持多格式是在执行execve时,它加载了文件的头部,来判断文件是什么格式,在链表中寻找能够解析这种文件格式的内核模块
  • 两种加载方法
    • 静态库:直接执行可执行程序的入口
    • 动态库:由ld来动态链接这个程序,再把控制权移交给可执行程序的入口

静态链接与动态链接的可执行文件对比&装载时及运行时动态链接的实例

在实例中,编写了一个hello.c文件,输出“Hello World!”用于实验。在使用gcc -static -o hello.m32.static hello.c生成了hello.m32.static静态链接的可执行文件,用gcc -o hello.m32.dynamic hello.c生成了hello.m32.dynamic动态链接的可执行文件后,在shell中使用ls -l hello.m32.*查看静态链接和动态链接的可执行文件的大小,发现静态链接版本大小大约是动态链接版本的100倍,如下图所示:

动态链接分为可执行程序装载时动态链接和运行时动态链接,下面是这两种动态链接的实例。

装载时动态链接
编写shlibexample.h和shlibexample.c,是一个简单动态库的源码,只提供一个函数ShardLibApi()。源码如下图所示:


之后编译成libshlibexample.so文件:

运行时动态链接
编写dllibexample.h和dllibexample.c文件,里面写了DynamicalLoadingLibApi()函数供运行时动态链接。源码如下图所示:


之后编译成libdllibexample.so文件:

生成了两个so文件之后,开始编写执行函数main,如下图所示:

下面使用gcc编译此main.c文件,使用参数-L指明头文件所在目录(-L.表示头文件就在当前目录),使用-l指明库文件名,如libshlibexample.so去掉lib和.so部分。dllibexample只在程序运行到相关语句时才会访问,在编译时不需要任何相关信息,只是用-ldl指明其需要使用共享库dlopen等函数。当然在实际运行时,也要确保libdllibexample.so是应用可以查找到的,这也是要修改环境变量LD_LIBRARY_PATH的原因,编译结果如下图所示:

实验:Linux内核如何装载和启动一个可执行程序

使用实验楼环境,首先移除LinuxKernel下的menu,从老师的github上clone一个新的menu目录,在打开其test.c文件中发现,在menuOS的功能中增加了一个exec功能,其源代码如下:

并在main函数中调用了Exec函数,如下图所示:

在查看其Makefile的定义规则时看到,在rootfs中,在生成根文件系统时把编写的hello.c生成的hello执行程序一并载入到镜像rootfs.img中了,hello的功能是打印"Hello World!",如下图所示:

使用make rootfs将menuOS在qemu下运行起来,在功能列表下发现增加了exec指令,执行exec指令,在子进程中成功打印了hello world,执行效果如下图所示:

下面使用gdb对这个过程进行跟踪调试:
为避免在加载内核时在某些系统调用函数初始化的时候遇到断点而暂停内核加载,我先在sys_execve一处设置断点,在执行到sys_execve时再分别在load_elf_binary、start_thread处设置断点。
执行到sys_execve时的执行效果如下图所示:

在此时gdb返回的断点信息可以发现,在sys_execve内部调用返回do_execve()函数,如下图所示:

之后让程序继续执行,后发现程序执行到load_elf_binary处的断点,如下图所示:

后继续执行程序,程序来到start_thread断点处,执行到此函数,即开始静态链接hello可执行文件,elf_entry指向了hello中分配的入口地址,使用po new_ip指令打印其指向的地址,new_ip是返回到用户态的第一条指令的地址,理论上是hello的入口地址,如下图所示:

后结束gdb调试,返回shell,在menu文件夹中使用readelf -h hello来查看hello的elf头部文件信息,查看其入口地址与new_ip所指向的地址是否一致,发现是一致的,同为0x8048d0a,如下图所示:

问题与总结

本周的实验在自己的电脑上遇到了一个问题,无论是在进行预处理、编译、汇编、链接实验中还是在装载和运行时动态链接实验中,只要是在gcc编译源码时在后面加上-m32,编译就会报错,如下图所示:

究其原因,经过查阅相关资料,发现因为自己的环境是64位的linux环境,需要生成32位的目标文件,虽然环境可以向下兼容,但是gcc的开发环境没有安装完备,编译时无法找到生成32位目标文件所需的一系列头文件。
解决方案可以是完善gcc开发环境,安装gcc的多环境库文件,在shell中输入sudoapt-get install gcc-multilib解决。
对于可执行程序的装载,个人的理解是当调用fork创建子进程后,子进程要调用一种exec函数以执行一个新程序;在调用一种exec函数时,该进程执行的程序被替换为新程序,而新程序则从其main函数处开始执行,但依然始终是同一个进程。课本中使用中国古代的“庄周梦蝶”的故事以类比,确有异曲同工之处。

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

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

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

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

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

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

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

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

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

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

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

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

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

  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. 2020-2021-1 20209307 《Linux内核原理与分析》第九周作业

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

随机推荐

  1. HDFS简单编程实例:文件合并

    下图显示了HDFS文件系统中路径为“localhost:50070/explorer.html#/user/hadoop”的目录中所有的文件信息: 对于该目录下的所有文件,我们将执行以下操作: 首先, ...

  2. mysql数据库连接出问题,提示超时 java.sql.SQLException: An attempt by a client to checkout a Connection has timed out.解决办法

    mysql数据库连接出问题,提示超时,后来发现问题在于连接mysql数据库的jar包跟数据库版本不对应导致的,更换jar包一致就解决了.

  3. Redis出现的问题

    1):Could not connect to Redis at 127.0.0.1:6379: Connection refused 分析: 1-1:虚拟机中的 6379 端口可能没有开启 查看虚拟 ...

  4. LeetCode--024--两两交换链表中的节点(java)

    给定一个链表,两两交换其中相邻的节点,并返回交换后的链表. 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换. 示例: 给定 1->2->3->4, 你应该返回 2-&g ...

  5. nlp基础(一)基本应用

    1.问答系统,它主要是针对那些有明确答案的用户问题,而且通常面向特定的领域,比如金融,医疗,这一类的机器人.它的技术实现方案分为基于检索和基于知识库两大类. 2.第二个任务型对话系统,大家看论文的时候 ...

  6. 本地项目初始化git推送到服务器

    1. 创建本地项目,在项目根目录执行git init命令 git init 2.在git服务器上创建一个仓库,这里使用GitHub创建一个仓库. 3.执行git remote add origin & ...

  7. 记一次前端css样式的三角形的应用

    1)面试题是这样的要求用css实现 <section> <div></div> <div></div> </section> & ...

  8. [Codeforces477D]Dreamoon and Binary

    Problem 给定一个字符串数的二进制表示(不含前导0)s(长度不超过5000), 对于一个数n(初值为0),可以进行以下两种操作: 1.将n的二进制表示(无前导0)写到已经写的串的后面. 2.n加 ...

  9. Python中的装饰器的简单介绍01

    一. 装饰器是什么? 简单来说,装饰器其实也就是一个函数,一个用来包装函数的函数,返回一个修改之后的函数对象,将其重新赋值原来的标识符,并永久丧失对原始函数对象的访问. 二.装饰器语法 (1)无参数装 ...

  10. GDT全局描述符表

    GDT全局描述符表 什么是GDT全局描述符表 GDT全称为Global Descriptor Table,全局描述符表. 保护模式的寻址方式不在使用寄存器分段的方式直接寻址方式了.而采用的是使用GDT ...