1.预处理、编译、链接和目标文件的格式
1.1可执行程序的来源
1.1.1一个简单的流程
.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); }