2021-2022-1 20212813《Linux内核原理与分析》第八周作业

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

一、基础知识

1、编译链接的过程

程序从源代码到可执行文件需要经过以下步骤:
预处理、编译、汇编、链接,如下图所示,每一步产生不同的文件。
2021-2022-1 20212813《Linux内核原理与分析》第八周作业
各步骤的任务如下:

  • 预处理:删除所有注释;删除所有的“#define”,展开所有的宏定义;处理所有的条件预编译指令;添加行号和文件名标识等
  • 编译:编译时,gcc首先检查代码的规范性、是否有语法错误等,以确定代码实际要做的工作。在检查无误后,gcc把代码翻译成汇编语言
  • 汇编:指把汇编语言代码翻译成目标机器指令的过程,形成的.o格式文件已经是ELF格式文件
  • 链接:将各种代码和数据部分收集起来并组合成为一个单个文件,这个文件可以被加载到内存中并执行

以下两张图分别展示的是汇编和链接之后的文件节区信息表,能够发现链接之后的大部分节Addr有了地址值,并且多了很多内容。
2021-2022-1 20212813《Linux内核原理与分析》第八周作业
2021-2022-1 20212813《Linux内核原理与分析》第八周作业

2、ELF 可执行文件格式

ELF(Excutable and Linking Format)是一个文件格式的标准。如下图所示,通过readelf -h test查看可执行文件hello的头部,头部里面注明了目标文件类型ELF32,入口点地址为0x8048730,即可执行文件加载到内存中开始执行的第一行代码地址。可执行文件的格式和进程的地址空间有一个映射的关系,当程序要加载到内存中运行时,将ELF文件的代码段和数据段加载到进程的地址空间。

2021-2022-1 20212813《Linux内核原理与分析》第八周作业
ELF文件的三种类型:

  1. 可重定位文件:保存着代码和适当的数据,用来和其他的object文件一起创建一个可执行文件或者是一个共享文件。主要是.o文件;
  2. 可执行文件:保存着一个用来执行的程序;
  3. 共享目标文件:共享库是指可以被可执行文件或其他库文件使用的目标文件

3、静态链接和动态链接

静态链接

静态链接是在生成可执行文件时进行的。在目标模块中记录符号地址,而在可执行文件中改写为指令直接使用的数字地址。
2021-2022-1 20212813《Linux内核原理与分析》第八周作业

动态链接

在装入或运行时进行链接。通常被链接的共享代码称为动态链接库(DLL, Dynamic-Link Library)或共享库(shared library)。动态链接分为可执行程序装载时动态链接和运行时动态链接:

  • 装载时动态链接

编写以下文件example.c和example.h生成共享库:

#include <stdio.h>
#include "example.h"

int fun_zzx()
{       
        printf("This is a shared libary\n");
        return 0;
}       
#ifndef _EXAMPLE_H_
#define _EXAMPLE_H_

#define SUCCESS 0
#define FAILURE (-1)

int fun_zzx();

#endif

测试文件test.c:

#include <stdio.h>

int main()
{
        fun_zzx();
        return 0;
}

测试结果如下图所示:
2021-2022-1 20212813《Linux内核原理与分析》第八周作业

  • 运行时动态链接

修改example.c文件为:

#include <stdio.h>
#include "example.h"

int fun_zzx()
{
        printf("This is a Dynamical Loading libary!\n");
        return SUCCESS;
}

新的测试文件test.c代码如下:

#include <stdio.h>
#include <dlfcn.h>
#include "example.h"

int main()
{
        printf("This is a Main program!\n");
        /* Use Dynamical Loading Lib */
        void * handle = dlopen("libexample.so",RTLD_NOW);
        if(handle == NULL)
        {
                printf("Open Lib libexample.so Error:%s\n",dlerror());
                return   FAILURE;
        }
        int (*func)(void);
        char * error;
        func = dlsym(handle,"fun_zzx");	
        if((error = dlerror()) != NULL)
        {
                printf("fun_zzx not found:%s\n",error);
                return   FAILURE;
        }
        printf("Calling fun_zzx() function of libexample.so!\n");
        func();
        dlclose(handle);
        return SUCCESS;
        return 0;
}

测试结果如下图所示:
2021-2022-1 20212813《Linux内核原理与分析》第八周作业

二、实验过程

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

execve系统调用的整个过程的简单流程图如下:

按照之前调试MenuOS的方法,启动gdb在sys_execve、load_elf_binary和start_thread处设置断点,然后运行OS:
2021-2022-1 20212813《Linux内核原理与分析》第八周作业在MenuOS中执行exec命令,跟踪结果如下:
2021-2022-1 20212813《Linux内核原理与分析》第八周作业
2021-2022-1 20212813《Linux内核原理与分析》第八周作业
追踪到start_thread,用po new_ip,得到的是0x8048730,通过readelf –h hello可以看到hello这个可执行程序它的入口点地址也是0x8048730。
2021-2022-1 20212813《Linux内核原理与分析》第八周作业
之后在执行hello程序的过程中,对寄存器的值进行修改以更新的执行环境:2021-2022-1 20212813《Linux内核原理与分析》第八周作业

三、遇到的问题

如果要调用动态加载共享库,就要使用定义在dlfcn.h中的dlopen。给出文件名libexample.so和标志RTLD_NOW打开动态链接库,返回handle句柄。dlsym函数与上面的dlopen函数配合使用,根据操作句柄(由dlopen打开动态链接后返回的指针)handle与符号(要求获取的函数或全局变量的名称)fun_zzx,返回符号对应的地址。使用此地址可以获得库中特定函数的地址,并且调用库中的相应函数。这样就可以使用动态加载共享库里面所定义的函数了。

上一篇:如何导出已有的谷歌插件,又如何把导出的插件安装到360浏览器中,又如何对插件小修小改?


下一篇:非线性规划(1)