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

一.ELF目标文件格式

  • 目标文件:ABI,应用程序二进制接口,是编译器生成的文件。
  • ELF:可执行的和可链接的格式,是一个目标文件格式的标准。三种类型是:
    • 可重定位文件:Linux中每个内核源代码.c文件都会生成一个同名的.o文件,该文件即为可重定位目标文件。
    • 可执行文件:由多个可重定位文件结合生成。
    • 共享目标文件:共享库,指可以被可执行文件或者其他库文件使用的目标文件。Linux下共享库后缀为.so的文件。
  • ELF文件的作用:参与程序的连接(建立一个程序)和程序的执行(运行一个程序)。
  • ELF格式简介

可以使用入下命令将hello.c编译为一个32位静态链接ELF可执行文件 hello.m32.static 。

ELF文件由4部分组成,分别是ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。

二.程序编译

预处理:.i 文件任然是文本文件,可用任意编辑工具打开查看。

gcc -E hello.c -o hello.i

编译-S 表示只进行编译而不进行汇编(仅生成汇编代码,不进一步翻译为机器指令),-m32生成32位平台格式文件,其与64位使用不同的寄存器名及指令集。

gcc -S hello.i -o hello.s -m32

汇编:汇编后形成的.o文件已经是ELF格式文件了。

gcc -c hello.s -o hello.o.m32 -m32

链接:将各种代码和数据部分收集起来并且组合成为一个单一文件的过程,这个文件被加载到内存中并执行。将输编译输出的.o文件与libc库文件进行链接,生成最终的可执行文件。

gcc hello.o.m32 -o hello.m32.static -m32 -static

三.链接与库

  • 链接从过程上分为:符号解析重定位
  • 符号解析:编译器需要到其他的共享库中找到printf的“定义(机器指令片段)”,找到后把该机器指令与hello.o拼接到一起,生成了执行文件hello。hello中printf就存在了(有定义即有了明确的地址)。
  • 重定位:把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程。即在装入时对目标程序中指令和数据的修改过程。
  • 根据链接的时机不同,又分为静态链接动态链接
  • 静态链接:在编译链接时直接将需要的执行代码复制到最终的可执行文件中。
  • 动态链接:在编译时不直接复制可执行代码,是通过记录一系列符号和参数,在程序运行或加载时将这些信息传递给操作系统。
    • 在编译时候不加“ -static ”选项,编辑器会默认使用动态链接。静态链接在编译时会把需要的所有代码都链接进去所以应用程序相对比较大。如下图所示:
  • 动态链接分为可执行程序装载时动态链接运行时动态链接

装载时动态链接

使用如下命令:

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

可以将一个动态库源码编译成libshlibexample.so文件。

运行时动态链接

运行时动态链接库的源文件为 dllibexample.h 和 dllibexample.c。

编译成 libdllibexample.so 文件的指令如下:

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

动态链接实例

如下代码分别以装载时动态链接和运行时动态链接调用了两个动态链接库。

#include<stdio.h>
#include "shlibexample.h"
#include<dlfcn.h> int main()
{
printf("This is a Main program!\n"); /*装载时动态链接*/
printf("Calling SharedLibApi() function of libshlibexample.so!\n");
SharedLibApi(); /*运行时动态链接*/
void *handle=dlopen("libdllibexample.so",RTLD_NOW);
if(handle == NULL)
{
printf("Open Lib libdllipexample.so Error:%s\n",dlerror());
return FAILURE;
}
int (*func)(void);
char *error;
func=dlsym(handle,"DynamicalLoadingLibApi");
if((error=dlerror())!=NULL)
{
printf("DynamicalLoadingLibApi not found:%s\n",error);
return FAILURE;
}
printf("Calling DynamicalLoadingLibApi() function of libdllibexample.so!\n");
func();
dlclose(handle);
return SUCCESS;
}

编译运行:

$ sudo gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32
$ export LD_LIBRARY_PATH=$PWD #将当前目录加入默认路径,否则main找不到依赖的库文件,当然也可以将库文件复制到默认路径下。
$ ./main

其中,参数-L指明头文件所在的目录,参数-l指明库文件名,如libshlibexample.so去掉lib和.so部分。参数-ldl指明其所需要使用共享库dlopen等函数。

最终的编译运行效果如下:

遇到的问题1:

解决办法:

libshlibexample.so是一个第三方库,并不存在于系统的默认路径中,搜索不到。因此一个简答的方法是把libshlibexample.so放到/usr/local/lib目录下,就可以解决这个问题。

进入libshlibexample.so所在的目录,打开终端,运行以下命令即可:

sudo cp libshlibexample.so /usr/local/lib/

遇到的问题2:

解决办法:

运行时动态链接的操作不在该目录下,因此找不到libdllibexample.so文件。在该目录下重新编译得到libdllibexample.so文件即可。

四.程序装载

  • shell本身不限制命令行参数的个数,命令行参数个数受限于命令本身。
  • shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数
  • execve的函数原型:
int execve(const char *filename,char *const argv[],char * const envp[]);
  • 调用关系:sys_execve() -> do_execve() -> do_execve_common() -> exec_binprm() -> search_binary_handler() -> load_elf_binary() -> start_thread()

  • fork与execve的区别和联系:

    • 都是比较特殊的系统调用;
    • fork在陷入内核态后有两次返回,第一次返回到原来父进程的位置继续向下执行(这和其他的系统调用是一样的),第二次是在子进程中fork也返回了一次,这次会返回到ret_from_fork,之后正常返回用户态;
    • execve在执行时陷入内核态,在内核中调用execve加载的可执行文件把当前进程的可执行程序给覆盖了,当execve的系统调用返回时,返回的已经不是原来的那个可执行程序了,返回的是新的可执行程序执行的起点,即main函数的大致位置(一般地址为0x8048xxx,由编译器设定)。
  • 执行 readelf -h 可以查看 ELF 可执行文件首部信息。

五.实验

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

将menu目录删除,利用git命令克隆一个新的menu目录。

用test_exec.c将test.c覆盖。查看test.c文件可以看到新增加了exec系统调用。



启动内核,使用help命令可以发现增加了exec指令,执行exec指令发现比fork指令增加了一行输出“hello word!”。实际上是新加载了一个可执行程序来输出一行语句。

返回到LinuxKernel目录下,启动内核。在进行gdb调试之前先启动gdb,把3.18.6的内核加载进来,之后连接到target remote 1234。

设置断点:



执行到 start_thread 处的断点,使用 po new_ip 指令打印其指向的地址:

退出调试状态,输入readelf -h hello可以查看hello的EIF头部

参考资料连接:https://www.cnblogs.com/feifanrensheng/p/10039959.html

2019-2020-1 20199324《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. Flink与Spark Streaming在与kafka结合的区别!

    本文主要是想聊聊flink与kafka结合.当然,单纯的介绍flink与kafka的结合呢,比较单调,也没有可对比性,所以的准备顺便帮大家简单回顾一下Spark Streaming与kafka的结合. ...

  2. 在h5页面上添加音乐播放

    接到需求说要做一个h5轮播图,同时配上背景音乐. Html部分: <!--音乐开始--> <div id="music"> <div id=" ...

  3. JS-语句四

    For 循环: for 循环是创建循环时常会用到的工具. 下面是 for 循环的语法: ; 语句 ; 语句 ) { 被执行的代码 } 其中语句1是初始值:语句2是.条件判断:语句3是状态改变:被执行的 ...

  4. 循环(while,break,continue),转义字符

    01. 程序的三大流程 在程序开发中,一共有三种流程方式: 顺序 -- 从上向下,顺序执行代码 分支 -- 根据条件判断,决定执行代码的 分支 循环 -- 让 特定代码 重复 执行 02. while ...

  5. UVA 11584 入门DP

    一开始把它当成暴力来做了,即,从终点开始,枚举其最长的回文串,一旦是最长的,马上就ans++,再计算另外的部分...结果WA了 事实证明就是一个简单DP,算出两个两个点组成的线段是否为回文,再用LCS ...

  6. ssh到ubuntu没颜色

    ssh远程到ubuntu系统, 没有颜色. 原因是 .bashrc 配置没生效. $ echo '. $HOME/.bashrc' > ~/.profile

  7. 吴裕雄--天生自然 PHP开发学习:While 循环

    <html> <body> <?php $i=1; while($i<=5) { echo "The number is " . $i . &q ...

  8. UML-GRASP总结

    对象设计的核心 1).对象交互 2).职责分配

  9. pip常见使用方法

    pip可以理解类似yum管理rpm包的管理python包工具 pip参数解释 pip --help Usage: pip <command> [options] Commands: ins ...

  10. protobuf 的enum与string转换

    c/c++ enum 介绍 说起c/c++ 的enum,比起python 真的是方便简洁 enum type{ type1 = 0, type2 } enum的元素对应的int 默认从0 开始依次增加 ...