Livepatch 模块 ELF 格式¶
本文档概述了热补丁模块必须遵循的 ELF 格式要求。
1. 背景和动机¶
以前,热补丁需要单独的特定于架构的代码来编写重定位。但是,模块加载器中已经存在用于编写重定位的特定于架构的代码,因此这种先前的方法产生了冗余代码。因此,热补丁没有复制代码和重新实现模块加载器已经可以做的事情,而是利用模块加载器中的现有代码来执行所有特定于架构的重定位工作。具体来说,热补丁重用了模块加载器中的 apply_relocate_add() 函数来编写重定位。本文档中描述的补丁模块 ELF 格式使热补丁能够做到这一点。希望这将使热补丁更容易移植到其他架构,并减少将热补丁移植到特定架构所需的特定于架构的代码量。
由于 apply_relocate_add() 需要访问模块的节头表、符号表和重定位节索引,因此为热补丁模块保留了 ELF 信息(参见第 5 节)。热补丁管理其自己的重定位节和符号,这将在本文档中进行描述。用于标记热补丁符号和重定位节的 ELF 常量是根据 glibc 的定义从特定于操作系统的范围中选择的。
为什么热补丁需要编写自己的重定位?¶
典型的热补丁模块包含已修补的函数版本,这些函数可以引用未导出的全局符号和未包含的局部符号。引用这些类型符号的重定位不能按原样保留,因为内核模块加载器无法解析它们,因此会拒绝热补丁模块。此外,我们无法应用影响在补丁模块加载时尚未加载的模块的重定位(例如,对未加载的驱动程序的修补)。以前,热补丁通过在生成的补丁模块 ELF 输出中嵌入特殊的 “dynrela”(动态 rela)节来解决此问题。使用这些 dynrela 节,热补丁可以解析符号,同时考虑到其作用域和符号所属的模块,然后手动应用动态重定位。但是,这种方法要求热补丁提供特定于架构的代码才能写入这些重定位。在新的格式中,热补丁使用其自己的 SHT_RELA 重定位节来代替 dynrela 节,并且 rela 引用的符号是特殊的热补丁符号(请参见第 2 节和第 3 节)。特定于架构的热补丁重定位代码被对 apply_relocate_add() 的调用所取代。
2. Livepatch modinfo 字段¶
热补丁模块必须具有 “livepatch” modinfo 属性。有关如何执行此操作,请参见 samples/livepatch/ 中的示例热补丁模块。
用户可以使用 ‘modinfo’ 命令并通过查找是否存在 “livepatch” 字段来识别热补丁模块。内核模块加载器也使用此字段来识别热补丁模块。
示例:¶
Modinfo 输出
% modinfo livepatch-meminfo.ko
filename: livepatch-meminfo.ko
livepatch: Y
license: GPL
depends:
vermagic: 4.3.0+ SMP mod_unload
3. Livepatch 重定位节¶
热补丁模块管理其自己的 ELF 重定位节,以便在适当的时候将重定位应用于模块以及内核 (vmlinux)。例如,如果补丁模块修补了当前未加载的驱动程序,则热补丁将在驱动程序加载后将相应的热补丁重定位节应用于该驱动程序。
补丁模块中的每个 “对象”(例如 vmlinux 或模块)可能具有与之关联的多个热补丁重定位节(例如,修补同一对象中的多个函数)。热补丁重定位节和应用重定位的目标节(通常是函数的文本节)之间存在 1-1 的对应关系。热补丁模块也可能没有热补丁重定位节,如示例热补丁模块的情况(请参见 samples/livepatch)。
由于为热补丁模块保留了 ELF 信息(请参见第 5 节),因此可以通过将适当的节索引传递给 apply_relocate_add() 来简单地应用热补丁重定位节,然后 apply_relocate_add() 使用它来访问重定位节并应用重定位。
热补丁重定位节中的 rela 引用的每个符号都是热补丁符号。在热补丁调用 apply_relocate_add() 之前,必须解析这些符号。有关更多信息,请参见第 3 节。
3.1 Livepatch 重定位节格式¶
热补丁重定位节必须使用 SHF_RELA_LIVEPATCH 节标志进行标记。有关定义,请参见 include/uapi/linux/elf.h。模块加载器识别此标志,并且会在补丁模块加载时避免应用这些重定位节。这些节还必须用 SHF_ALLOC 标记,以便模块加载器不会在模块加载时将其丢弃(即,它们将与其他 SHF_ALLOC 节一起复制到内存中)。
热补丁重定位节的名称必须符合以下格式
.klp.rela.objname.section_name
^ ^^ ^ ^ ^
|________||_____| |__________|
[A] [B] [C]
- [A]
重定位节名称以字符串 “.klp.rela.” 为前缀
- [B]
重定位节所属的对象名称(即 “vmlinux” 或模块的名称)紧跟在前缀之后。
- [C]
此重定位节应用到的节的实际名称。
示例:¶
Livepatch 重定位节名称
.klp.rela.ext4.text.ext4_attr_store
.klp.rela.vmlinux.text.cmdline_proc_show
修补 vmlinux 和模块 9p、btrfs、ext4 的补丁模块的 `readelf --sections` 输出
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ snip ]
[29] .klp.rela.9p.text.caches.show RELA 0000000000000000 002d58 0000c0 18 AIo 64 9 8
[30] .klp.rela.btrfs.text.btrfs.feature.attr.show RELA 0000000000000000 002e18 000060 18 AIo 64 11 8
[ snip ]
[34] .klp.rela.ext4.text.ext4.attr.store RELA 0000000000000000 002fd8 0000d8 18 AIo 64 13 8
[35] .klp.rela.ext4.text.ext4.attr.show RELA 0000000000000000 0030b0 000150 18 AIo 64 15 8
[36] .klp.rela.vmlinux.text.cmdline.proc.show RELA 0000000000000000 003200 000018 18 AIo 64 17 8
[37] .klp.rela.vmlinux.text.meminfo.proc.show RELA 0000000000000000 003218 0000f0 18 AIo 64 19 8
[ snip ] ^ ^
| |
[*] [*]
- [*]
热补丁重定位节是 SHT_RELA 节,但具有一些特殊特征。请注意,它们被标记为 SHF_ALLOC (“A”),以便在将模块加载到内存中时不会丢弃它们,并且还带有 SHF_RELA_LIVEPATCH 标志(“o” - 用于特定于操作系统)。
补丁模块的 `readelf --relocs` 输出
Relocation section '.klp.rela.btrfs.text.btrfs_feature_attr_show' at offset 0x2ba0 contains 4 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
000000000000001f 0000005e00000002 R_X86_64_PC32 0000000000000000 .klp.sym.vmlinux.printk,0 - 4
0000000000000028 0000003d0000000b R_X86_64_32S 0000000000000000 .klp.sym.btrfs.btrfs_ktype,0 + 0
0000000000000036 0000003b00000002 R_X86_64_PC32 0000000000000000 .klp.sym.btrfs.can_modify_feature.isra.3,0 - 4
000000000000004c 0000004900000002 R_X86_64_PC32 0000000000000000 .klp.sym.vmlinux.snprintf,0 - 4
[ snip ] ^
|
[*]
- [*]
重定位引用的每个符号都是热补丁符号。
4. Livepatch 符号¶
热补丁符号是热补丁重定位节引用的符号。这些是从已修补对象的函数新版本访问的符号,其地址无法由模块加载器解析(因为它们是局部或未导出的全局符号)。由于模块加载器仅解析导出的符号,并且不是新的已修补函数引用的每个符号都被导出,因此引入了热补丁符号。在无法在加载补丁模块时立即知道符号地址的情况下,也会使用它们。例如,当热补丁修补尚未加载的模块时,就是这种情况。在这种情况下,相关的热补丁符号只需在目标模块加载时进行解析。在任何情况下,对于任何热补丁重定位节,在热补丁可以为该重定位节调用 apply_relocate_add() 之前,必须解析该节引用的所有热补丁符号。
必须使用 SHN_LIVEPATCH 标记热补丁符号,以便模块加载器可以识别并忽略它们。热补丁模块将这些符号保留在其符号表中,并且可以通过 module->symtab 访问该符号表。
4.1 Livepatch 模块的符号表¶
通常,模块符号表的精简副本(仅包含“核心”符号)可通过 module->symtab 获得(请参见 kernel/module/kallsyms.c 中的 layout_symtab())。对于热补丁模块,在模块加载时复制到内存中的符号表必须与编译补丁模块时生成的符号表完全相同。这是因为每个热补丁重定位节中的重定位都使用其符号索引引用它们各自的符号,并且必须保留原始符号索引(以及符号表顺序),以便 apply_relocate_add() 找到正确的符号。
例如,采用来自热补丁模块的这个特定的 rela:
Relocation section '.klp.rela.btrfs.text.btrfs_feature_attr_show' at offset 0x2ba0 contains 4 entries:
Offset Info Type Symbol's Value Symbol's Name + Addend
000000000000001f 0000005e00000002 R_X86_64_PC32 0000000000000000 .klp.sym.vmlinux.printk,0 - 4
This rela refers to the symbol '.klp.sym.vmlinux.printk,0', and the symbol index is encoded
in 'Info'. Here its symbol index is 0x5e, which is 94 in decimal, which refers to the
symbol index 94.
And in this patch module's corresponding symbol table, symbol index 94 refers to that very symbol:
[ snip ]
94: 0000000000000000 0 NOTYPE GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.printk,0
[ snip ]
4.2 Livepatch 符号格式¶
热补丁符号必须将其节索引标记为 SHN_LIVEPATCH,以便模块加载器可以识别它们并且不尝试解析它们。有关实际定义,请参见 include/uapi/linux/elf.h。
Livepatch 符号名称必须符合以下格式
.klp.sym.objname.symbol_name,sympos
^ ^^ ^ ^ ^ ^
|_______||_____| |_________| |
[A] [B] [C] [D]
- [A]
符号名称以字符串“.klp.sym.”为前缀。
- [B]
前缀之后紧跟着符号所属的对象名称(即 “vmlinux” 或模块名称)。
- [C]
符号的实际名称。
- [D]
符号在对象中的位置(根据 kallsyms)。这用于区分同一对象内的重复符号。符号位置以数字表示(0、1、2...)。唯一符号的符号位置为 0。
示例:¶
Livepatch 符号名称
.klp.sym.vmlinux.snprintf,0
.klp.sym.vmlinux.printk,0
.klp.sym.btrfs.btrfs_ktype,0
补丁模块的 `readelf --symbols` 输出
Symbol table '.symtab' contains 127 entries:
Num: Value Size Type Bind Vis Ndx Name
[ snip ]
73: 0000000000000000 0 NOTYPE GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.snprintf,0
74: 0000000000000000 0 NOTYPE GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.capable,0
75: 0000000000000000 0 NOTYPE GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.find_next_bit,0
76: 0000000000000000 0 NOTYPE GLOBAL DEFAULT OS [0xff20] .klp.sym.vmlinux.si_swapinfo,0
[ snip ] ^
|
[*]
- [*]
请注意,这些符号的 'Ndx'(节索引)是 SHN_LIVEPATCH (0xff20)。“OS” 表示特定于操作系统。
5. 符号表和 ELF 节访问¶
可以通过 module->symtab 访问 livepatch 模块的符号表。
由于 apply_relocate_add() 需要访问模块的节头、符号表和重定位节索引,因此会为 livepatch 模块保留 ELF 信息,并且模块加载器可以通过 module->klp_info 访问该信息,这是一个 klp_modinfo
结构。当加载 livepatch 模块时,模块加载器会填充此结构。