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

  • 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. JS(JavaScript)的初了解3(更新中···)

    1. {} 在JS中我们把它叫代码块.如果代码块里的内容没有执行完,语句不会向下执行. 代码块是一个独立的整体.如果JS中某一条语句出错,那么就会在此终止不会向下执行. 2. 循环语句 循环,就是对一 ...

  2. AJAX理解

    注:首先我们要明白请求是什么?请求分两种,一.静态请求(如:返回js.css.图片等) 二.动态请求(返回跟用户有关的数据) http(apache.nginx等)服务器会判断如果是一个静态请求,会直 ...

  3. gitlab或github下fork后如何同步源的新更新内容?

    两种方式: 项目 fetch 到本地,通过命令行的方式 merge 懒人方法,只用 Github ,不用命令行 1.项目 fetch 到本地,通过命令行的方式 merge 提示:跟上游仓库同步代码之前 ...

  4. Linux修改磁盘挂载目录

    比如想把已经挂载在home目录上的硬盘挂载到data目录上, 如下操作 #df -h(查看分区情况及数据盘名称) # mkdir /data(如果没有data目录就创建,否则此步跳过) # umoun ...

  5. R语言-默认镜像设置

    问题1:如何设置默认镜像 你希望下载某些R包,因此希望设定默认的CRAN网站镜像,这样R每次下载时不需要你选择镜像. 解决方案 该方案要求用户R系统中包含一个.Rprofile文件,如方法3.16描述 ...

  6. leecode第一百四十六题(LRU缓存机制)

    class LRUCache { private: unordered_map<int, list<pair<int,int>>::iterator> _m; // ...

  7. ActiveMQ组件使用方法

    由于组件使用了spring,故需要相关的spring包及配置 首先,需要加载对应的jar包 然后,编写调用类 package com.demo.testSpring; import com.jfina ...

  8. hive新功能cube和rollup

    1.cube简称数据魔方,可以实现hive多个任意维度的查询,cube(a,b,c)则首先会对(a,b,c)进行group by,然后依次是(a,b),(a,c),(a),(b,c),(b),(c), ...

  9. 菜鸟脱壳之脱壳的基础知识(二) ——DUMP的原理

    菜鸟脱壳之脱壳的基础知识(二)——DUMP的原理当外壳的执行完毕后,会跳到原来的程序的入口点,即Entry Point,也可以称作OEP!当一般加密强度不是很大的壳,会在壳的末尾有一个大的跨段,跳向O ...

  10. sqlalchemy 获取表结构。

    from sqlalchemy.engine import reflection insp = reflection.Inspector.from_engine(engine) colums=insp ...