Linux 或 Android 系统中的二进制可执行程序加载和执行过程
本文目录 7 个章节
Linux 或 Android 系统中的二进制可执行程序加载和执行过程
# Linux 或 Android 系统中的二进制可执行程序加载和执行过程
1. 程序启动(Load)
用户请求程序启动: 用户通过命令行(Linux)或点击应用图标(Android)启动程序。
调用execve()系统调用: 在 Linux 和 Android 中,
execve()是启动新程序的核心系统调用。该调用会加载指定的可执行文件,并将其映像加载到内存中。execve()会替换当前进程的映像,因此,它会加载一个新的程序并初始化它的执行环境。execve("/path/to/program", argv, envp);操作系统加载器(Linker/Loader): 操作系统加载器负责加载程序及其所需的库文件,解析 ELF 文件的头部、程序段等,并将其映射到进程的虚拟地址空间。
- ld-linux.so / ld.so:动态链接器,Linux 中负责动态链接的加载(即加载共享库)。它处理对
.so库的依赖解析,并执行符号链接。
- ld-linux.so / ld.so:动态链接器,Linux 中负责动态链接的加载(即加载共享库)。它处理对
加载可执行文件: 加载器会按照 ELF 格式解析程序头信息,确定程序入口点、加载程序的各个段(如
.text,.data)到内存。
2. 地址空间分配
虚拟内存管理: 每个进程有独立的虚拟地址空间,由操作系统通过虚拟内存管理机制映射到物理内存。这允许程序使用较大的地址空间并且隔离进程之间的内存。
程序段加载:
- 代码段(.text):存储程序执行的指令。
- 数据段(.data):存储已初始化的全局变量。
- 未初始化数据段(.bss):存储未初始化的全局变量。
堆和栈的分配:
- 堆:程序动态分配内存(通过
malloc()和free()等)。 - 栈:存储函数调用、局部变量等。栈的大小通常由操作系统设定,并随着函数调用/返回进行变化。
- 堆:程序动态分配内存(通过
3. 程序执行(Execute)
操作系统内核准备好执行环境: 在程序加载到内存后,操作系统内核将控制权交给用户空间,程序从入口点(通常是
main()函数)开始执行。CPU执行指令:
- CPU根据程序计数器(PC)指向的内存地址获取下一条指令。
- 指令被解码并执行,执行的操作包括数据处理、内存读写、跳转等。
上下文切换与调度:
- 操作系统根据调度策略(如时间片轮转、优先级调度等)决定哪个进程或线程运行。上下文切换是操作系统中通过保存和恢复寄存器状态来实现的。
4. 动态链接过程(动态链接的程序)
动态链接器(Dynamic Linker): 动态链接器负责在程序运行时加载共享库并解析符号。它的工作包括:
加载共享库:在程序启动时,加载器会根据动态段(
.dynamic)中的信息,加载所需的共享库(如libc.so)。符号解析与重定位:链接器会解析程序中的符号,寻找符号地址,并将程序中的符号重定位到实际的内存地址。
GOT与PLT: 动态链接的核心机制之一是 全局偏移表(GOT) 和 过程链接表(PLT)。当程序首次调用共享库的函数时:
- PLT:会先跳转到动态链接器,动态链接器会解析符号并修改 GOT 表中的函数地址,确保后续调用直接跳转到共享库中的正确函数。
5. 栈与堆的管理
栈管理: 栈用于保存局部变量和函数调用信息。每当一个函数被调用时,栈帧会被创建,保存当前函数的局部变量和返回地址。
- 栈空间的分配由操作系统负责管理,程序员不能直接控制栈的分配和释放。栈在函数调用返回时会自动清理。
堆管理: 堆用于动态内存分配。程序员使用
malloc()、calloc()等函数来从堆中请求内存,并通过free()来释放内存。- 堆的内存分配由操作系统管理,通常会通过内存池或者内存堆管理算法来优化性能(如
ptmalloc或glibc中的 malloc 实现)。
- 堆的内存分配由操作系统管理,通常会通过内存池或者内存堆管理算法来优化性能(如
6. 系统调用与中断处理
系统调用: 程序与操作系统之间的交互是通过系统调用来实现的。常见的系统调用有
read()、write()、open()等。- 系统调用使用软中断(如
int 0x80或syscall指令)进入内核模式,从而由操作系统内核处理请求。
- 系统调用使用软中断(如
中断处理: CPU支持硬件中断和软件中断。硬件中断通常来自外部设备(如定时器、I/O设备等),而软件中断则来自程序自身(例如异常、错误或系统调用)。
- 操作系统通过中断向 CPU 通知特殊事件,然后处理该事件(例如内存访问错误或定时器到期)。
7. 相关工具与底层支持
链接器(Linker):
- ld:Linux中的标准链接器,负责将目标文件(.o)链接成可执行文件。它处理符号解析、地址重定位、库链接等操作。
动态链接器(Dynamic Linker):
- ld-linux.so:在 Linux 中,
ld-linux.so负责程序启动时动态链接共享库。 - linker:在 Android 中,
linker是负责加载共享库并解析符号的工具。
- ld-linux.so:在 Linux 中,
调试工具:
- GDB(GNU Debugger):用于调试程序,能够在程序执行时暂停,查看栈信息、变量值等。
- LLDB:Android 使用 LLDB 作为调试工具,与 GDB 类似,专门针对 Android 平台进行优化。
内存管理:
- 操作系统内核提供虚拟内存管理,使用 页表 和 交换空间(Swap Space)来管理内存的分配与回收。
- 动态内存分配(如
malloc()、free())由操作系统提供支持,通常通过内存堆管理算法(如ptmalloc)实现。