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

来源:互联网 发布:stc51单片机开发板 编辑:程序博客网 时间:2024/05/21 17:55

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

1. 实验准备

下载课件中准备的代码。

int Execstaic(int argc, char *argv[]){    int pid;    /* fork another process */      asm volatile(      "mov $0x78, %%eax\n\t"      "int $0x80\n\t"      "mov %%eax, %0\n\t"      :"=m"(pid)      :    );    if (pid < 0)     {         /* error occurred */        fprintf(stderr,"Fork Failed!");        exit(-1);    }     else if (pid == 0)     {        /* child process */        printf("This is Child Process!\n");        execlp("/hellostaic","hello",NULL);    }     else     {          /* parent process  */        printf("This is Parent Process!\n");        /* parent will wait for the child to complete*/        wait(NULL);        printf("Child Complete!\n");    }    return 0;} int main(){    PrintMenuOS();    SetPrompt("MenuOS>>");    MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);    MenuConfig("quit","Quit from MenuOS",Quit);    MenuConfig("time","Show System Time",Time);    MenuConfig("exec","exec progress",Exec);    MenuConfig("execStaic","exec static progress",Execstaic);    ExecuteMenu();}

其中hellpStatic的代码如下:

#include <stdio.h>int main(){  printf("hello world.\n");  return 0;}

然后将它编译成静态文件:
[root@localhost menu]# gcc -o hellostatic hello.c -static
编译完成后,将静态文件hellostatic与init拷贝到rootfs中,并在rootfs中执行命令:
find . | cpio -o -Hnewc |gzip -9 > ../rootfs.img

按照课件描述,启动linux跟踪。
这里写图片描述
在弹出窗口中输入相应的命令:
这里写图片描述

2. 代码分析

当输入命令后,代码进入跟踪:
这里写图片描述
而do_execve()最终是调用到函数do_execve_common,在此函数中,有两条比较关键的代码:

static int do_execve_common(...){        //1. 打开对应的二进制文件    file = do_open_exec(filename);    //2. 创建一个结构体,在这个结构体中保存了需要加载的二进制文件的信息    bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);    //3. 加载二进制文件,并设置eip等参数    retval = exec_binprm(bprm);}

其中exec_binprm函数为其中比较关键代码,而在这一函数中,比较关键的代码为:

static int exec_binprm(struct linux_binprm *bprm){        ... ...         //查找二进制文件的加载方式    ret = search_binary_handler(bprm);    ... ... }

再次查看search_binary_handler:

int search_binary_handler(struct linux_binprm *bprm){struct linux_binfmt *fmt;       ... ...  retry:    read_lock(&binfmt_lock);    //循环遍历查找可以解析需要加载的文件的代码。fmt    list_for_each_entry(fmt, &formats, lh) {        if (!try_module_get(fmt->module))            continue;        read_unlock(&binfmt_lock);        bprm->recursion_depth++;        //加载文件,而此函数最终通过函数指针调用到load_elf_binary        retval = fmt->load_binary(bprm);            ... ....}

这里需要注意一个问题。 linux对于linux_binfmt,有多种不同的定义,因而最终实际调用load_binary也不同,系统会根据加载文件读取128个字节的文件头部后,决定了linux_binfmt的实际定义。a.out可执行文件的装载过程叫做load_aout_binary;而装载可执行脚本程序的处理过程叫做load_script()。在本例中,elf格式的文件的定义:

static struct linux_binfmt elf_format = {    .module     = THIS_MODULE,    .load_binary    = load_elf_binary,    .load_shlib = load_elf_library,    .core_dump  = elf_core_dump,    .min_coredump   = ELF_EXEC_PAGESIZE,};

因此程序也就是调用load_elf_binary将程序加载起来。其中load_elf_binary()的主要步骤是:
1. 检查ELF可执行文件格式的有效性,比如魔数、程序头表中段(segment)的数量
2. 寻找动态链接”.interp”段,设置动态链接器路径(与动态链接有关)
3. 根据ELF可执行文件的程序头表的描述,对ELF文件进行映射,比如代码、数据、只读数据。
4. 初始化ELF进程环境,比如进程启动时EDX寄存器的地址应该是DT_FINI的地址
5. 将系统调用返回地址修改成ELF可执行文件的入口点,这个入口点取决于程序的链接方式,对于静态链接的ELF可执行文件,这个程序入口就是ELF文件的文件头中e_entry所指的地址;对于动态链接的ELF可执行文件,程序入口点是动态链接器。
注:摘自《程序员的自我修养–链接、装载与库》
其中第5步是能过函数start_thread()实现的

voidstart_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp){    set_user_gs(regs, 0);    regs->fs        = 0;    regs->ds        = __USER_DS;    regs->es        = __USER_DS;    regs->ss        = __USER_DS;    regs->cs        = __USER_CS;    regs->ip        = new_ip; //修改相应的值    regs->sp        = new_sp;    regs->flags     = X86_EFLAGS_IF;    /*     * force it to the iret return path by making it look as if there was     * some work pending.     */    set_thread_flag(TIF_NOTIFY_RESUME);}

3. 总结

程序加载通过do_execve()完成,而do_execve()执行过程:
1) 首查找被执行的文件,如果找到文件,则读取文件的前128个字节。 用于判断文件的格式。 开头的4个字节,称为魔数。通过对魔数的判断可以确定文件的格式和类型。
2) 读取128个字节的文件头部后,调用search_binary_handle去搜索和匹配合适的可执行文件装载处理过程。通过文件头部的魔数确定文件的格式,并调用相应的装载处理过程。比如ELF可执行文件的装载处理过程叫做load_elf_binary();a.out可执行文件的装载过程叫做load_aout_binary;而装载可执行脚本程序的处理过程叫做load_script()
3)当load_elf_binary()执行完毕,返回至do_execve()再返回至sys_execve()时, 上面的第5步已经把系统调用返回地址改成了被装载的ELF程序的入口地址了。所以当sys_execve()系统调用从内核态返回到用户态时,EIP寄存器直接跳转到ELF程序的入口地址,于是新的程序开始执行,ELF可执行文件装载完成。

0 0
原创粉丝点击