发布时间:2026/7/18 16:34:46
从 `hello.c` 到 ELF:亲手走一遍 GCC 编译、汇编与链接全过程 平时编译 C 程序我们往往只写一条命令gcc hello.c-ohello几秒之后可执行文件就生成了。但这条命令背后到底发生了什么头文件去了哪里.o为什么不能直接运行printf的实现什么时候进入程序动态链接和静态链接又差在哪为了把这些问题串起来我用一个简单的hello.c做了完整实验分别保留预处理文件、汇编文件和目标文件再借助objdump、readelf、ldd观察符号、重定位和 ELF 布局。这套过程不只适用于本机 GCC。以后换成 ARM 交叉编译器编译链路仍然相同只是目标架构和使用的库发生了变化。一条命令背后的四个阶段从 C 源文件到可执行程序通常会经过四个阶段hello.c --预处理-- hello.i --编译-- hello.s --汇编-- hello.o --链接-- hello阶段GCC 参数输出主要工作预处理-Ehello.i展开头文件和宏处理条件编译编译-Shello.s语法、语义分析生成汇编代码汇编-chello.o生成机器码和可重定位 ELF链接无停止参数hello解析符号、重定位并生成可执行文件这里有个很容易写错的地方汇编后停止使用的是小写-c。大写-C是预处理选项通常和-E搭配用来保留注释。准备一段测试代码为了方便观察库函数符号测试程序保留了printf、atoi和exit等调用#includestdio.h#includestdlib.hintmain(intargc,char*argv[]){if(argc!2){printf(Usage: %s number\n,argv[0]);exit(EXIT_FAILURE);}printf(program: %s, input: %s\n,argv[0],argv[1]);intnumberatoi(argv[1]);switch(number){case0:printf(h\n);break;case1:printf(e\n);break;case2:printf(l\n);break;case3:printf(l\n);break;case4:printf(o\n);break;default:printf(hello\n);break;}returnEXIT_SUCCESS;}atoi()无法可靠判断转换失败这里只是为了观察编译和链接过程。正式项目更适合使用strtol()并检查非法字符、溢出和数值范围。把四个阶段分别执行gcc-Ehello.c-ohello.i gcc-S-g-O0hello.i-ohello.s gcc-chello.s-ohello.o gcc hello.o-ohello-g用来保留调试信息方便后面混合显示源代码和汇编-O0关闭优化让生成结果更接近源代码结构。第一站预处理文件为什么这么大gcc-Ehello.c-ohello.i预处理器主要完成下面几件事展开#include引入的头文件替换宏定义处理#if、#ifdef等条件编译默认删除注释插入行标记保留内容对应的源文件和行号例如#12hello.c它告诉后续编译阶段接下来的内容与hello.c第 12 行相关。实际行标记还可能包含其他标志用来表示进入或离开头文件等状态。比较文件大小和行数stathello.c hello.iwc-lhello.c hello.i实验中几十行的源文件经过预处理后增长到近两千行。真正让文件膨胀的不是业务代码而是stdio.h、stdlib.h以及它们递归包含的类型、声明和宏。这也是hello.i最实用的用途当宏展开或条件编译不符合预期时直接查看预处理结果比盯着源文件猜更有效。第二站C 代码如何变成汇编gcc-S-g-O0hello.i-ohello.s编译阶段会完成词法分析、语法分析、语义检查、中间表示生成以及目标代码生成。开启优化后编译器还会对中间表示进行优化。这一阶段输出的hello.s已经是面向目标 CPU 的汇编代码但还不是机器码。外部函数也不会简单地在这个阶段“复制进来”。对于printf、atoi这类普通外部调用编译器主要生成调用指令和符号引用真正的实现由链接阶段处理。想观察优化的影响可以生成两份汇编文件gcc-S-O0hello.c-ohello_O0.s gcc-S-O2hello.c-ohello_O2.sdiff-uhello_O0.s hello_O2.s有些系统上编译器甚至会把简单的printf(h\n)优化成puts(h)。因此符号表里出现puts并不一定意味着源代码直接调用了它。第三站hello.o为什么还不能运行gcc-chello.s-ohello.o汇编器会把汇编助记符编码成 CPU 机器指令同时生成符号表和重定位信息。这时的hello.o已经包含机器码但它属于“可重定位目标文件”还不能直接执行。原因主要有两个printf、atoi等外部符号的最终位置还没有确定文件还没有形成操作系统可直接装载的完整程序映像hello.o也不是一段没有结构的二进制数据而是一个 ELF 文件通常包含ELF 文件头.text代码节.rodata只读数据节.data和.bss数据节符号表重定位节使用-g时生成的调试信息这里需要区分两个概念section通常译为“节”主要服务于编译和链接segment通常译为“段”主要描述程序如何装载到内存目标文件主要关注节可执行文件被内核加载时程序头表中的段更关键。用objdump拆开目标文件先看反汇编objdump-dhello.o同时显示源代码和汇编objdump-Shello.oobjdump -S需要目标文件包含调试信息否则通常只能看到反汇编无法正确对应源代码。继续查看符号表和重定位objdump-thello.o objdump-rhello.o如果想看更完整的 ELF 元数据可以使用readelf-hhello.o readelf-Shello.o readelf-sWhello.o readelf-rWhello.o第四站链接器如何把零散内容拼成程序gcc hello.o-ohello链接阶段主要完成四件事符号解析把未定义符号和目标文件或库中的定义对应起来。合并输入文件中的同类节并确定整体布局。根据最终布局修正代码和数据里的地址引用。生成 ELF 头和程序头表让内核与动态加载器知道如何装载程序。使用gcc链接时输入不只有hello.o和 C 标准库。GCC 还会自动加入启动文件、运行时支持库和默认链接选项。想看它实际调用了哪些工具和文件可以执行gcc-vhello.o-ohello输出里常见的crt1.o、crti.o、crtn.o属于 C 运行时启动文件。它们负责在进入main前后完成必要的初始化和收尾工作。动态链接和静态链接体积差多少普通命令默认通常生成动态链接程序gcc hello.o-ohello ldd helloldd可以看到libc.so.6和动态加载器等依赖。括号中的十六进制数是本次检查时观察到的映射地址启用 ASLR 后可能在不同运行间变化。再生成一个静态链接版本gcc hello.o-static-ohello_staticls-lhhello hello_static ldd hello_static实验中的动态版本只有几 KB静态版本接近 1 MB体积差距非常明显。原因在于动态链接程序主要记录共享库依赖运行时再由加载器映射库静态链接会把需要的库代码复制进可执行文件不过静态链接不等于“复制到任何同架构 Linux 都能运行”。程序仍然受到 CPU 指令集、内核 ABI、系统调用、设备文件和运行配置等条件约束。符号表里那些0到底代表什么objdump-thello.o objdump-thello在hello.o中可以看到两类典型符号printf、exit、atoi等显示为*UND*说明它们是未定义外部符号值通常为 0main已经定义在.text中它的值表示相对于所在节起点的偏移因此不能简单地说“.o里的符号地址全是 0”。更准确的理解是可重定位文件中的已定义符号通常保存节内偏移未定义外部符号等待链接器解析链接完成后已定义符号才会获得最终链接布局中的虚拟地址使用下面的命令可以更清楚地查看符号类型、绑定方式和节索引readelf-sWhello.oobjdump -r没输出就没有重定位了吗objdump-rhello.o objdump-rhellohello.o中能看到针对外部函数和只读数据的重定位条目。链接完成后objdump -r hello可能没有输出这表示普通静态重定位已经被链接器处理不能据此断言可执行文件完全没有重定位。动态链接程序通常仍然包含 GOT、PLT 和共享库相关的动态重定位。应该继续检查readelf-rWhello objdump-Rhello现代 Linux 发行版还可能默认生成 PIE也就是位置无关可执行文件。它在装载时会根据实际基址完成相应的动态重定位filehello readelf-hhello|grepType不同 GCC 和发行版的默认设置可能不同因此实验结果不必和其他机器逐字一致重点是看清 ELF 类型和工具输出。readelf常用命令速查观察目标命令ELF 类型、架构和入口地址readelf -h hello节头表观察链接视图readelf -S hello程序头表观察装载视图readelf -l hello符号表readelf -sW hello重定位表readelf -rW hello动态段和共享库依赖readelf -d hello反汇编代码objdump -d hello.o动态重定位objdump -R hello动态库依赖ldd helloreadelf -h会输出整个 ELF 文件头其中包含入口地址而不是只显示入口地址。这次实验真正看懂了什么走完这套流程后gcc hello.c -o hello就不再是一条黑盒命令了hello.i告诉我们头文件和宏在预处理阶段被展开hello.s展示编译器为目标 CPU 生成的汇编代码hello.o已经包含机器码但仍保留未解析符号和重定位信息链接器负责解析符号、安排地址并生成可装载的 ELF动态链接和静态链接的差别不只是文件大小还涉及部署和运行环境objdump更适合观察指令readelf更适合查看 ELF 元数据这也是学习交叉编译前很值得做的一次实验。把gcc换成 ARM 交叉编译器后四个阶段没有变但生成的机器码、ELF 架构、动态加载器和链接库都会换成目标板对应的版本。以后再遇到undefined reference、Exec format error、动态库找不到或者程序明明存在却无法运行就可以先判断问题发生在编译、链接还是装载阶段而不是只盯着最后一条报错。

相关新闻

2026/7/18 16:34:46

基于MATLAB深度卷积特征的多曝光图像自适应融合系统设计与实现

摘要:在数字摄影和计算机视觉领域,高动态范围(HDR)场景的图像采集一直是一个重要挑战。传统相机受限于传感器的有限动态范围,无法在单次曝光中同时捕捉场景的亮部和暗部细节,导致亮部过曝或暗部欠曝现象。多…

2026/7/18 16:29:45

从嘉立创 EDA 导出的芯片原理图和封装,再导入 AD 的时候其 Top Paste 层芯片引脚没有,开钢网的时候没有芯片引脚的位置,这种情况该怎么办,如何解决

问题根源先说清楚嘉立创 EDA(LCEDA)导出 .PcbDoc 到 AD,两个软件 Mask(助焊 / 阻焊)底层机制不一样: 立创 EDA 贴片焊盘自动根据焊盘尺寸实时生成 Paste 开窗; 导出 AD 时不会把 “自动生成开窗…

2026/7/18 19:14:53

高速PCB设计中信号走线等长控制的关键技术与实践

1. 高速信号走线等长控制的必要性在PCB设计中,高速信号走线的等长控制是一个看似简单但极其关键的技术细节。我第一次真正意识到它的重要性是在设计一个DDR3内存接口时,当时系统频繁出现数据校验错误,经过两周的排查才发现问题出在地址线和数…

2026/7/18 19:14:53

SPI从模式DMA接收优化与高速传输实践

1. SPI从设备接收数据的核心挑战在嵌入式系统开发中,SPI(Serial Peripheral Interface)作为同步串行通信协议,因其简单高效的特点被广泛应用于各类外设连接。但当SPI设备工作在从模式(Slave Mode)接收数据时,开发者常会遇到数据丢失、响应延迟…

2026/7/18 19:14:53

PCB过孔设计:核心要素与工程实践

1. 过孔设计在PCB中的核心地位在PCB设计领域,过孔(Via)是连接不同层间导体的关键结构,就像城市中的立交桥连接着不同高度的道路。我从事PCB设计十多年来,见过太多因为过孔设计不当导致的信号完整性问题、散热不良甚至整…

2026/7/18 19:14:53

从功能实现到商业 SDK:Android 外设连接项目真正难在哪里?

从功能实现到商业 SDK:Android 外设连接项目真正难在哪里?Android SDK、USB Host、PTP、MTP、架构设计、外设开发、商业项目很多 Android 开发者都有过这样的经历:接到一个需求:"手机连接一个外部设备,然后读取数…

2026/7/18 19:09:53

DC-DC转换器环路补偿设计与稳定性优化实践

1. 电源稳定性的基础认知 电源稳定性是任何电子系统可靠工作的基石。想象一下,你正在用手机玩游戏,突然屏幕闪烁几下然后自动重启——这很可能就是电源系统失稳导致的典型故障。在DC-DC转换器这类开关电源中,稳定性问题会表现为输出电压振荡、…

2026/7/17 5:59:06

3步解锁音乐自由:ncmdumpGUI终极NCM文件解密转换指南

3步解锁音乐自由:ncmdumpGUI终极NCM文件解密转换指南 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换,Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾在网易云音乐下载了心爱的歌曲&#…

2026/7/18 10:53:38

CANoe 19 SP3 配置 GB/T 27930-2023 A类系统:3步搭建BMS仿真测试环境

CANoe 19 SP3 配置 GB/T 27930-2023 A类系统:3步搭建BMS仿真测试环境随着新能源汽车行业的快速发展,充电通信协议的标准化和测试验证变得尤为重要。GB/T 27930-2023作为中国智能充电协议的最新版本,对充电机与电动汽车之间的通信提出了更严格…

2026/7/17 7:39:19

3步搞定RTL8852BE驱动:从零开始配置Wi-Fi 6网卡

3步搞定RTL8852BE驱动:从零开始配置Wi-Fi 6网卡 【免费下载链接】rtl8852be Realtek Linux WLAN Driver for RTL8852BE 项目地址: https://gitcode.com/gh_mirrors/rt/rtl8852be 还在为Linux系统无法识别RTL8852BE Wi-Fi 6网卡而烦恼吗?&#x1f…

2026/7/18 0:00:24

某智驾大牛创业

作者:钟声编辑:Mark出品:红色星际头图:智能驾驶图片据悉,国内某头部智驾公司端到端模型技术大牛Z投身创业,并且已经拿到融资。Z不仅是该头部公司内部最年轻的对标阿里P10级别技术负责⼈,更是业内…

2026/7/18 16:50:29

3个高效策略:快速掌握Axure中文界面配置

3个高效策略:快速掌握Axure中文界面配置 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包。支持 Axure 11、10、9。不定期更新。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axure RP的英文界面感…