- 目标文件(ABI,应用程序二进制接口):编译器生成的文件。
- 目标文件的格式:out格式、COFF格式、PE(windows)格式、ELF(Linux)格式。
- ELF(Executable and linkable Format)即可执行和可链接的格式,是一个目标文件格式的标准。ELF格式的文件用于存储Linux程序。其包含了以下三类:
- 可重定位文件:保存着代码和适当的数据,用来和其它的目标文件一起来创建一个可执行文件、静态库文件或者是一个共享目标文件
- 可执行文件:保存着一个用来执行的程序,一般由多个可重定位文件结合生成,是完成了所有重定位工作和符号解析(除了运行时解析的共享库符号)的文件。
- 共享目标文件:保存着代码和合适的数据,用来被两个链接器链接。第一个是链接编辑器(静态链接),可以和其它的可重定位和共享目标文件来创建其它的object。第二个是动态链接器,联合一个可执行文件和其它的共享目标文件来创建一个进程映象。
- 程序从源代码到可执行文件的步骤:预处理、编译、汇编、链接
①编译器预处理:gcc -E -o XX.cpp XX.c (-m32)
②汇编器编译成汇编代码
gcc -x cpp-output -S -o hello.s hello.cpp (-m32)
③汇编代码编译成二进制目标文件
gcc -x assembler -c hello.s -o hello.o (-m32)
④链接成可执行文件
gcc -o hello.static hello.c (-m32) -static
预处理和编译完的文件均为文本文件,汇编和链接完的文件均为ELF文件。 - 可执行文件处理过程也是系统调用。
- 程序装载中fork与execve的区别和联系
- 他们都是比较特殊的系统调用
- fork在陷入内核态后有两次返回,第一次返回到原来父进程的位置继续向下执行,第二次是在子进程返回,这次会返回到ret_from_fork,之后正常返回用户态。
- execve在执行时陷入内核态,在内核中调用execve加载的可执行文件把当前进程的可执行程序给覆盖了,当其返回时,返回的已经不是原来的那个可执行程序了,而是新的程序,返回的是新的可执行程序执行的起点,即main函数的大致位置(一般地址为0x8048xxx,由编译器设定)。
- ELF可执行文件总是被映射到0x8048000这个地址。
- 链接,从过程上讲:符号解析、重定位。根据链接时机:静态链接、动态链接。编译器默认使用动态链接,动态链接分为两种:可执行程序装载时动态链接、运行时动态链接。动态链接库实际上是一个图的遍历,装载和链接之后将CPU的控制权交给可执行程序。
1.准备好代码,编译成libshlibexample.so,libdllibexample.so文件
$ gcc -shared shlibexample.c -o libshlibexample.so $ gcc -shared dllibexample.c -o libdllibexample.so
2. main函数中分别以共享库和动态加载共享库的方式使用libshlibexample.so文件和libdllibexample.so文件
3. 动态衔接运行测试,使用参数-L表明文件路径,-l表示库文件名。(这部分之前练习过,当复习一遍了)
gcc main.c -o main -L./ -l shlibexample -ldl -m32 export LD_LIBRARY_PATH=$PWD ./main二、使用gdb跟踪execve系统调用内核处理函数sys_execve
- 进入LinuxKernel目录,删除menu目录,克隆一个新的menu
- 进入menu目录,用make rootfs对覆盖后新文件进行编译、自动生成根文件系统。新增了exec系统调用,在MenuOS输入help查询系统调用函数,测试新加入的exec命令。
- 启动qemu,并加载内核和端口。
- 水平分割启动gdb,设置sys_execve和load_elf_binary和start_thread三个断点,按“c”继续执行到sys_exec时输入exec命令,结果运行停在sys_exec。
5. new_ip是返回到用户态的第一条指令,退出调试。
输入redelf -h hello可以查看hello的EIF头部,可以看到定义的入口地址与“new_ip”所指向的地址一致。
do_ execve调用do_ execve_ common,do_ execve_ common主要依靠exec_ binprm。在exec_ binprm中又有一个至关重要的函数,叫做search_binary_ handler。这就是sys_execve的内部处理过程。
int do_execve(struct filename *filename,
const char __user *const __user *__argv,
const char __user *const __user *__envp)
{
return do_execve_common(filename, argv, envp);
}
static int do_execve_common(struct filename *filename,
struct user_arg_ptr argv,
struct user_arg_ptr envp)
{
// 检查进程的数量限制
// 选择最小负载的CPU,以执行新程序
sched_exec();
// 填充 linux_binprm结构体
retval = prepare_binprm(bprm);
// 拷贝文件名、命令行参数、环境变量
retval = copy_strings_kernel(1, &bprm->filename, bprm);
retval = copy_strings(bprm->envc, envp, bprm);
retval = copy_strings(bprm->argc, argv, bprm);
// 调用里面的 search_binary_handler
retval = exec_binprm(bprm);
// exec执行成功
}
static int exec_binprm(struct linux_binprm *bprm)
{
// 扫描formats链表,根据不同的文本格式,选择不同的load函数
ret = search_binary_handler(bprm);
// ...
return ret;
}



