Linux内核装载和启动一个可执行程序的过程探究

本周学习了孟宁老师《Linux内核分析》Linux内核如何装载和启动一个可执行程序这一章的内容,为了巩固所学,现在做实验并把实验过程记录如下:

 

首先来看看代码的编译和链接的过程,我们编写好一段代码(hello.c):

#include <stdio.h>

int main()
{
    printf("Hello World!\n");

    return 0;
}

使用如下命令:

gcc -E -o hello.cpp hello.c -m32 

这条指令把源代码进行预编译处理,生成预处理文件,预处理主要对源代码中的宏进行展开以及执行其他一些文本替换的工作。所生成的预处理文件(hello.cpp)仍然是一个文本格式的文件,我们可以打开这个文件看到文件的内容。

下面这条指令:

gcc -x cpp-output -S -o hello.s hello.cpp -m32

进行的是编译的工作,生成32位环境下的汇编文件(hello.s)。汇编文件是比C语言源代码低级的,更加贴近机器语言程序的由汇编语言编写的文件。

接下来我们可以对汇编文件进行汇编处理,生成一个目标文件(hello.o),我们可以通过以下的命令进行处理:

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

下面我们就可以进行链接处理,从而生成一个可执行文件:

gcc -o hello hello.o -m32

这条命令默认使用动态链接库生成可执行文件,同样的,我们可以使用如下的命令生成静态链接的可执行文件:

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

这里对gcc命令中的参数进行说明: 

-x language  

明确指出后面输入文件的语言为language (而不是从文件名后缀得到的默认选择).这个选项应用于后面所有的输入文件,直到遇着下一个`-x'选项. language的可选值有`c', `objective-c', `c-header', `c++', `cpp-output', `assembler',和`assembler-with-cpp'。

-m32

明确指出生成的文件是针对32位计算机的。

更多的参数参考本链接

由此,我们知道一个C语言源代码文件要编译成可执行文件要经历四个步骤:预处理、编译、汇编和链接。

 

我们生成的目标文件以及最后链接生成的可执行文件都是ELF格式的文件。ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。它自最早在 System V 系统上出现后,被 xNIX 世界所广泛接受,作为缺省的二进制文件格式来使用。可以说,ELF是构成众多xNIX系统的基础之一。

ELF格式需要使用在两种场合:

a) 组成不同的可重定位文件,以参与可执行文件或者可被共享的对象文件的链接构建;

b) 组成可执行文件或者可被共享的对象文件,以在运行时内存中进程映像的构建。

我们可以在Linux系统下使用file命令查看一个目标文件的详细信息:

[root@host Code]$ file hello.o

hello.o:     ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped 

我们从输出的信息可以看出这个目标文件是ELF格式的。那对于 file 命令来说,它又能如何知道这些信息?答案是在ELF对象文件的最前面有一个ELF文件头,里面记载了所适用的处理器、对象文件类型等各种信息。至于ELF文件里面的内容,除了file命令所显示出来的那些之外,更重要的是包含另外一些数据,用于描述ELF文件中ELF文件头之外的内容。如果你的系统中安装有 GNU binutils 包,那我们可以使用其中的 readelf 工具来读出整个ELF文件头的内容,比如:

[root@host Code]$ readelf -h ./hello.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          184 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         9
  Section header string table index: 6
关于ELF文件更多的内容请参见本链接

可执行文件的格式和进程的地址空间存在映射关系,所以可执行文件可以对应着进程的虚拟地址进行加载。
下面我们使用exec*库函数加载一个可执行程序,具体的程序代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(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   */
        execlp("/bin/ls","ls",NULL);
    } 
    else 
    {  
        /*     parent process  */
        /* parent will wait for the child to complete*/
        wait(NULL);
        printf("Child Complete!");
        exit(0);
    }
}

代码中我们使用系统调用fork()创建一个新的进程,并且使用ELF可执行文件替换原进程的代码段。

动态链接分为可执行程序装载时动态链接和运行时动态链接,我们首先编写程序:

//    dllibexample.h 文件
#ifndef _DL_LIB_EXAMPLE_H_
#define _DL_LIB_EXAMPLE_H_ #ifdef __cplusplus extern "C" { #endif /* * Dynamical Loading Lib API Example * input : none * output : none * return : SUCCESS(0)/FAILURE(-1) * */ int DynamicalLoadingLibApi(); #ifdef __cplusplus } #endif #endif /* _DL_LIB_EXAMPLE_H_ */
//    dllibexample.c 文件
#include <stdio.h> #include "dllibexample.h" #define SUCCESS 0 #define FAILURE (-1) /* * Dynamical Loading Lib API Example * input : none * output : none * return : SUCCESS(0)/FAILURE(-1) * */ int DynamicalLoadingLibApi() { printf("This is a Dynamical Loading libary!\n"); return SUCCESS; }
//    shlibexample.h文件
#ifndef _SH_LIB_EXAMPLE_H_
#define _SH_LIB_EXAMPLE_H_ #define SUCCESS 0 #define FAILURE (-1) #ifdef __cplusplus extern "C" { #endif /* * Shared Lib API Example * input : none * output : none * return : SUCCESS(0)/FAILURE(-1) * */ int SharedLibApi(); #ifdef __cplusplus } #endif #endif /* _SH_LIB_EXAMPLE_H_ */
//    shlibexample.c文件
#include <stdio.h> #include "shlibexample.h" /* * Shared Lib API Example * input : none * output : none * return : SUCCESS(0)/FAILURE(-1) * */ int SharedLibApi() { printf("This is a shared libary!\n"); return SUCCESS; }

接下来针对这两个文件编译成为动态库文件:

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

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

下面我们在main.c文件中分别以共享库和动态加载共享库的方式使用libshlibexample.so文件和libdllibexample.so文件,main.c的代码如下:

#include <stdio.h>

#include "shlibexample.h" 

#include <dlfcn.h>

/*
 * Main program
 * input    : none
 * output    : none
 * return    : SUCCESS(0)/FAILURE(-1)
 *
 */
int main()
{
    printf("This is a Main program!\n");
    /* Use Shared Lib */
    printf("Calling SharedLibApi() function of libshlibexample.so!\n");
    SharedLibApi();
    /* Use Dynamical Loading Lib */
    void * handle = dlopen("libdllibexample.so",RTLD_NOW);
    if(handle == NULL)
    {
        printf("Open Lib libdllibexample.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;
}

我们对main.c文件进行编译链接,最后运行,结果如下:

$ gcc main.c -o main -L/path/to/your/dir -lshlibexample -ldl -m32

$ exportLD_LIBRARY_PATH=$PWD 

$ ./main

This is a Main program!

Calling SharedLibApi() functionof libshlibexample.so!

This is a shared libary!

Calling DynamicalLoadingLibApi() functionof libdllibexample.so!

This is a Dynamical Loading libary!

 

下面我们使用gdb跟踪分析一个execve系统调用内核处理函数sys_execve,我们在实验楼的环境中实验,由于网速的问题,这里就不贴实验结果了,具体实验参照课程标准。

 

以上就是实验内容的部分,时间紧迫,以上的内容写于2015-04-20晚上22:32,为了不耽误作业的提交,我先提交部分内容,余下的实验我于近日补齐上交。

 

Allen原创作品转载请注明出处《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

posted on 2015-04-20 22:35  lingzshen  阅读(232)  评论(0编辑  收藏  举报