尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
如何从零写一个链接器:claudes-c-compiler内置链接器的符号解析与重定位完全指南
如何从零写一个链接器claudes-c-compiler内置链接器的符号解析与重定位完全指南【免费下载链接】claudes-c-compilerClaude Opus 4.6 wrote a dependency-free C compiler in Rust, with backends targeting x86 (64- and 32-bit), ARM, and RISC-V, capable of compiling a booting Linux kernel.项目地址: https://gitcode.com/gh_mirrors/cl/claudes-c-compilerclaudes-c-compilerCCC是一个用 Rust 从零编写的、零依赖的 C 编译器它不仅内置了编译器前端、优化器、汇编器还自带一个完整的链接器——能把.o目标文件和.a静态库直接拼成可运行的 ELF 可执行文件无需系统ld。本文带你拆解这个内置链接器最核心的两块逻辑符号解析与重定位帮你建立从零实现链接器的完整认知。 链接器到底在做什么链接器的工作可以概括为一句话把多个半成品目标文件组装成一个成品可执行文件。每个.o文件里都有代码和数据但其中的函数调用、全局变量引用都只是占位符重定位项地址尚未确定。链接器要完成三步步骤通俗解释对应模块① 加载输入解析.o、.a、.so文件input.rs② 符号解析搞清楚每个名字是谁定义的、从哪个库来linker_common/③ 重定位把占位符填上最终地址写出 ELFemit_exec.rs完整的流水线设计输入加载 → 符号解析 → 节合并 → PLT/GOT → 地址布局 → 重定位应用在 x86/linker/README.md 中有详细图示是入门的最佳路线图。四个架构x86-64、ARM、RISC-V、i686共享同一套 ELF64 链接逻辑被抽取到 linker_common/每个后端只需实现自己架构相关的部分重定位类型、PLT/GOT、ELF 头。这是从零写链接器时非常值得学习的架构设计。 第一步解析目标文件链接一切之前先要把磁盘上的字节流变成内存结构。parse_object.rs 中的parse_elf64_object完成这件事校验 ELF 魔数、ELFCLASS64、小端字节序、ET_REL可重定位对象解析节头表从.shstrtab还原节名读取每个节的原始字节解析.symtab符号表每个符号 24 字节解析所有SHT_RELA重定位表按目标节分组解析结果存进Elf64Object结构体节列表、符号表、节数据、重定位表一个对象文件就变成了一张待办清单。为了复用symbols.rs 定义了GlobalSymbolOpstrait让四个后端用各自的GlobalSymbol结构字段略有差异统一接入同一套解析/合并代码——这是泛型 trait处理多架构差异的经典手法。 符号解析谁定义了这个名字这是链接器的心脏。dynamic.rs 中的register_symbols_elf64遍历每个对象文件的符号表把符号登记到全局符号表globals一个HashMap符号名, GlobalSymbol规则清晰明了已定义的全局符号→ 写入全局表全局定义会覆盖弱定义静态定义还能覆盖动态来自.so的定义COMMON 符号C 语言文件级未初始化变量的产物→ 记录但允许被真实定义替换未定义符号→ 只插入占位条目表示我还需要它优先级总结为静态已定义 动态已定义 弱定义 未定义。这套规则正是理解ld行为的关键。静态库的选择性加载--start-group 算法.a静态库不能整个塞进可执行文件——那会拖入几百个你根本用不到的对象。archive.rs 实现了传统 Unix 链接语义循环直到不再有变化 对每个尚未拉入的库成员 如果它定义了当前未定义符号中的任意一个 拉入该成员登记它的符号这个迭代到不动点的循环能自动处理成员之间的传递依赖A 需要 BB 又需要 C。link.rs 中更进一步把所有库放在一个组里反复扫描等效于 GNU ld 的--start-group/--end-group解决了库之间的循环依赖问题。动态库兜底去系统 .so 里找人用户显式-l的库都加载完后还剩未定义符号dynamic.rs 的resolve_dynamic_symbols_elf64会到标准系统库libc.so.6、libm.so.6、libgcc_s.so.1里匹配命中的符号标记为is_dynamic并把 SONAME 记入DT_NEEDED列表。有个精妙细节链接器自己会提供一批符号_edata、_end、_GLOBAL_OFFSET_TABLE_、__bss_start等清单维护在 symbols.rs在检查未定义之前先排除否则会误报错误。 节合并把碎片拼成整块符号都找齐后各对象文件的.text、.data、.bss等碎片要拼成输出文件的大节。merge.rs 的merge_sections_elf64做三件事按名字归并.text.foo、.text.bar→ 统一进.text计算偏移每个输入节在输出节中的起始位置遵守对齐要求排序布局只读 → 可执行 → 读写 → BSS 的经典顺序至此每个符号都能算出最终虚拟地址——接下来就是重定位登场。 重定位填上最终地址重定位项的通用形式是四个数offset补丁位置、sym涉及哪个符号、type怎么算、addend附加常数。以 x86-64 为例最常见的几种计算式S符号值、A加数、P补丁位置、GGOT 项地址重定位类型计算式用途R_X86_64_64S A64 位绝对地址R_X86_64_PC32S A - P32 位 PC 相对call指令的位移R_X86_64_GOTPCRELG A - P经 GOT 间接寻址R_X86_64_GOTPCRELX可松弛为直接lea本地符号的优化机会链接器遍历所有输入节的所有重定位逐项计算并写入输出缓冲区对应偏移——这就是 emit_exec.rs 中重定位应用阶段。值得注意的两个优化GOT→LEA 松弛本地定义符号不必绕 GOT把mov symGOTPCREL(%rip), %reg改写成lea sym(%rip), %reg省一次内存间接访问死节消除--gc-sections时gc_sections.rs 从入口点做可达性分析不可达的节直接丢弃 PLT 与 GOT如何调用动态库函数调用printf这类运行时才在共享库里解析的函数不能直接写地址——链接器于是生成两个跳板GOT全局偏移表存放符号地址的数组PLT过程链接表每个外部函数一个 16 字节存根先跳到 GOT 项初始时 GOT 项指回 PLT 存根内部的解析器第一次调用时动态链接器ld.so解析出真实地址并打补丁——这就是延迟绑定。PLT/GOT 的构建逻辑在 plt_got.rs它扫描所有重定位PLT32遇到动态函数就分配 PLT 项若代码取的是动态数据对象地址如stderr还会生成COPY 重定位在.bss里复制一份。️ 推荐阅读路径想动手学习建议按这个顺序读源码x86/linker/README.md — 完整流水线设计文档含内存布局图linker_common/README.md — 共享模块的文件清单与设计决策linker_common/parse_object.rs → dynamic.rs → merge.rs — 解析、符号解析、合并三件套x86/linker/emit_exec.rs — 输出与重定位应用的重头戏x86/linker/emit_shared.rs — 进阶.so共享库输出RELRO、R_X86_64_RELATIVE构建后运行./target/release/ccc --version看到Backend: standalone即确认正在使用内置链接器。✅ 小结从零写一个链接器核心就是三张表 一次回填解析.o得到符号表和重定位表用优先级规则构建全局符号表靠迭代算法按需拉取静态库合并节、分配地址后按类型计算式逐项填充占位符动态符号留给运行时用 PLT/GOT 搭好跳板claudes-c-compiler 的内置链接器证明了这套逻辑完全可以在一个纯 Rust、零依赖的项目里实现——它甚至撑起了 Linux 内核级项目的链接需求。跟着 src/backend/linker_common/ 的源码走一遍你就拥有了理解任何现代链接器ld、lld、mold的底层视角。【免费下载链接】claudes-c-compilerClaude Opus 4.6 wrote a dependency-free C compiler in Rust, with backends targeting x86 (64- and 32-bit), ARM, and RISC-V, capable of compiling a booting Linux kernel.项目地址: https://gitcode.com/gh_mirrors/cl/claudes-c-compiler创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

Android 15.1更新提示存储空间不足?揭秘OTA存储校验与清理全攻略

Android 15.1更新提示存储空间不足?揭秘OTA存储校验与清理全攻略

昨天后台有个读者私信我,说手机收到了Android 15.1的更新推送,点下载之后直接弹了个“存储空间不足”。他特别困惑:手机明明还剩17.8GB可用空间,一个系统更新包撑死也就两三GB,怎么就不够了?我让他打开设置…

📅 2026/10/2 21:06:18
Spring AI Function Calling实战:Java后端工具调用与多轮对话

Spring AI Function Calling实战:Java后端工具调用与多轮对话

1. 为什么 Function Calling 值得你花时间吃透Function Calling 这个词,这两年在 Java 后端圈子里出现的频率越来越高。很多人第一次听到它,以为是什么新出的 RPC 框架或者某种远程调用协议,其实不是。它解决的是一个非常具体的问题&#xff…

📅 2026/10/2 21:06:18
MCP 简史:675 天换过五版规范,从 6 个示例服务器到 247 家机构

MCP 简史:675 天换过五版规范,从 6 个示例服务器到 247 家机构

MCP 简史:675 天换过五版规范,从 6 个示例服务器到 247 家机构 从 2024 年 11 月 25 日 Anthropic 发帖那天算起,到今天正好 675 天,不到两年。这期间 MCP 换过五版规范,官方 SDK 的累计下载量越过了 10 亿次&#xff…

📅 2026/10/2 21:06:18
MORE NEWS

更多资讯

📰

蓝牙芯片驱动开发-第4章第6题-OTA升级中如何确保固件完整性

蓝牙面试题解析:OTA 升级中如何确保固件完整性? 难度:⭐⭐⭐⭐ 较难 | 场景:社招二面/三面、OTA 开发 | 高频:🔥🔥🔥🔥🔥 标准答案 OTA 升级通过 传输层加密 + 分块校验 + 哈希验证 + 数字签名 四层保障固件的端到端完整性: ① OTA 完整性保障模型 手机/云端…

📰

SAP MIGO收货报错BK128/K5112:科目确定失败排查与修复指南

前两天收到一个同事的求助,说他们在SAP系统里执行MIGO收货时被拦住了,屏幕上同时弹出两个报错:BK128和K5112。这位同事是MM模块出身,对财务集成的科目确定逻辑本来就有点头大,一看到这两个编号连着蹦出来,整…

📰

Redis Lua原子预扣:大模型API网关配额防透支实践

做网关层大模型API治理有一段时间了,最让我记忆深刻的是某次月底账单事故:内部一个测试项目开了每日100万token的配额,结果一个压测脚本十几分钟就把当天配额烧穿,等发现时账单已经飘红。事后复盘,问题不在于没做限流&…

📰

Jev-Omni:轻量多模态决策模型的动态门控与一致性校准

1. 项目概述:Jev-Omni不是“玩具模型”,而是多模态决策能力的工程化落地切口你可能在热搜里看到过“Jev-Omni”这个名字,搭配着“图文音视频全支持”“《原神》声音被仿冒判赔75万”这类标题一起刷屏。但别急着划走——这不是又一个PPT级AI概…

📰

Figma-Context-MCP 路线图深度解析:从组件提取到企业级变量系统的演进规划

AI 应用MCP 服务 【免费下载链接】Figma-Context-MCP MCP server to provide Figma layout information to AI coding agents like Cursor 项目地址: https://gitcode.com/gh_mirrors/fi/Figma-Context-MCP 点击查看 免费下载 导读 ROADMAP.md 是 Figma-Context-M…

📰

AI工程新范式:skills可复用调用单元实践指南

1. “skills”不是功能模块,而是一套AI时代的新工作流范式最近三个月,我在三个不同行业的客户项目里,都撞见了同一个词反复出现在技术方案文档、内部会议纪要甚至实习生的周报里——不是“API”,不是“Agent”,而是ski…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬