1. 动态链接和静态链接
1.1. 编译命令
1aarch64-linux-gnu-gcc -o dummy_arm64 dummy.c
2aarch64-linux-gnu-gcc -static -o dummy_arm64_static dummy.c
1.2. 查看文件格式信息
1$ file dummy_arm64*
2dummy_arm64: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=3fa0f1369e1b3181f059174b078dd893e813abb2, for GNU/Linux 3.7.0, with debug_info, not stripped
3dummy_arm64_static: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=5475ac20b174d94d96e666e170fb2d6ce69cdf76, for GNU/Linux 3.7.0, with debug_info, not stripped
1.3. 读取elf文件信息
可以看到动态链接的程序需要有解释器/lib64/ld-linux-x86-64.so.2
,与file
目录看到的是一样的。静态链接的程序没有解释器段。
1$ readelf -l dummy_arm64
2
3Elf file type is EXEC (Executable file)
4Entry point 0x4004c0
5There are 9 program headers, starting at offset 64
6
7Program Headers:
8 Type Offset VirtAddr PhysAddr
9 FileSiz MemSiz Flags Align
10 PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
11 0x00000000000001f8 0x00000000000001f8 R 0x8
12 INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
13 0x000000000000001b 0x000000000000001b R 0x1
14 [Requesting program interpreter: /lib/ld-linux-aarch64.so.1]
15 LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
16 0x00000000000006c8 0x00000000000006c8 R E 0x10000
17
18$ readelf -l dummy_arm64_static
19
20Elf file type is EXEC (Executable file)
21Entry point 0x400540
22There are 6 program headers, starting at offset 64
23
24Program Headers:
25 Type Offset VirtAddr PhysAddr
26 FileSiz MemSiz Flags Align
27 LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
28 0x000000000007aa71 0x000000000007aa71 R E 0x10000
29 LOAD 0x000000000007b3b8 0x000000000048b3b8 0x000000000048b3b8
30 0x0000000000002560 0x0000000000007ba8 RW 0x10000
1.4. 符号信息
aarch64-linux-gnu-nm -u dummy_arm64
可以看到动态链接的程序由未定义的符号(用U标示),而静态链接的程序没有未定义的符号。
1$ aarch64-linux-gnu-nm -u dummy_arm64
2 w __gmon_start__
3 U __libc_start_main@GLIBC_2.34
4 U abort@GLIBC_2.17
用aarch64-linux-gnu-nm -n dummy_arm64
看到的符号要比静态链接的程序少很多。
1$ aarch64-linux-gnu-nm -n dummy_arm64 | wc
2 32 93 1069
3
4$ aarch64-linux-gnu-nm -n dummy_arm64_static | wc
5 1664 4992 59232
2. exec的内核实现
exec
系统调用、1号进程的演化和内核调用用户态程序的umh
最终都会调用到exec_binprm
。
exec
是一系列系统调用,对应的内核代码在fs/exec.c
,其核心函数是exec_binprm
。
2.1. exec_binprm
exec
过程中,主要涉及两个结构体:struct linux_binprm
和struct linux_binfmt
。
struct linux_binprm
包含了exec
过程中需要用到的配置信息,其中也包括一些从用户态传递的信息。
struct linux_binfmt
是exec
过程中用来表示不同可执行文件格式的结构体,它包含了如何解析和加载不同可执行文件格式的函数指针。
注意在alloc_bprm
时,就已经通过bprm_mm_init
为进程申请并初始化了struct mm_struct
。
2.2. search_binary_handler
search_binary_handler
会通过prepare_binprm
读取可执行文件的头部的BINPRM_BUF_SIZE
(256)字节记录到struct linux_bprm
的buf
。
search_binary_handler
通过遍历formats
链表中的struct linux_binfmt
并尝试执行load_binary
,load_binary
会对buf
进行检查,如果不匹配,则直接返回失败。search_binary_handler
继续遍历,直到load_binary
执行成功。
1/// fs/exec.c
2/* binfmt handlers will call back into begin_new_exec() on success. */
3static int exec_binprm(struct linux_binprm *bprm)
4{
5 pid_t old_pid, old_vpid;
6 int ret, depth;
7
8 /* Need to fetch pid before load_binary changes it */
9 old_pid = current->pid;
10 rcu_read_lock();
11 old_vpid = task_pid_nr_ns(current, task_active_pid_ns(current->parent));
12 rcu_read_unlock();
13
14 /* This allows 4 levels of binfmt rewrites before failing hard. */
15 for (depth = 0;; depth++) {
16 struct file *exec;
17 if (depth > 5)
18 return -ELOOP;
19
20 ret = search_binary_handler(bprm);
21 if (ret < 0)
22 return ret;
23 if (!bprm->interpreter)
24 break;
25
26 exec = bprm->file;
27 bprm->file = bprm->interpreter;
28 bprm->interpreter = NULL;
29
30 allow_write_access(exec);
31 if (unlikely(bprm->have_execfd)) {
32 if (bprm->executable) {
33 fput(exec);
34 return -ENOEXEC;
35 }
36 bprm->executable = exec;
37 } else
38 fput(exec);
39 }
40
41 audit_bprm(bprm);
42 trace_sched_process_exec(current, old_pid, bprm);
43 ptrace_event(PTRACE_EVENT_EXEC, old_vpid);
44 proc_exec_connector(current);
45 return 0;
46}
对于elf文件来说,exec_binprm
会调用load_elf_binary
来加载可执行文件。elf文件格式解析代码在fs/binfmt_elf.c
。
1/// fs/binfmt_elf.c
2static struct linux_binfmt elf_format = {
3 .module = THIS_MODULE,
4 .load_binary = load_elf_binary,
5 .load_shlib = load_elf_library,
6#ifdef CONFIG_COREDUMP
7 .core_dump = elf_core_dump,
8 .min_coredump = ELF_EXEC_PAGESIZE,
9#endif
10};
2.3. load_elf_binary
load_elf_binary
比较长,其主要完成的工作如下:
- 对文件做一些检查,如magic、arch等。
- 解析elf文件头信息,查看是否有解释器段,如果有,打开解释器并记录到
struct file *interpreter
,并加载解释器的elf信息。 - 用户态可能是在多线程的一个子线程中调用了
exec
,需要清理其他线程,同时也要清理进程的一些其他资源,如文件、信号处理程序等 - 设置进程名
- 映射可执行文件一些必要的段。
- 设置进程的页表,包括加载程序段,加载解释器段等。
- 设置进程的栈,包括设置栈顶,设置栈保护等。
- 清空bss段。
- 修改当前进程的
pt_regs
- 设置
pstate
为EL0。这样函数返回后,会切换至用户态。 - 配置进程的入口点,如果是动态链接程序,入口点是解释器elf文件的入口,如果是静态链接程序,入口点是可执行文件的入口。
- 设置
1+-- load_elf_binary
2| +-- load_elf_phdrs
3| +-- for (i = 0; i < elf_ex->e_phnum; i++, elf_ppnt++)
4| | +-- elf_read
5| | +-- interpreter = open_exec(elf_interpreter)
6| +-- load_elf_phdrs /// if (interpreter)
7| +-- parse_elf_properties(interpreter ?: bprm->file, ...)
8| +-- begin_new_exec
9| +-- SET_PERSONALITY2
10| +-- setup_new_exec
11| +-- setup_arg_pages
12| +-- randomize_stack_top
13| +-- set_brk
14| +-- elf_map
15| +-- load_elf_interp
16| +-- set_binfmt
17| +-- create_elf_tables
18| +-- arch_randomize_brk
19| +-- vm_mmap /// if (current->personality & MMAP_PAGE_ZERO)
20| +-- current_pt_regs
21| +-- finalize_exec
22| +-- START_THREAD(elf_ex, regs, elf_entry, bprm->p)