SerenityOS readelf 命令完全指南:ELF 文件结构解析与安全加固审计
本指南基于 SerenityOS 内置的 readelf 命令及其 man 手册(Base/usr/share/man/man1/readelf.md),系统讲解其在 SerenityOS 中的全部命令行选项、输出含义与底层实现原理。读完本文,你将能够在 SerenityOS 终端中熟练使用 readelf 查看 ELF 头、程序头、节头、符号表、动态段与重定位信息,并通过 --checksec 对二进制进行安全加固特性审计(RELRO、栈金丝雀、NX、PIE、RPATH/RUNPATH)。
命令概览与适用场景
readelf 是 SerenityOS 用户态提供的 ELF(Executable and Linkable Format)文件查看工具,其功能与 GNU binutils 中的 readelf 对齐:解析目标文件的文件头、段(section)、节(program header)、符号表、动态链接信息与重定位表等,并以人类可读的表格形式输出。它常用于:
- 排查一个程序"是什么类型、面向什么架构"(
-h); - 理解动态链接器如何加载该程序(
-l、-d); - 审计二进制是否启用了常见安全加固(
-c); - 快速提取某个节内的可读字符串(
-p)。
该工具的完整实现位于 Userland/Utilities/readelf.cpp(约 720 行),基于 SerenityOS 的 LibELF 库完成解析,并在 Userland/Utilities/CMakeLists.txt 中注册、链接 LibELF(见 CMakeLists.txt)。
基本用法与参数总览
readelf 的完整命令形式如下(与 man 手册一致):
$ readelf [--all] [--file-header] [--program-headers] [--section-headers] [--headers] [--syms] [--dyn-syms] [--dynamic] [--notes] [--relocs] [--unwind] [--checksec] [--string-dump section-name] <path>
其中 path 为待解析的 ELF 文件路径,是唯一必需的位置参数。所有选项均为可选,若不指定任何选项,程序不会输出任何解析结果(源码中仅进行文件映射与合法性校验,readelf.cpp 之后的所有输出块均依赖对应开关)。
选项速查表
| 短选项 | 长选项 | 说明 |
|---|---|---|
-a |
--all |
显示全部信息(等价于打开除 -p 外的所有开关) |
-h |
--file-header |
显示 ELF 文件头 |
-l |
--program-headers |
显示程序头(program headers / segments) |
-S |
--section-headers |
显示节头(section headers) |
-e |
--headers |
等价于 -h -l -S -s -r -d -n -u -c |
-s |
--syms |
显示符号表(.symtab) |
| — | --dyn-syms |
显示动态符号表(.dynsym) |
-d |
--dynamic |
显示动态段 |
-n |
--notes |
显示 core notes |
-r |
--relocs |
显示重定位信息 |
-u |
--unwind |
显示展开(unwind)信息 |
-c |
--checksec |
显示安全加固信息 |
-p section-name |
--string-dump section-name |
以字符串形式转储指定节的内容,section-name 为节名 |
这些选项在源码中通过 LibCore::ArgsParser 注册(readelf.cpp),长选项与短选项一一对应,--dyn-syms 没有短别名(源码中传 '\0',见 readelf.cpp)。
组合选项的展开逻辑
-e/--headers 与 -a/--all 属于"宏开关",会在内部展开为多个具体开关(readelf.cpp):
--headers仅展开为-h -l -S(文件头、程序头、节头三项);--all则一次性开启文件头、程序头、节头、动态符号表、动态段、core notes、重定位、unwind、符号表与 checksec 共十项,等价于--headers加上-s -r -d -n -u -c --dyn-syms。
值得注意的是,man 手册对 -e 的描述为"Equivalent to: -h -l -S -s -r -d -n -u -c",但源码实现中 --headers 只展开 -h -l -S 三项,-s -r -d -n -u -c 需要由 --all 或逐项指定来开启,这是文档描述与当前实现存在的一处细微差异,实际使用请以源码行为为准。
各选项详解与输出解读
-h, --file-header:查看 ELF 文件头
该选项输出 ELF 头(Elf_Ehdr)的关键字段(readelf.cpp):
ELF header:
Magic: 7f 45 4c 46 02 01 01 00 ...
Type: 2 (Executable)
Machine: 62 (x86_64)
Version: 0x1
Entry point address: 0x401000
Start of program headers: 64 (bytes into file)
Start of section headers: 15464 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29
要点:
- Magic:前 4 字节固定为
7f 45 4c 46(即\x7fELF),源码逐字节输出,可打印 ASCII 字符以字符形式显示,否则以十六进制显示(readelf.cpp); - Type:文件类型由
ELF::Image::object_file_type_to_string()解析(Image.cpp),支持ET_NONE(None)、ET_REL(Relocatable)、ET_EXEC(Executable)、ET_DYN(Shared object)、ET_CORE(Core); - Machine:目标架构由
object_machine_type_to_string()解析(Image.cpp),源码中已列出 x86_64、Intel 80386/80486、MIPS、SPARC、Motorola 68000 等常见架构,SerenityOS 自身二进制通常为 x86_64。
-S, --section-headers:查看节头表
输出节头表,列包含 Name / Type / Address / Offset / Size / Flags(readelf.cpp):
Section Headers:
Name Type Address Offset Size Flags
.text PROGBITS 0x0000401000 0x00001000 0x00001234 AX
.rodata PROGBITS 0x0000405000 0x00003000 0x00000800 A
.bss NOBITS 0x0000407000 0x00004000 0x00001000 WA
...
节类型字符串由 object_section_header_type_to_string() 映射(readelf.cpp),覆盖 NULL、PROGBITS、SYMTAB、STRTAB、RELA、HASH、DYNAMIC、NOTE、NOBITS、REL、DYNSYM、INIT_ARRAY、FINI_ARRAY、PREINIT_ARRAY、GROUP、SYMTAB_SHNDX、RELR、GNU_HASH 等标准与 GNU 扩展类型。若文件无任何节,会输出 "There are no sections in this file."。
-l, --program-headers:查看程序头(段)
程序头描述 ELF 在加载时如何映射进内存,输出列包含 Type / Offset / VirtAddr / PhysAddr / FileSiz / MemSiz / Flg / Align(readelf.cpp):
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x00000000 0x00000000 0x00000000 0x00001000 0x00001000 0x04 0x1000
LOAD 0x00001000 0x00004000 0x00004000 0x00002200 0x00003200 0x06 0x1000
INTERP 0x00000238 0x00000238 0x00000238 0x0000001c 0x0000001c 0x04 0x1
[Interpreter: /usr/lib/Loader.so]
...
要点:
- 程序头类型由
object_program_header_type_to_string()映射(readelf.cpp),覆盖NULL、LOAD、DYNAMIC、INTERP、NOTE、SHLIB、PHDR、TLS、GNU_EH_FRAME、GNU_RELRO、GNU_STACK,以及 OpenBSD 的RANDOMIZE、WXNEEDED、BOOTDATA扩展; - 若某个程序头类型为
PT_INTERP,会额外输出[Interpreter: ...]行显示动态解释器路径(readelf.cpp),SerenityOS 上通常为/usr/lib/Loader.so; - PhysAddr 列当前直接复用 VirtAddr 值,源码注释标明这是假设 PhysAddr = VirtAddr 的简化处理(readelf.cpp),并留有 TODO(段到节的映射关系尚未输出,见 readelf.cpp)。
-s, --syms 与 --dyn-syms:查看符号表
两个选项分别输出普通符号表 .symtab 与动态符号表 .dynsym,列包含 Num / Value / Size / Type / Bind / Name(readelf.cpp):
Symbol table '.dynsym' contains 42 entries.
Num: Value Size Type Bind Name
0: 0x00000000 0x00000000 NOTYPE LOCAL Undefined
1: 0x00004020 0x00000040 FUNC GLOBAL _start
2: 0x00004100 0x00000012 OBJECT GLOBAL __stack_chk_fail
...
符号类型由 object_symbol_type_to_string() 映射(readelf.cpp),包括 NOTYPE、OBJECT、FUNC、SECTION、FILE、TLS、IFUNC;符号绑定由 object_symbol_binding_to_string() 映射(readelf.cpp),包括 LOCAL、GLOBAL、WEAK。
实现细节:动态符号表通过 ELF::DynamicObject 遍历(readelf.cpp),普通符号表则通过 ELF::Image::for_each_symbol 遍历(readelf.cpp);若文件无对应符号信息,会输出 "No dynamic symbol information for this file." 或 "Symbol table '.symtab' contains zero entries."。
-d, --dynamic:查看动态段
该选项要求文件为动态 ELF,输出动态段条目(readelf.cpp):
Dynamic section '.dynamic' at offset 0x00003000 contains 12 entries.
Tag Type Name / Value
0x00000001 DT_NEEDED Shared library: libc.so
0x00000002 DT_RPATH Library rpath: /usr/lib
0x0000000e DT_SONAME Library soname: libfoo.so
...
其中 DT_NEEDED 显示依赖的共享库名(按顺序对应),DT_RPATH、DT_RUNPATH 显示库搜索路径,DT_SONAME 显示库自身标识,其余条目直接以十六进制值输出(readelf.cpp)。若文件无 .dynamic 节,输出 "No dynamic section in this file."。
-r, --relocs:查看重定位信息
重定位是动态链接的核心:静态链接器留下"待填写的地址",动态加载器在运行时按重定位条目修正。该选项输出三类重定位(readelf.cpp):
- 普通重定位节(如
.rela.dyn),列包含 Offset / Type / Sym Value / Sym Name; - PLT 重定位节(如
.rela.plt),格式同上; - 压缩重定位
.relr.dyn(RELR 格式),列出全部需要修正的偏移。
重定位类型名称通过 object_relocation_type_to_string() 按机器架构解析(readelf.cpp),当前支持 x86_64、AArch64 与 RISC-V 三种架构的动态重定位枚举;其他架构显示 (?)。若文件非动态 ELF,输出 "No relocations in this file."。
-n, --notes:查看 core notes
该选项遍历所有 PT_NOTE 程序头并输出其原始数据(readelf.cpp)。源码注释说明:SerenityOS 上 core notes 采用 JSON 格式存储,但不同系统格式各异,因此当前实现仅做原始数据透出,并未做结构化解析(标注有 FIXME)。无 notes 时输出 "No core notes in this file."。
-u, --unwind:查看展开信息
当前实现为占位:统一输出 "Decoding of unwind sections for machine type {arch} is not supported."(readelf.cpp),源码中留有 TODO。因此该选项在 SerenityOS 当前版本中主要用于确认架构,实际展开信息解析尚未落地。
-c, --checksec:安全加固审计
这是 SerenityOS readelf 最具特色的功能,将二进制安全属性汇总为一行彩色输出(readelf.cpp):
Security Hardening:
RELRO Stack Canary NX PIE RPATH RUNPATH Symbols
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 42 symbols
各项判定逻辑如下:
| 项目 | 判定依据(源码位置) |
|---|---|
| RELRO | 是否存在 PT_GNU_RELRO 程序头;若存在再检查动态段是否有 DT_BIND_NOW,有则为 Full RELRO(绿),否则 Partial RELRO(黄),无则为 No RELRO(红) |
| Stack Canary | 符号表中是否出现 __stack_chk_fail 或 __intel_security_cookie 符号,有则 Canary found(绿) |
| NX | 是否存在 PT_GNU_STACK 程序头且不带 PF_X 执行标志,满足则为 NX enabled(绿) |
| PIE | 文件类型为 ET_REL 或 ET_DYN 即为 PIE enabled(绿),ET_EXEC 则为 No PIE(红) |
| RPATH / RUNPATH | 动态 ELF 中是否存在 rpath / runpath 值,为空输出绿色 No RPATH/No RUNPATH,否则以红色输出具体路径 |
| Symbols | 输出符号总数 |
颜色通过 ANSI 转义序列实现:绿色 \033[0;32m、黄色 \033[0;33m、红色 \033[0;31m(readelf.cpp),在支持 ANSI 的终端中可直接通过颜色区分安全状态。
-p, --string-dump section-name:字符串转储指定节
按 \0 分隔符将指定节的内容切分为字符串,并输出每个字符串在节内的十六进制偏移(readelf.cpp):
$ readelf -p .rodata /bin/Shell
String dump of section '.rodata':
[ 0] Usage: {}
[ 11] /bin/Shell
[ 1d] error: {}
若节不存在,输出错误 Could not find section '{name}' 并返回退出码 1。-p 是唯一带参数(节名)的选项,需放在选项值位置,如 -p .text 或 --string-dump .rodata。
底层实现剖析
参数解析与权限模型
readelf 的入口 serenity_main 首先通过 Core::System::pledge("stdio rpath map_fixed") 声明所需的系统能力(readelf.cpp):stdio(标准 IO)、rpath(读文件)、map_fixed(固定地址映射,用于动态 ELF 映射)。随后用 LibCore::ArgsParser 注册全部选项与位置参数 path(readelf.cpp),这也是 man 手册末尾标注 "Auto-generated through ArgsParser" 的原因——手册由 ArgsParser 的选项描述自动生成。
文件加载与合法性校验
加载流程为(readelf.cpp):
- 通过
LexicalPath::absolute_path将相对路径解析为绝对路径; - 用
Core::MappedFile::map将文件映射进内存; - 构造
ELF::Image并调用is_valid()校验,失败则输出 "File is not a valid ELF object" 并退出; - 调用
ELF::validate_program_headers(声明于 Validation.h)做更严格的程序头校验,同时从中提取PT_INTERP解释器路径,失败则输出 "Invalid ELF headers"。
ELF::Image 的解析发生在构造函数中(Image.cpp),后续所有节/程序头/符号遍历都基于该解析结果。
动态 ELF 的特殊处理
若目标文件是动态 ELF(is_dynamic() 为真),readelf 会额外执行动态加载流程(readelf.cpp):
- 若
PT_INTERP未提供解释器路径,默认使用/usr/lib/Loader.so并给出警告; - 映射解释器并校验其合法性;
- 通过
ELF::DynamicLoader::try_create创建加载器、loader->map()得到ELF::DynamicObject,后续动态段、动态符号表、重定位、RPATH/RUNPATH 等信息均从该对象读取。
因此 readelf 在 SerenityOS 中不仅是"静态查看器",对动态二进制还会真实执行一次动态加载的解析路径,这是其区别于常规 readelf 实现的显著特点。
构建与运行
readelf 作为用户态工具随 SerenityOS 一起构建,在 Userland/Utilities/CMakeLists.txt 的 SERENITY_BIN 列表中注册,并链接 LibELF(CMakeLists.txt)。整体构建步骤参见 BuildInstructions.md。构建完成后在 SerenityOS 终端中直接调用:
$ readelf --all /bin/Shell # 查看 Shell 二进制的全部 ELF 信息
$ readelf -c /bin/Shell # 快速安全加固审计
$ readelf -h /System/bin/id # 仅看 ELF 头
小结
SerenityOS 的 readelf 在 man 手册(Base/usr/share/man/man1/readelf.md)的基础上,由 readelf.cpp 提供了完整的 ELF 解析能力:文件头、节头、程序头、两类符号表、动态段、重定位(含 RELR 压缩重定位)与 core notes,并通过 --checksec 提供一行式的安全加固审计;其底层基于 LibELF 实现解析、DynamicLoader 实现动态对象加载,是理解 SerenityOS ELF 加载链路与二进制安全特性的实用入口。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.21 K635- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python40
jforgamejforgame是一个一站式游戏服务器开发框架。包含游戏服务器开发所需要的各种组件,比如网关,socket服务端与客户端,自定义高效消息编解码,游戏热更新,游戏通用工具等等。包含游戏服,跨服,匹配服,后台管理系统等实现,同时提供大量业务案例以供学习。亦可用于其他socket应用,例如及时聊天等。Java131
fizz-gateway-nodeAn Aggregation API Gateway in Java . FizzGate 是一个基于 Java开发的微服务聚合网关,是拥有自主知识产权的应用网关国产化替代方案,能够实现热服务编排聚合、自动授权选择、线上服务脚本编码、在线测试、高性能路由、API审核管理、回调管理等目的,拥有强大的自定义插件系统可以自行扩展,并且提供友好的图形化配置界面,能够快速帮助企业进行API服务治理、减少中间层胶水代码以及降低编码投入、提高 API 服务的稳定性和安全性。Java80
certd开源SSL证书管理工具;全自动证书申请、更新、续期;通配符证书,泛域名证书申请;证书自动化部署到阿里云、腾讯云、主机、群晖、宝塔;https证书,pfx证书,der证书,TLS证书,nginx证书自动续签自动部署JavaScript80
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python290