可执行程序的装载

1.预处理、编译、链接和目标文件的格式

1.1可执行程序的来源

1.1.1一个简单的流程

image

.c(省略了预处理)汇编成汇编汇编代码asm,然后汇编成目标码.o,在链接成可执行文件,可执行文件加载到内存执行。

1.1.2用hello.c做简单的实验

#include <stdio.h>
int main()
{
    printf("hello");
    return 0;
}
#预处理,hello.cpp为预处理的中间文件
#预处理负责把include的文件包含进来以及宏替换等工作
gcc -E -o hello.cpp hello.c -m32    
#预处理后的文件编译成汇编代码
gcc -x cpp-output -S -o hello.s hello.cpp -m32
#将汇编代码hello.s编译成目标代码,得到二进制的hello.o文件
gcc -x assembler -c hello.s -o hello.o -m32
#hello.o链接成可执行文件
gcc -o hello hello.o -m32
#hello可执行文件使用共享库
#静态编译出的hello.static是把所有需要执行的依赖的东西放在程序内部,因此会比hello大 
gcc -o hello.static hello.o -m32 -static

1.2目标文件的格式elf(executable and linkable format)

elf格式文件中三种主要的目标文件

  • 可重定位文件,保存着代码和适当的数据,用来和其他的object文件一起创建一个可执行文件或一个共享文件(主要是.o文件)
  • 可执行文件,保存一个用来执行的程序,该文件指出了exec(系统调用)如何创建程序进程映像
  • 共享object文件,保存着代码和合适的数据,用来被链接器(链接编译器、动态链接器)链接(主要是.so文件)

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

,是一种封装机制,简单说是把所有的源代码编译成目标代码后打成的包。库的开发者除了提供库的目标代码外,还提供一系列的头文件,头文件中就包含了库的接口,库分为静态库(static library)和共享库(share library)。在Linux中静态库以一种存档(archive)的特殊文件格式存放在磁盘中,由后缀.a标识;共享库通常用.so后缀来表示。win下分别是.lib和.dll。

通常情况下,对函数库的链接是放在编译时期(compile time)完成的。所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行文件(executable file)。程序在运行时,与函数库再无瓜葛,因为所有需要的函数已拷贝到自己门下。所以这些函数库被成为静态库(static libaray),通常文件名为“libxxx.a”的形式。其实,我们也可以把对一些库函数的链接载入推迟到程序运行时期(runtime)。这就是动态链接库(dynamic link library)技术,动态链接库的名字形式为 “libxxx.so” 后缀名为 “.so”

3. 可执行程序的装载——可执行程序的装载相关关键问题分析

可执行程序的装载也是系统调用,其中execve系统调用内核处理过程比较特殊,正常的系统调用陷入到内核态在返回到用户态,继续执行系统调用下一条指令。

3.1 execve特殊之处

当前可执行程序执行到execve这个系统调用时,陷入到内核态,在内核态里用execve加载的可执行文件把当前进程的可执行程序覆盖掉,当execve这个系统调用返回时,已经不是原来那个可执行程序,而是新的可执行程序。

3.2 简单分析execve

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

  • int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
  • 库函数exec*都是execve的封装例程

当系统调用陷入到内核时,sys_call调用sys_execve,sys_execve内部会解析可执行文件格式

  • 调用顺序:do_execve -> do_execve_common –>  exec_binprm
  • 再,search_binary_handle符合寻找文件格式对应的解析模块
    list_for_each_entry(fmt, &formats, lh) {
         if (!try_module_get(fmt->module))
            continue;
         read_unlock(&binfmt_lock);
         bprm->recursion_depth++;
         retval = fmt->load_binary(bprm);
         read_lock(&binfmt_lock);
  • 对于ELF格式的可执行文件fmt->load_binary(bprm);执行的应该是load_elf_binary,其内部是和ELF文件格式解析的部分需要和ELF文件格式标准结合起来阅读。

3.3 linux内核如何支持多种不同可执行文件格式

//查看load_elf_binary对应的模块
static struct linux_binfmt elf_format = {
  .module     = THIS_MODULE,
  //elf_format变量声明时,把load_elf_binary赋值给.load_binary这个函数指针
  .load_binary    = load_elf_binary,    
  .load_shlib = load_elf_library,
  .core_dump  = elf_core_dump,
  .min_coredump   = ELF_EXEC_PAGESIZE,
};


static int __init init_elf_binfmt(void)
{
    //将elf_format注册进了内核链表中
    register_binfmt(&elf_format);
    return 0;
}

elf_format和init_elf_binfmt就是观察者模式的观察者。当出现elf文件格式时,观察者就会自动执行load_elf_binary。

上述只是解析elf的部分代码,此外还要完成下面关键步骤

在load_elf_binary中有个start_thread

//修改了pt_regs,pt_regs是内核堆栈的栈底,当发生中断时,将ip,sp压栈。
//当执行一个新进程时,需要将起点替换
void start_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);
}
原文地址:https://www.cnblogs.com/boyiliushui/p/5488285.html