文章摘要
作者在开发RISC-V用户空间模拟器时,深入研究了从内核执行程序到main函数运行前的底层机制。重点分析了Linux系统中execve系统调用的工作原理,它通过可执行文件路径、参数列表和环境变量来加载程序。高级语言如Rust的Command实际上是对此系统调用的封装,标准库会处理命令路径解析等预处理工作。
文章总结
文章标题:main()函数之前的旅程
主要内容概述:
本文作者通过开发RISC-V用户空间模拟器的经历,深入探讨了从内核被请求运行程序到程序main()函数第一行代码执行之间发生的复杂过程。以下是关键环节的解析:
1. 程序启动的起点:execve系统调用
- 触发机制:在Linux系统中,程序通过
execve系统调用启动,参数包括可执行文件名、参数列表和环境变量。 - 高层封装:如Rust的
std::process::Command会处理路径解析(类似Shell通过PATH环境变量解析命令),但内核最终需要完整的可执行文件路径。 - 解释器处理:若文件以
#!开头(如Python脚本),内核会调用指定解释器执行。
2. 可执行文件格式:ELF
- 结构解析:Linux使用ELF格式,包含代码、数据、符号表等。通过
readelf工具可查看头部信息,关键字段包括:- 魔数:
45 4c 46(ASCII "ELF")标识文件类型。 - 入口地址:程序执行的起始指令位置。
- 段头表:描述代码段(
.text)、数据段(.data)、未初始化变量区(.bss)等。
- 魔数:
- 动态链接:PLT(过程链接表)支持动态加载共享库(如
libc),符号表(.symtab)则包含大量调试信息(即使简单如"Hello World"程序也可能包含上千符号)。
3. 内核的准备工作
- 内存加载:内核将ELF标记为“可加载”的段载入内存,应用安全策略(如地址随机化ASLR、非执行位NX)。
- 栈的初始化:
- 布局:栈通常从内存高端向低端增长,存放参数(
argv)、环境变量(envp)和ELF辅助向量(auxv,含页大小等系统信息)。 - 模拟示例:作者用RISC-V模拟器代码演示了内核如何压栈参数和环境变量指针。
- 布局:栈通常从内存高端向低端增长,存放参数(
4. 入口函数_start与运行时初始化
- 入口点:ELF头部指定的
_start函数是程序第一条指令,通常由C库(如musl/glibc)提供,也可自定义(如Rust中#[no_mangle]标记)。 - 语言运行时:
- Rust:
lang_start初始化运行时后调用用户main。 - C/C++:类似但更轻量,而Java/Python等语言的运行时初始化更复杂。
- Rust:
- 示例代码:Rust生成的
_start会从栈中获取argc/argv,再跳转到用户定义的main。
5. 未涉及的复杂细节
- 内核级操作(如地址空间分配、进程表管理)和硬件安全特性(如SMAP)被简略,但核心流程已覆盖。
结语
本文揭示了main()调用前的底层机制,从系统调用、文件格式到内存布局,展现了操作系统与编译器的协同工作。更多技术细节可参考作者提供的模拟器代码或直接联系讨论。
评论总结
以下是评论内容的总结:
关于符号表和库链接的讨论
- hagbard_c指出使用musl库会导致程序符号表比glibc更臃肿,并提供了具体数据对比:
- "The same program linked to glibc tops at 36 symbols in .symtab"
- vbezhenar提出直接使用Linux系统调用而非标准库的编程方式:
- "Much more fun to write software this way, IMO"
- hagbard_c指出使用musl库会导致程序符号表比glibc更臃肿,并提供了具体数据对比:
非传统编程方式的探索
- mmsc分享将整个代码库打包到"main()之前"或完全不用main()的实验:
- "pack a whole codebase into 'before main()' - or with no main() at all"
- itopaloglu83提到在旧微控制器上的类似实践:
- "You said see how to stack pointer, timers, and variables etc. all are configured"
- mmsc分享将整个代码库打包到"main()之前"或完全不用main()的实验:
关于shebang和解释器的实际问题
- khaledh分享了因shebang路径错误导致的调试问题:
- "the Java error was completely misleading"
- "the application was running on a remote host that had a different path"
- khaledh分享了因shebang路径错误导致的调试问题:
ELF动态链接机制的澄清
- fweimer和turbert详细解释了Linux下动态链接的实际工作流程:
- "The kernel then loads the dynamic linker...and transfers control to its entry point"(fweimer)
- "Any linked objects are loaded in userspace by the elf interpreter"(turbert)
- fweimer和turbert详细解释了Linux下动态链接的实际工作流程:
内存布局表示的教学问题
- bignerd_95指出传统内存图示方式与学生认知习惯的矛盾:
- "Most diagrams...draw higher addresses at the top of the page"
- "If we just drew memory like an editor...it would click instantly"
- bignerd_95指出传统内存图示方式与学生认知习惯的矛盾:
相关学习资源推荐
- archmaster分享了自己编写的相关学习资料:
- "I wrote https://cpu.land/ a couple years ago"
- archmaster分享了自己编写的相关学习资料:
注:所有评论评分均为None,因此未在总结中体现认可度差异。总结保持了不同观点的平衡,每个主要观点都引用了2-3条原始评论的关键内容。