linux内核分析 第七周 Linux内核如何装载和启动一个可执行程序

一、编译链接的过程和ELF可执行文件格式

vi hello.c
gcc -E -o hello.cpp hello.c -m32 //预处理.c文件,预处理包括把include的文件包含进来以及宏替换等工作

vi hello.cpp
gcc -x cpp-output -S -o hello.s hello.cpp -m32 //编译

vi hello.s
gcc -x assembler -c hello.s -o hello.o -m32 //汇编

vi hello.o
gcc -o hello hello.o -m32 //链接

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

ELF文件中有三种文件

可重定位文件:保存着代码和适当的数据,用来和其他的object文件一起来创建一个可执行文件或者是一个共享文件。

可执行文件:保存着一个用来执行的程序;该文件指出了exec(BA_OS)如何来创建程序进程映象。

共享文件:保存着代码和合适的数据,用来被下面的两个链接器链接。

  第一个是连接编辑器[请参看ld(SD_CMD)],可以和其他的可重定位和共享object文件来创建其他的object。

  第二个是动态链接器,联合一个可执行文件和其他的共享object文件来创建一个进程映象。

object文件参与程序的链接(创建)和执行。

ELF文件头

用readelf查看

在文件开始保存了:路线图:描述该文件组织情况,程序头表:告诉系统如何创建一个进程的内存映像,section头表:描述文件的section信息。(每个section在这个表中有一个入口,给出该section信息)

静态链接的ELF可执行文件和进程的地址空间

程序从0x804800开始,正式开始是在头部结束之后。

可执行文件加载到内存中开始执行的第一行代码。

一般静态链接将会把所有代码放在同一个代码段。

动态连接的进程会有多个代码段。

二、可执行程序、共享库和动态链接

可执行程序的执行环境

一般执行一个程序的Shell环境,实验中直接使用execve系统调用

Shell本身不限制命令行参数的个数,命令行参数的个数受限于命令自身,如:

int main(int argc, char *argv[])

int main(int argc, char argv[], char envp[])//envp是shell的执行环境

Shell会调用execve将命令行参数和环境参数传递给可执行程序的main函数

int execve(const char * filename,char * const argv[ ],char * const envp[ ]);

 

命令行参数和环境串都放在用户态堆栈中

可执行程序动态链接

共享库的动态链接

准备.so文件(在Linux下动态链接文件格式,在Windows中是.dll)

#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_ */
/*------------------------------------------------------*/

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

int SharedLibApi()
{
    printf("This is a shared libary!\n");
    return SUCCESS;
}/* _SH_LIB_EXAMPLE_C_ */
  • 编译成.so文件

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

 

3. 动态加载库

#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_ */
/*------------------------------------------------------*/

#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;
}

4. main.c

#include <stdio.h>
#include "shlibexample.h" //只include了共享库
#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();//可以直接调用,因为include了这个库的接口
    /* 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);//与dlopen函数配合,用于卸载链接库       
    return SUCCESS;
}

dlsym函数与上面的dlopen函数配合使用,通过dlopen函数返回的动态库句柄(由dlopen打开动态链接库后返回的指针handle)以及对应的符号返回符号对应的指针。

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

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

创建新进程

新进程调用execve()系统调用执行指定的ELF文件

调用内核的入口函数sys_execve(),sys_execve()服务例程修改当前进程的执行上下文;当ELF被load_elf_binary()装载完成后,函数返回至do_execve()在返回至sys_execve()。ELF可执行文件的入口点取决于程序的链接方式:

静态链接:elf_entry就是指向可执行文件里边规定的那个头部,即main函数处。

动态链接:可执行文件是需要依赖其它动态链接库,elf_entry就是指向动态链接器的起点。
多进程、多用户、虚拟存储的操作系统出现以后,可执行文件的装载过程变得非常复杂。引入了进程的虚拟地址空间;然后根据操作系统如何为程序的代码、数据、堆、栈在进程地址空间中分配,它们是如何分布的;最后以页映射的方式将程序映射进程虚拟地址空间。

五、分析sys_execve

sys_execve被调用后,涉及的主要函数为:do_execve -> do_execve_common -> exec_binprm

syscall

SYSCALL_DEFINE3(execve,
               const char __user *, filename,
               const char __user *const __user *, argv,
               const char __user *const __user *, envp)
       {   //真正执行程序的功能exec.c文件中的do_execve函数中实现
           return do_execve(getname(filename), argv, envp);
 
       }
 

do_execve

int do_execve(struct filename *filename,
          const char __user *const __user *__argv,
          const char __user *const __user *__envp)
      {
          struct user_arg_ptr argv = { .ptr.native = __argv };
          struct user_arg_ptr envp = { .ptr.native = __envp };
          //调用do_execve_common
          return do_execve_common(filename, argv, envp);
      }
 

do_execve_common

static int do_execve_common(struct filename *filename,
                        struct user_arg_ptr argv,
                        struct user_arg_ptr envp)
        {
            struct linux_binprm *bprm;
            struct file *file;
            struct files_struct *displaced;
            int retval;
 
            ..
 
            //打开要执行的文件,并检查其有效性
            file = do_open_exec(filename);
            retval = PTR_ERR(file);
            if (IS_ERR(file))
                goto out_unmark;
 
            sched_exec();
            // 填充linux_binprm结构
            bprm->file = file;
            bprm->filename = bprm->interp = filename->name;
 
            ...
 
            //将文件名、环境变量和命令行参数拷贝到新分配的页面中
            retval = copy_strings_kernel(1, &bprm->filename, bprm);
            if (retval < 0)
                goto out;
            bprm->exec = bprm->p;
            retval = copy_strings(bprm->envc, envp, bprm);
            if (retval < 0)
                goto out;
            retval = copy_strings(bprm->argc, argv, bprm);
            if (retval < 0)
                goto out;
            //调用exec_binprm,保存当前的pid并且调用 search_binary_handler
            retval = exec_binprm(bprm);
            if (retval < 0)
                goto out;
            /* execve succeeded */
            current->fs->in_exec = 0;
            current->in_execve = 0;
            acct_update_integrals(current);
            task_numa_free(current);
            free_bprm(bprm);
            putname(filename);
            if (displaced)
                put_files_struct(displaced);
            return retval;
 
        }
 

 

posted @ 2016-04-10 14:58  20125221银雪纯  Views(176)  Comments(0Edit  收藏  举报