首页
/ SerenityOS readelf 命令完全指南:ELF 文件结构解析与安全加固审计

SerenityOS readelf 命令完全指南:ELF 文件结构解析与安全加固审计

2026-09-09 23:54:44作者:戚魁泉Nursing

本指南基于 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 / Flagsreadelf.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),覆盖 NULLPROGBITSSYMTABSTRTABRELAHASHDYNAMICNOTENOBITSRELDYNSYMINIT_ARRAYFINI_ARRAYPREINIT_ARRAYGROUPSYMTAB_SHNDXRELRGNU_HASH 等标准与 GNU 扩展类型。若文件无任何节,会输出 "There are no sections in this file."。

-l, --program-headers:查看程序头(段)

程序头描述 ELF 在加载时如何映射进内存,输出列包含 Type / Offset / VirtAddr / PhysAddr / FileSiz / MemSiz / Flg / Alignreadelf.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),覆盖 NULLLOADDYNAMICINTERPNOTESHLIBPHDRTLSGNU_EH_FRAMEGNU_RELROGNU_STACK,以及 OpenBSD 的 RANDOMIZEWXNEEDEDBOOTDATA 扩展;
  • 若某个程序头类型为 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 / Namereadelf.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),包括 NOTYPEOBJECTFUNCSECTIONFILETLSIFUNC;符号绑定由 object_symbol_binding_to_string() 映射(readelf.cpp),包括 LOCALGLOBALWEAK

实现细节:动态符号表通过 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_RPATHDT_RUNPATH 显示库搜索路径,DT_SONAME 显示库自身标识,其余条目直接以十六进制值输出(readelf.cpp)。若文件无 .dynamic 节,输出 "No dynamic section in this file."。

-r, --relocs:查看重定位信息

重定位是动态链接的核心:静态链接器留下"待填写的地址",动态加载器在运行时按重定位条目修正。该选项输出三类重定位(readelf.cpp):

  1. 普通重定位节(如 .rela.dyn),列包含 Offset / Type / Sym Value / Sym Name
  2. PLT 重定位节(如 .rela.plt),格式同上;
  3. 压缩重定位 .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_RELET_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;31mreadelf.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 注册全部选项与位置参数 pathreadelf.cpp),这也是 man 手册末尾标注 "Auto-generated through ArgsParser" 的原因——手册由 ArgsParser 的选项描述自动生成。

文件加载与合法性校验

加载流程为(readelf.cpp):

  1. 通过 LexicalPath::absolute_path 将相对路径解析为绝对路径;
  2. Core::MappedFile::map 将文件映射进内存;
  3. 构造 ELF::Image 并调用 is_valid() 校验,失败则输出 "File is not a valid ELF object" 并退出;
  4. 调用 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.txtSERENITY_BIN 列表中注册,并链接 LibELFCMakeLists.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 加载链路与二进制安全特性的实用入口。

热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
docsdocs
暂无描述
Markdown
900
5.83 K
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.15 K
2.77 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
860
1.36 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
929
1.85 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.94 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
534
603
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.37 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
398
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.05 K
528