程序启动过程简介
以Linux
为例,Linux在启动以后为每个终端用户建立一个进程执行shell解释程序,该程序接收病执行命令的过程如下:
- 读取命令行,分析命令,以命令名作为文件名,相关参数传入
execve
内部处理,执行execve
后,引发int 0x80
中断,进入内核态; - 终端进程调用
fork
建立一个子进程,并系统调用wait4
等待,子进程根据文件名找到相关文件,将其调入内存,执行这个程序; - 若命令末尾有
&
则终端不需要系统调用wait4
等待。
Linux支持不同的可执行文件格式,通过struct linux_binfmt
结构体管理不同的可执行文件,这个结构中有对应的可执行文件的处理函数。
总体过程如下:
- 进入内核态后,
do_execve
函数读入可执行文件,检查权限,读取可执行文件的相关信息; - 根据文件类型,在
struct linux_binfmt
中找到相应的load_binary()
函数(对于ELF格式的文件,处理函数是load_elf_binary
),加载可执行文件
以ELF
文件格式为例,其加载可执行文件的过程如下:
- 读入
ELf
文件的头部,根据头部信息读入各种数据,扫描程序段描述符(Program Header Table),找到类型为PT_LOAD
的段(即.text, .data, .bss
等节区) - 将段映射到内存的固定地址上,如果没有动态连接器的描述段,则返回入口地址为应用程序入口;
- 若应用程序使用了动态链接库,除加载可执行文件外,控制权交给动态链接器(ld-linux.so)。内核搜寻段表(Program Header Table),找到标记为
PT_INTERP
段中所对应的动态连接器的名称,并使用load_elf_interp
加载其映像,并把返回的入口地址设置成load_elf_interp
的返回值,即动态链接器的入口;
ELF文件
ELF
文件的大体结构:
----------------------
ELF Header | #程序头,有该文件的Magic number(参考man magic),类型等
----------------------
Program Header Table | #对可执行文件和共享库有效,它描述下面各个节(section)组成的段
----------------------
Section1 |
----------------------
Section2 |
----------------------
Section3 |
----------------------
..... |
----------------------
Program Section Table | #仅对可重定位目标文件和静态库有效,用于描述各个Section的重定位信息等。
----------------------
ELF 文件的主要节区(section)有 .data,.text,.bss,.interp
等,而主要段(segment)有 LOAD,INTERP
等。
section | 解释 | 实例 |
---|---|---|
.data | 初始化的数据 | 比如 int a=10 |
.bss | 未初始化的数据 | 比如 char sum[100];这个在程序执行之前,内核将初始化为0 |
.text | 程序代码正文 | 即可执行指令集 |
.interp | 描述程序需要的解析器(动态连接和装载程序) | 存在解释器的全路径,如/lib/ld-linux.so |
示例
ELF Header:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 712 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 13
Section header string table index: 12
Section Headers:
ELF 文件的主要节区(section)有 .data,.text,.bss,.interp
等,而主要段(segment)有 LOAD,INTERP 等。
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000017 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000218
0000000000000030 0000000000000018 I 10 1 8
[ 3] .data PROGBITS 0000000000000000 00000057
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000057
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 00000057
000000000000000d 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000064
000000000000002a 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000008e
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 00000090
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000248
0000000000000018 0000000000000018 I 10 8 8
[10] .symtab SYMTAB 0000000000000000 000000c8
0000000000000120 0000000000000018 11 9 8
[11] .strtab STRTAB 0000000000000000 000001e8
000000000000002e 0000000000000000 0 0 1
[12] .shstrtab STRTAB 0000000000000000 00000260
0000000000000061 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello world!\n");
return 0;
}
对应的汇编代码如下:
.file "helloworld.c"
.text
.section .rodata
.LC0:
.string "hello world!"
.text
.globl main
.type main, @function
main:
.LFB5:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
leaq .LC0(%rip), %rdi
call puts@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE5:
.size main, .-main
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
寄存器 | 名称 | 注释 |
---|---|---|
EIP/RAX | 程序指令指针 | 通常指向下一条指令的位置 |
ESP/RSP | 程序堆栈指针 | 通常指向当前堆栈的当前位置 |
EBP/RBP | 程序基指针 | 通常指向函数使用的堆栈顶端 |
void main()
{
__asm__ __volatile__("jmp forward;"
"backward:"
"popl %esi;"
"movl $4, %eax;"
"movl $2, %ebx;"
"movl %esi, %ecx;"
"movl $12, %edx;"
"int $0x80;"
"movl $1, %eax;"
"movl $0, %ebx;"
"int $0x80;"
"forward:"
"call backward;"
".string \"Hello World\\n\";");
}
- %rax 作为函数返回值使用。
- %rsp 栈指针寄存器,指向栈顶
- %rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数。。。
- %rbx,%rbp,%r12,%r13,%14,%15 用作数据存储,遵循被调用者使用规则,简单说就是随便用,调用子函数之前要备份它,以防他被修改
- %r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值
参考
Was this helpful?
0 / 0