新手必看:如何用objdump和readelf深入分析ELF文件结构(附hello_world.o实例) 📅 发布时间:2026/7/11 4:01:14 👁️ 浏览次数: 从零开始用objdump和readelf解剖ELF文件以hello_world.o为例你是否曾好奇自己写下的几行C语言代码在编译之后究竟变成了什么模样那个看似神秘的.o文件里除了你的代码逻辑还藏着哪些不为人知的秘密对于许多刚接触系统底层或逆向工程的开发者来说理解编译后的产物是迈向进阶的关键一步。今天我们就以最经典的hello_world.o为例拿起objdump和readelf这两把“手术刀”亲手解剖一个ELF目标文件看看它的五脏六腑是如何运作的。这篇文章面向所有对程序底层结构感兴趣的初学者无论你是想深入理解编译链接过程还是为后续的调试、逆向分析打下基础这次探索都将是一次扎实的实践。1. 认识我们的“病人”ELF文件与目标文件在动手术之前我们得先了解病人的基本情况。ELF全称Executable and Linkable Format即可执行可链接格式是Linux和许多类Unix系统上二进制文件可执行文件、目标文件、共享库等的标准格式。你可以把它想象成一个结构严谨的集装箱里面分门别类地存放着程序运行所需的一切机器指令、数据、符号信息、重定位表等等。我们今天的“病人”hello_world.o是一个可重定位目标文件。它与最终的可执行文件如a.out关键区别在于“可重定位”这四个字。这意味着文件中的代码和数据地址还没有被最终确定它们通常是基于零地址或某个相对地址编译的需要链接器ld在链接阶段将其与其他目标文件或库合并并分配最终的绝对内存地址。为了有个直观感受我们先创建一个简单的C程序并编译成目标文件// hello.c #include stdio.h int global_init_var 84; // 已初始化的全局变量将进入 .data 段 int global_uninit_var; // 未初始化的全局变量将进入 .bss 段 static int static_var 0; // 初始化为0的静态变量进入 .bss 段 void func(int arg) { printf(Argument is: %d\n, arg); } int main() { static int local_static_var 42; // 已初始化的局部静态变量进入 .data 段 int local_var 10; // 局部自动变量存在于栈上不占目标文件空间 func(local_var global_init_var); return 0; }使用GCC编译它生成目标文件gcc -c hello.c -o hello.o现在我们就得到了一个标准的ELF可重定位目标文件hello.o它将成为我们后续所有分析的样本。2. 第一把手术刀用readelf查看ELF宏观结构readelf命令是专门为解析ELF文件格式而生的工具它能以人类可读的方式展示ELF文件头、段表、节表、符号表等核心结构信息让我们对文件的整体布局有一个宏观的把握。2.1 查看ELF文件头文件的“身份证”ELF文件头位于文件开头包含了描述整个文件属性的元数据。使用readelf -h可以查看readelf -h hello.o输出会类似于下面这样为简洁起见部分细节已简化ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2s 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: 1168 (bytes into file) 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: 10这里有几个关键信息需要解读Magic 文件开头的魔数7f 45 4c 46对应字符\177ELF是ELF格式的标识。ClassELF64表明这是一个64位架构的文件。如果是32位则会显示ELF32。Data2s complement, little endian表示数据以二进制补码、小端字节序方式存储。这是x86/x86-64架构的典型特征。TypeREL明确指出了这是一个可重定位文件Relocatable file。如果是可执行文件这里会是EXEC动态共享库则是DYN。MachineAdvanced Micro Devices X86-64指明了目标机器架构。Entry point address 入口点地址为0。对于可重定位文件这个地址是未定义的要等到链接成可执行文件时才会被确定。Start of section headers节头表Section Header Table在文件中的偏移位置1168字节处。这个表是理解目标文件内容的核心地图。注意ELF文件中有两个容易混淆的概念段Segment和节Section。段是程序执行视图的概念由程序头表描述用于操作系统加载程序节是链接视图的概念由节头表描述用于编译器和链接器处理数据。目标文件.o通常没有程序头表所以上面显示为0只有节头表。2.2 查看节头表文件的“详细目录”节头表是ELF文件的“目录”它列出了文件中所有的“节”Section。每个节都有其特定的类型和用途比如存放代码、数据、符号等。使用readelf -S查看readelf -S hello.o输出会列出所有节的信息包括名称、类型、地址、偏移量、大小等。一个典型的输出可能包含以下关键节节名可能因编译器和平台略有差异节名 (Name)类型 (Type)地址 (Addr)偏移量 (Off)大小 (Size)说明.textPROGBITS00000000000000000x000000400x0000007f程序代码存放机器指令.dataPROGBITS00000000000000000x000000c00x00000008已初始化的全局/静态变量初始值非0.bssNOBITS00000000000000000x000000c80x00000008未初始化或初始化为0的全局/静态变量.rodataPROGBITS00000000000000000x000000c80x0000001e只读数据如字符串常量、const变量.commentPROGBITS00000000000000000x000000e60x00000036编译器版本信息.note.GNU-stackPROGBITS00000000000000000x0000011c0x00000000堆栈执行属性提示.eh_framePROGBITS00000000000000000x000001200x00000058异常处理框架信息用于C等.rela.textRELA00000000000000000x000001780x00000060.text节的重定位信息.symtabSYMTAB00000000000000000x000002000x00000168符号表.strtabSTRTAB00000000000000000x000003680x0000006a字符串表存放符号名等字符串从这个表中我们可以清晰地看到文件是如何被组织的。.text、.data、.bss、.rodata是我们要重点关注的几个核心节。3. 第二把手术刀用objdump进行微观探查如果说readelf给了我们一张地图那么objdump就是我们的显微镜和翻译器它能让我们看到每个节内部的原始字节并将其反汇编成可读的汇编代码或者解析出符号和重定位信息。3.1 探查代码节 (.text)机器指令的真相.text节存放着所有函数的机器指令。我们可以先用objdump -h快速查看各节摘要然后用objdump -s以十六进制和ASCII格式显示节内容最后用objdump -d进行反汇编。查看节摘要objdump -h hello.o这个命令的输出与readelf -S类似但格式更简洁专注于大小和偏移。查看.text节的原始内容objdump -s -j .text hello.o输出会是连续的十六进制字节流。对于初学者直接看这个可能像天书。这就是我们需要反汇编的原因。反汇编.text节objdump -d hello.o这是最激动人心的部分。你会看到类似下面的输出基于之前的示例代码Disassembly of section .text: 0000000000000000 func: 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 48 83 ec 10 sub $0x10,%rsp 8: 89 7d fc mov %edi,-0x4(%rbp) b: 8b 45 fc mov -0x4(%rbp),%eax e: 89 c6 mov %eax,%esi 10: bf 00 00 00 00 mov $0x0,%edi 15: b8 00 00 00 00 mov $0x0,%eax 1a: e8 00 00 00 00 callq 1f func0x1f 1f: b8 00 00 00 00 mov $0x0,%eax 24: c9 leaveq 25: c3 retq 0000000000000026 main: 26: 55 push %rbp 27: 48 89 e5 mov %rsp,%rbp 2a: 48 83 ec 20 sub $0x20,%rsp 2e: c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp) 35: 8b 15 00 00 00 00 mov 0x0(%rip),%edx # 3b main0x15 3b: 8b 45 fc mov -0x4(%rbp),%eax 3e: 01 d0 add %edx,%eax 40: 89 c7 mov %eax,%edi 42: e8 00 00 00 00 callq 47 main0x21 47: b8 00 00 00 00 mov $0x0,%eax 4c: c9 leaveq 4d: c3 retq左侧第一列是当前指令在.text节内的偏移地址从0开始。中间列是机器指令对应的十六进制字节码。注意观察callq和mov 0x0(%rip), %edx指令的操作数部分很多都是00 00 00 00。这正是“可重定位”的体现这些是占位符链接器需要根据符号的实际地址来填充这些零。右侧是反汇编得到的ATT语法汇编指令。3.2 探查数据节变量存储的奥秘数据主要分布在.data、.bss和.rodata节。查看.data节已初始化数据objdump -s -j .data hello.o输出可能如下Contents of section .data: 0000 54000000 2a000000 T...*...这里显示了两个四字节整数小端序0x00000054(十进制84) 和0x0000002a(十进制42)。它们分别对应我们代码中的global_init_var和local_static_var。查看.bss节objdump -h -j .bss hello.o你会发现.bss节在文件中的大小Size不为0但它在文件中的偏移量之后没有实际的“内容”Contents。这是因为.bss节在文件中不占据实际空间它仅仅在节头表中声明了程序运行时需要多少未初始化的内存空间在这里是8字节对应global_uninit_var和static_var。这能有效减小目标文件和最终可执行文件的体积。查看.rodata节只读数据objdump -s -j .rodata hello.o输出Contents of section .rodata: 0000 41726775 6d656e74 2069733a 2025640a Argument is: %d. 0010 00 .这里清晰地存储着printf函数中使用的格式字符串Argument is: %d\n。因为它是常量所以被放在只读节试图修改它会导致段错误。3.3 探查符号与重定位链接的基石目标文件之所以能被链接依赖于符号表和重定位表。查看符号表objdump -t hello.o # 或 readelf -s hello.o符号表列出了文件中定义和引用的所有符号函数名、变量名等。你会看到global_init_var、static_var等变量符号以及func、main函数符号。每个符号都有其所在节如.data、.bss、.text、大小、绑定信息GLOBAL/LOCAL和类型OBJECT/FUNC。对于未定义的符号如printf其所在节标记为UND需要在链接时从其他目标文件或库中解析。查看重定位信息objdump -r hello.o # 或 readelf -r hello.o重定位表告诉链接器“在.text节的偏移量0x1b处有一个对符号printf的调用请你在链接时把printf的真实地址填进去。” 这就是链接器工作的核心依据。例如你会在输出中看到对printf和global_init_var的重定位条目。4. 综合实战追踪一个变量的完整生命周期让我们把上面的工具结合起来完成一次完整的侦查。以全局变量global_init_var为例定义在源码hello.c中我们定义了int global_init_var 84;。编译后归属因为它是一个已初始化的全局变量编译器会将其放入.data节。通过readelf -S或objdump -h可以确认.data节的存在和大小。查看存储值使用objdump -s -j .data可以看到.data节起始的4个字节是54 00 00 00小端序的84。符号记录使用objdump -t可以在符号表中找到global_init_var记录其类型为GLOBAL OBJECT位于.data节偏移量为0相对于.data节起始。被引用在main函数中我们使用了global_init_var。反汇编 (objdump -d) 可以看到类似mov 0x0(%rip), %edx的指令但地址是0。重定位需求使用objdump -r查看.text节的重定位信息会发现一条记录指出在main函数中某条指令对应上面mov指令的位置需要引用符号global_init_var。链接时链接器会根据global_init_var最终被分配到的内存地址来修正这条指令中的偏移量。通过这样一条线的追踪你就能清晰地理解一个变量从源代码到二进制指令的完整旅程以及链接器在其中扮演的关键角色。5. 进阶技巧与常见问题排查掌握了基本解剖方法后这些技巧能让你在实战中更得心应手。使用-C选项反汇编如果代码是用C编译的函数名可能被修饰mangled。使用objdump -d -C hello.o可以尝试进行反修饰demangle让函数名恢复可读。结合grep过滤信息当输出信息很多时用grep快速定位。例如objdump -t hello.o | grep main快速找到main函数的符号信息。对比分析编译时尝试不同优化等级-O0,-O1,-O2,-Os然后用objdump -d对比.text节的变化直观感受编译器优化对代码生成的影响。调试未定义符号错误链接时如果报错undefined reference to xxx先用objdump -t your.o | grep xxx检查是否在你自己的目标文件中定义。如果没有再用nm -D /usr/lib/libc.so.6 | grep xxx如果是库函数检查动态库或者确认是否链接了必要的库-l选项。分析段大小异常如果发现可执行文件体积莫名变大可以用size命令快速查看各段大小再用objdump -h和readelf -S详细查看是否有不期望的大数据节可能是某个大型数组或静态数据被错误定义。理解ELF文件结构不是一蹴而就的最好的学习方法就是像今天这样自己写一个小程序然后反复使用objdump和readelf去观察、修改代码、再观察。每次编译选项的改变、代码的细微调整都可能反映在二进制文件的结构上。当你习惯了这种“透过现象看本质”的思考方式无论是进行深度调试、性能分析还是探索系统安全都会拥有一个坚实而强大的起点。
Raspberry Pi Pico电赛硬核速通:GPIO/PWM/ADC/IRQ等7大外设工程实践 1. Raspberry Pi Pico 基础外设工程实践:面向电赛控制类的硬核速通Raspberry Pi Pico 是一款基于 RP2040 微控制器的开发板,其双核 ARM Cortex-M0 架构、丰富的片上外设资源以及极高的性价比,使其成为电子设计竞赛(电赛࿰… 2026/7/9 15:34:22
XUnity.AutoTranslator全攻略:让Unity游戏翻译变得简单高效 XUnity.AutoTranslator全攻略:让Unity游戏翻译变得简单高效 【免费下载链接】XUnity.AutoTranslator 项目地址: https://gitcode.com/gh_mirrors/xu/XUnity.AutoTranslator 突破语言壁垒:插件核心能力解析 解锁实时翻译技术 实时翻译技术是XUn… 2026/7/4 8:42:07
SiameseUniNLU应用场景:法律文书要素抽取(当事人/案由/诉讼请求/判决结果)结构化落地 SiameseUniNLU应用场景:法律文书要素抽取(当事人/案由/诉讼请求/判决结果)结构化落地 1. 项目背景与价值 法律文书处理一直是法律科技领域的核心挑战。传统的法律文书分析需要专业律师花费大量时间人工阅读和标注,效率低下且成本… 2026/7/8 14:55:34
AutoCAD 2020 DATAEXTRACTION 命令实战:3步提取高程点坐标并导出TXT AutoCAD高程点坐标提取实战:从零到精通的3步标准化流程在测绘工程、土木设计和地质勘探领域,CAD图纸中的高程点数据提取是基础却至关重要的环节。传统手工记录坐标的方式不仅效率低下,还容易引入人为误差。本文将深入解析AutoCAD 2020内置的D… 2026/7/11 4:00:02
告别百度排名内卷!GEO发稿:品牌AI数字资产沉淀的核心方案 传统品牌发稿围绕搜索引擎抓取与排名,追求百度收录和关键词位次。但随着生成式AI成为信息获取主渠道,品牌曝光阵地已从搜索结果页转向AI对话界面。GEO(生成式引擎优化)发稿正式取代传统SEO,核心逻辑是打造优质、权威、… 2026/7/11 3:56:01
基于STM32单片机智能温度PID控制系统恒温蓝牙无线APP/WiFi无线APP/摄像头视频监控设计DIY184 本系统由STM32F103C8T6单片机核心板、1.44寸TFT彩屏、(无线蓝牙/WIFI模块-可选)、DS18B20温度传感器(防水)、加热电阻驱动电路、散热风扇驱动电路、蜂鸣器驱动电路、独立按键电路及电源组成。注意视频监控及WIFI套餐才拥有视频监控… 2026/7/11 3:54:00
LV3296与PIC18F45K40嵌入式条码扫描系统设计 1. LV3296与PIC18F45K40硬件系统架构解析在嵌入式条码扫描系统中,LV3296作为前端数据采集模块,与PIC18F45K40微控制器构成了典型的"传感器处理器"架构。这种组合在工业自动化、零售POS机和物流分拣等领域有着广泛应用。LV3296是一款高性能的CM… 2026/7/11 3:54:00
Unity 2023.2打包PICO4 APK:三大高频错误解析与实战解决方案 1. 项目概述:PICO4开发与Unity打包的“最后一公里”如果你正在为PICO4开发VR应用,那么从Unity编辑器里那个运行流畅的场景,到最终能在头显里安装运行的APK文件,中间往往横亘着一道名为“打包”的鸿沟。这感觉就像精心组装了一台精… 2026/7/11 3:49:59
Re-TRAC框架:用结构化状态表示重塑LLM搜索智能 1. 这不是又一个“更大即更好”的故事,而是对搜索智能本质的重新定义你有没有试过让一个大模型帮你查点东西?比如“2024年诺贝尔物理学奖得主在获奖前最被引用的三篇论文是什么?”——问题一抛出去,模型立刻开始调用搜索工具&… 2026/7/11 3:47:58
5分钟搞定Kodi字幕难题:智能字幕插件让你追剧无忧 [特殊字符] 5分钟搞定Kodi字幕难题:智能字幕插件让你追剧无忧 🎬 【免费下载链接】zimuku_for_kodi Kodi 插件,用于从「字幕库」网站下载字幕 项目地址: https://gitcode.com/gh_mirrors/zi/zimuku_for_kodi 还记得那个深夜吗?你刚下载… 2026/7/11 0:00:11
工业信号干扰处理与FOD4216光耦应用实战 1. 工业环境中的信号干扰挑战在工业自动化领域,信号采集的准确性直接关系到整个控制系统的可靠性。典型的工业现场充斥着各种干扰源:大功率电机启停产生的电磁干扰、变频器工作产生的高频噪声、继电器触点火花放电,以及长距离传输引入的共模干… 2026/7/11 0:00:11
OpenHarmony 完整项目工程整合规范 + 模块化分层架构(API23+ 标准企业级结构) 摘要前面系列教程覆盖了 ArkUI 组件、路由、生命周期、本地存储、网络请求、Ability 底层全套基础能力,本篇统一梳理标准工程目录分层、模块化拆分、代码复用规范、全局工具统一管理、项目打包权限配置、常见工程报错统一解决方案,形成可直接用于课程设计… 2026/7/11 0:00:11
6个月转型AI工程师:实战路径与核心技能 1. 项目概述:6个月转型AI工程师的可行性路径在2023年大模型技术爆发的背景下,AI工程师岗位需求同比增长217%(LinkedIn数据)。不同于传统算法工程师需要3-5年培养周期,现代AI工程师更侧重工程化落地能力。我在硅谷科技公… 2026/7/7 11:26:57
TPAFE0808与PIC18F87K22的多通道信号采集方案 1. 项目背景与核心需求在工业自动化、医疗设备和科研仪器等领域,多通道信号采集与系统监测是基础且关键的技术需求。传统方案往往面临通道数量不足、信号调理复杂、系统集成度低等问题。TPAFE0808作为一款8通道模拟前端芯片,与PIC18F87K22微控制器的组合… 2026/7/8 20:15:17
STC3115与PIC18LF26K80构建高精度电池管理系统 1. STC3115与PIC18LF26K80在电池管理系统中的核心价值在现代电子设备中,电池管理系统(BMS)的重要性不亚于设备的核心处理器。STC3115作为一款高精度电池电量监测IC,与PIC18LF26K80微控制器的组合,构成了一个既能精确监控又能智能管理的完整解… 2026/7/8 14:25:08