zoukankan      html  css  js  c++  java
  • C程序运行的背后(2)

    话说上回说到,C程序运行之前,必须要加载到其进程地址空间中。今儿咱就扯扯这个加载到底是怎么加载的。 一图胜前言,这个图简单说明了可执行文件加载过程的逻辑流,在此只做粗粒度概要说明。需要准确描述的,请出门左转,看源码去吧。

    1.  程序总是运行在进程上下文(context)中的,当输入./memlayout时,shell会创建一个子进程。除每个进程独有的专属信息外,子进程会继承父进程的大部分资源,如环境变量、进程空间映像等。也就是说,如果不重置子进程的内容,子进程会运行与父进程一样的程序。为了让子进程可以运行别的程序,就要通过execve这个系统调用来指定。

    int   execve( char *pathname, char *argv[], char *envp[] )
    int   do_execve( char *pathname, char *argv[], char *envp[] ,struct pt_regs *regs )
    
    // pathname -> 可执行文件名指针
    // argv   -> 参数指针
    // envp   -> 环境变量指针
    // pt_regs  -> 用来保存切换到内核前用户空间的寄存器值

    就像春风秋雨一样,原本一切都是那么自然,自然到你忍不住向女神表白,然后女神跟你说“你是个好人”。当execvedo_execve说我要你时,却凭空多出了一个变量struct pt_regs,这绝不不可以说不任性!这是因为,do_execve中的参数命令行参数指针、环境变量指针,会被内核用来设置子进程的用户栈。而根据系统调用约定(calling conventions),Linux和Unix在系统调用时有一个不同,那就是Linux是用寄存器来传递系统调用参数的,而Unix是通过栈。所以在切换到内核时,就有必要保存传递到寄存器中的参数。而pt_regs这个结构体就是用来保存CPU的寄存器状态的。原来还是那么自然,只是女神已成路人。

    // include/asm-i386/ptrace.h
     struct pt_regs {     
        long ebx;    // pathname
        long ecx;     // argv
        long edx;     // envp
        long esi;     
        long edi; 
        long eax;  
        long eip;
        ……
    }

    2.  那么do_execve要大发神威了吧?切莫急先。物理学告诉我们,力都是有一个作用对象的,就好比我们追的都是女神。而do_execve的操作对象是一个叫struct linux_binprm的结构体,它用来保存要执行的文件的相关信息。do_execve会调用load_binprm,将需要的可执行文件信息都加载到linux_binprm中,包括可执行文件的ELF头信息、路径名、参数字符串、环境变量字符串等等。(注意:load_binprm并不是一个真正意义上的函数,为了方便理解,用它来概括表示由do_execve完成的填充任务。)

    struct linux_binprm {     
      char buf[BINPRM_BUF_SIZE]; //保存可执行文件的头128字节
      struct page *page[MAX_ARG_PAGES]; // 保存参数、环境变量   
      struct mm_struct *mm;     
      unsigned long p;    //当前内存页最高地址
      int sh_bang;     
      struct file * file;     //要执行的文件
      int e_uid, e_gid;    //要执行的进程的有效用户ID和有效组ID     
      kernel_cap_t cap_inheritable, cap_permitted, cap_effective;     
      void *security;     
      int argc, envc;     //命令行参数和环境变量数目
      char * filename;    //要执行的文件的名称
      char * interp;      //要执行的文件的真实名称,通常和filename相同     
      unsigned interp_flags;     
      unsigned interp_data;     
      unsigned long loader, exec; 
    }

    接下来,do_execve调用search_binary_handler()来查找可执行文件内容的处理程序。对于ELF格式的文件,则调用相应的load_elf_binary(),如果是a.out格式,则调用load_aout_binary()。在根据可执行文件中的section信息并将它们加载到进程空间前,load_elf_binary会首先将进程空间清空,然后将可执行文件映像加载到进程空间中。另外,load_elf_binary还会设置好用户栈:

    调用setup_arg_pages,将linux_binprm.page中的参数、环境变量等字符串映射到用户栈中;

    调用create_elf_tables,将argc、argv、envp以及一些的“辅助向量(auxiliary vector)”压入到用户栈中。

    辅助向量,是内核向用户空间的应用程序传递信息的机制之一,主要供动态链接器(ld-linux.so)使用。

    之前的图是从《深入理解计算机系统》拷过来的,并不详细,于是重新画了一个:

     argc下面的地址才是栈真正开始的地方。

    3.  终于草原已经准备好了,可以策马奔腾啦。一切又回到load_elf_binary()中,不过接下来就是见证神奇的时候啦,因为load_elf_binary要调用start_thread啦。不要小瞧这个调用,它不亚于数学老师跟我们说”我要变形了“的效果。

    #define start_thread(regs, new_eip, new_esp) do {          
           __asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0));     
           set_fs(USER_DS);                             
           regs->xds = __USER_DS;                   
           regs->xes = __USER_DS;                   
           regs->xss = __USER_DS;                   
           regs->xcs = __USER_CS;                   
           regs->eip = new_eip;                     
           regs->esp = new_esp;                     
    } while (0)

    start_thread的实际调用是这样的start_thread(regs,elf_entry, bprm->p),其中__USER_*是前面提到的进程切换到内核前的寄存器值;elf_entry就是可执行文件的入口点,也就是C启动代码的入口位置,赋给eip;bprm->p就是用户栈的栈顶位置,赋给esp。接下来就会跳转到eip指向的C启动代码起始位置开始运行。

     

    至于从C启动代码到main的距离,咱以后再表。

     

    参考:

    1 do_execve的具体内容:http://wenku.baidu.com/view/e97820ee4afe04a1b071de32.html

    2 ELF文件加载详细流程:http://www.longene.org/techdoc/0328130001224576708.html

    3 辅助向量:http://www.tuicool.com/articles/MNRJVj

  • 相关阅读:
    CodeForces 510C Fox And Names (拓扑排序)
    Codeforces 1153D Serval and Rooted Tree (简单树形DP)
    HDU 6437 Problem L.Videos (最大费用)【费用流】
    Luogu P3381 (模板题) 最小费用最大流
    Codeforces 741B Arpa's weak amphitheater and Mehrdad's valuable Hoses (并查集+分组背包)
    Codeforces 1144F Graph Without Long Directed Paths (DFS染色+构造)
    HDU 2204 Eddy's 爱好 (容斥原理)
    Codeforces 939E Maximize! (三分 || 尺取)
    Codeforces 938D. Buy a Ticket (最短路+建图)
    CodeForces 959E Mahmoud and Ehab and the xor-MST (MST+找规律)
  • 原文地址:https://www.cnblogs.com/chenwu128/p/4194638.html
Copyright © 2011-2022 走看看