Linux ARM64架构 动态替换 altinstructions

这篇具有很好参考价值的文章主要介绍了Linux ARM64架构 动态替换 altinstructions。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

简介

在内核开发中,有时需要对内核代码进行修补,以解决bug、优化性能或引入新功能。替代指令(altinstructions)提供了一种在不修改原始代码的情况下进行修补的方法。它允许开发者在原始指令的位置插入替代指令,以实现所需的功能变更。

内核版本:4.19.90
处理器架构:aarch64

以vfat.ko模块为例:

# readelf -S vfat.ko
There are 35 section headers, starting at offset 0x6c08:

节头:
  [] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  ......
  [10] .altinstructions  PROGBITS         0000000000000000  00003768
       0000000000000078  0000000000000000   A       0     0     1
  [11] .rela.altinstruct RELA             0000000000000000  000037e0
       00000000000001e0  0000000000000018   I      31    10     8
  ......

我接触到的这台 arm64 架构的OS的内核模块没有 .altinstr_replacement 节。

内核源码中说明:

Alternatives with callbacks do not generate replacement instructions.
// linux-4.19.90/arch/arm64/include/asm/alternative.h

typedef void (*alternative_cb_t)(struct alt_instr *alt,
				 __le32 *origptr, __le32 *updptr, int nr_inst);

ARM64架构内核模块加载时会调用该函数:

static void __apply_alternatives(void *alt_region, bool is_module)
{
	......
	alternative_cb_t alt_cb;
	......
	alt_cb(alt, origptr, updptr, nr_inst);
	......
}

因此没有内核模块中 .altinstr_replacement 节。

备注,其它aarch64内核版本的机器可能会有.altinstr_replacement 节,比如:
OS:Kylin Linux Desktop V10 (SP1)
内核版本:5.4.18
处理器架构:aarch64

# readelf -S ip_tables.ko
There are 53 section headers, starting at offset 0xa0c0:

节头:
  [] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
   ......
  [21] .altinstructions  PROGBITS         0000000000000000  00003835
       000000000000006c  0000000000000000   A       0     0     1
  [22] .rela.altinstruct RELA             0000000000000000  000095e8
       00000000000001b0  0000000000000018   I      50    21     8
  [23] .altinstr_replace PROGBITS         0000000000000000  000038a4
       0000000000000024  0000000000000000   A       0     0     4
  ......

一、ALTERNATIVE宏

1.1 ALTERNATIVE宏

/*
 * Usage: asm(ALTERNATIVE(oldinstr, newinstr, feature));
 *
 * Usage: asm(ALTERNATIVE(oldinstr, newinstr, feature, CONFIG_FOO));
 * N.B. If CONFIG_FOO is specified, but not selected, the whole block
 *      will be omitted, including oldinstr.
 */
#define ALTERNATIVE(oldinstr, newinstr, ...)   \
	_ALTERNATIVE_CFG(oldinstr, newinstr, __VA_ARGS__, 1)
/*
 * alternative assembly primitive:
 *
 * If any of these .org directive fail, it means that insn1 and insn2
 * don't have the same length. This used to be written as
 *
 * .if ((664b-663b) != (662b-661b))
 * 	.error "Alternatives instruction length mismatch"
 * .endif
 *
 * but most assemblers die if insn1 or insn2 have a .inst. This should
 * be fixed in a binutils release posterior to 2.25.51.0.2 (anything
 * containing commit 4e4d08cf7399b606 or c1baaddf8861).
 *
 * Alternatives with callbacks do not generate replacement instructions.
 */
#define __ALTERNATIVE_CFG(oldinstr, newinstr, feature, cfg_enabled, cb)	\
	".if "__stringify(cfg_enabled)" == 1\n"				\
	"661:\n\t"							\
	oldinstr "\n"							\
	"662:\n"							\
	".pushsection .altinstructions,\"a\"\n"				\
	ALTINSTR_ENTRY(feature,cb)					\
	".popsection\n"							\
	" .if " __stringify(cb) " == 0\n"				\
	".pushsection .altinstr_replacement, \"a\"\n"			\
	"663:\n\t"							\
	newinstr "\n"							\
	"664:\n\t"							\
	".popsection\n\t"						\
	".org	. - (664b-663b) + (662b-661b)\n\t"			\
	".org	. - (662b-661b) + (664b-663b)\n"			\
	".else\n\t"							\
	"663:\n\t"							\
	"664:\n\t"							\
	".endif\n"							\
	".endif\n"

#define _ALTERNATIVE_CFG(oldinstr, newinstr, feature, cfg, ...)	\
	__ALTERNATIVE_CFG(oldinstr, newinstr, feature, IS_ENABLED(cfg), 0)

这段代码定义了一个用于在特定配置下进行指令替换的宏__ALTERNATIVE_CFG。

__ALTERNATIVE_CFG(oldinstr, newinstr, feature, cfg_enabled, cb):这个宏用于生成指令替换的代码。它接受五个参数:oldinstr是原始指令的字符串表示,newinstr是替代指令的字符串表示,feature是特性位的值,cfg_enabled是一个配置标志,指示是否启用了替代指令,cb是一个回调函数。这个宏根据配置标志决定是否进行指令替换。如果配置启用了替代指令,它会生成一段汇编代码,包括原始指令、.altinstructions节的条目以及替代指令。如果配置禁用了替代指令,它只会生成原始指令。

如果没有cb回调函数,那么会生成.altinstr_replacement节。如果有cb回调函数,那么则不会产生.altinstr_replacement节,通过cb回调函数来进行指令替换。

1.2 ALTINSTR_ENTRY

#define ALTINSTR_ENTRY(feature,cb)					      \
	" .word 661b - .\n"				/* label           */ \
	" .if " __stringify(cb) " == 0\n"				      \
	" .word 663f - .\n"				/* new instruction */ \
	" .else\n"							      \
	" .word " __stringify(cb) "- .\n"		/* callback */	      \
	" .endif\n"							      \
	" .hword " __stringify(feature) "\n"		/* feature bit     */ \
	" .byte 662b-661b\n"				/* source len      */ \
	" .byte 664f-663f\n"				/* replacement len */

这段代码定义了一个宏ALTINSTR_ENTRY,用于生成.altinstructions节中的条目。

ALTINSTR_ENTRY宏接受两个参数:feature和cb。feature表示特性位的值,cb表示回调函数的地址或偏移量。

该宏生成一个.altinstructions节的条目,包括以下内容:
(1).word 661b - .:表示标签 661b 相对于当前位置的偏移量,即原始指令的长度。
(2).if __stringify(cb) == 0:如果回调函数为0,则生成以下内容:
.word 663f - .:表示新指令(替代指令)相对于当前位置的偏移量。
(3).else:如果回调函数不为0,则生成以下内容:
.word __stringify(cb) - .:表示回调函数相对于当前位置的偏移量。
(4).hword __stringify(feature):表示特性位的值。
(5).byte 662b-661b:表示原始指令的长度。
(6).byte 664f-663f:表示替代指令的长度。

替代指令的生成与是否有回调函数有关。

生成的条目如下所示:

struct alt_instr {
	s32 orig_offset;	/* offset to original instruction */
	s32 alt_offset;		/* offset to replacement instruction */
	u16 cpufeature;		/* cpufeature bit set for replacement */
	u8  orig_len;		/* size of original instruction(s) */
	u8  alt_len;		/* size of new instruction(s), <= orig_len */
};

二、altinstructions节

2.1 .altinstructions

.altinstructions 是 Linux 内核中的一个特殊节(section),用于定义指令替换规则。它允许内核在运行时替换特定的汇编指令序列,以提高性能或解决特定的问题。

指令替换的规则包括两个部分:原始指令序列和替换指令序列。内核在执行时会检查原始指令序列是否匹配,如果匹配则使用替换指令序列来取代它。

.altinstructions 节主要用于在 Linux 内核中进行指令替换或修补。它提供了在运行时替换特定指令序列的机制,常用于优化关键代码路径或解决硬件问题。

struct alt_instr {
	s32 orig_offset;	/* offset to original instruction */
	s32 alt_offset;		/* offset to replacement instruction */
	u16 cpufeature;		/* cpufeature bit set for replacement */
	u8  orig_len;		/* size of original instruction(s) */
	u8  alt_len;		/* size of new instruction(s), <= orig_len */
};

(1)orig_offset(s32类型):原始指令的偏移量。指示原始指令在代码段中的位置。
(2)alt_offset(s32类型):替换指令的偏移量。指示替换指令在代码段中的位置。
(3)cpufeature(u16类型):替换指令所需的CPU特性位。这个字段用于在替换指令被应用时检查CPU是否支持相应的特性。
(4)orig_len(u8类型):原始指令的长度。表示原始指令所占用的字节数。
(5)alt_len(u8类型):新指令的长度。表示替换指令所占用的字节数。这个值必须小于等于 orig_len,以确保替换后的指令不会超出原始指令的范围。

.altinstructions 节中保存了 struct alt_instr 结构体数组。数组中的每一个元素代表了一条替换指令记录,给出了原始指令的位置、长度和用于修补原始指令的新指令的位置、长度。

这个结构体用于在 Linux 内核的 .altinstructions 节中定义指令替换规则。每个结构体实例表示一条指令的替换规则,其中包含原始指令和替换指令的相关信息。通过使用这些结构体,内核可以在运行时根据需要进行指令替换,以优化性能或解决特定的硬件问题。

# readelf -x 10 vfat.ko.altinstructions”节的十六进制输出:
 NOTE: This section has relocations against it, but these have NOT been applied to this dump.
  0x00000000 00000000 00000000 05000c0c 00000000 ................
  0x00000010 00000000 05000c0c 00000000 00000000 ................
  0x00000020 05000c0c 00000000 00000000 05000c0c ................
  0x00000030 00000000 00000000 05000c0c 00000000 ................
  0x00000040 00000000 05000c0c 00000000 00000000 ................
  0x00000050 05000c0c 00000000 00000000 05000c0c ................
  0x00000060 00000000 00000000 05000c0c 00000000 ................
  0x00000070 00000000 05000c0c                   ........
struct alt_instr {
	s32 orig_offset;	/* offset to original instruction */
	s32 alt_offset;		/* offset to replacement instruction */
	u16 cpufeature;		/* cpufeature bit set for replacement */
	u8  orig_len;		/* size of original instruction(s) */
	u8  alt_len;		/* size of new instruction(s), <= orig_len */
};
sizeof(struct alt_instr) = 12

“.altinstructions”节 都是存放struct alt_instr结构体数据。

# readelf -S vfat.ko
There are 35 section headers, starting at offset 0x6c08:

节头:
  [] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  ......
  [10] .altinstructions  PROGBITS         0000000000000000  00003768
       0000000000000078  0000000000000000   A       0     0     1
120(0x78) / 12 = 10

因此这个节存放了 10 个struct alt_instr结构体。

一个struct alt_instr结构体:

00000000 00000000 05000c0c

orig_offset和alt_offset都是等于0,会在.rela.altinstructions重定位节重新进行计算偏移。
s32 orig_offset = alt_offset = 0x0

u16 = cpufeature = 0x05
(0x0500,由于aarch64架构是小端处理器,对于0x0500,低地址在低字节,高地址在高字节,0x05在低地址,所以在低字节,所以对于 u16 cpufeature = 0x0005 = 0x05。
u16是两个字节,大于一个字节,因此要考虑字节序问题。
字节序(Byte Order)指的是在存储和表示多字节数据类型(如整数和浮点数)时,字节的排列顺序。

u8 = orig_len = alt_len = 0x0c
(orig_len 和 alt_len 都是一个字节,没有低字节和高字节一说,大小端是以字节为单位的)

2.2 .rela.altinstructions

.rela.altinstructions节是一个重定位节,用于存储.altinstructions节中数据结构的重定位信息。

在Linux ELF(Executable and Linkable Format)文件中,重定位节(Relocation Section)是用于存储链接器在链接过程中需要进行地址修正的信息。重定位节包含了需要修改的符号引用和相关的重定位类型。

重定位节中都是未定义的符号,即不是本模块定义的符号,因此这些符号的地址在内核模块加载时需要进行重新处理。

重定位节的名称通常以 “.rel” 或 “.rela” 开头,后面跟随符号表中相关的节名称。例如,“.rel.text” 表示与代码段(.text)相关的重定位信息。

在链接过程中,链接器会根据符号引用和重定位类型,将重定位节中的每个重定位项应用于对应的位置,修正地址或符号引用。

重定位节的结构和格式可以因不同的体系结构和文件格式而有所不同,但通常包含以下信息:
(1)Offset(偏移量):指定需要修正的位置在节中的偏移量,即指定了需要修改重定位项的位置。
(2)Symbol Index(符号索引):指定需要修正的符号引用在符号表中的索引。
(3)Type(类型):指定重定位的类型,如绝对重定位、PC相对重定位等。
(4)Addend(增量):一些重定位类型需要额外的增量值,用于计算最终的修正值。

/* Relocation table entry with addend (in section of type SHT_RELA).  */

typedef struct
{
  Elf64_Addr	r_offset;		/* Address */
  Elf64_Xword	r_info;			/* Relocation type and symbol index */
  Elf64_Sxword	r_addend;		/* Addend */
} Elf64_Rela;
sizeof(Elf64_Rela) = 24
# readelf -S vfat.ko
There are 35 section headers, starting at offset 0x6c08:

节头:
  [] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  ......
  [11] .rela.altinstruct RELA             0000000000000000  000037e0
       00000000000001e0  0000000000000018   I      31    10     8
  ......
480(0x1e0) / 24 = 20

计算得到该重定位节中有20个Elf64_Rela结构体数据。

# readelf -x 11 vfat.ko.rela.altinstructions”节的十六进制输出:
  0x00000000 00000000 00000000 05010000 02000000 ................
  0x00000010 e0040000 00000000 04000000 00000000 ................
  0x00000020 05010000 02000000 28210000 00000000 ........(!......
  0x00000030 0c000000 00000000 05010000 02000000 ................
  0x00000040 5c070000 00000000 10000000 00000000 \...............
  0x00000050 05010000 02000000 34210000 00000000 ........4!......
  0x00000060 18000000 00000000 05010000 02000000 ................
  0x00000070 54080000 00000000 1c000000 00000000 T...............
  0x00000080 05010000 02000000 40210000 00000000 ........@!......
  0x00000090 24000000 00000000 05010000 02000000 $...............
  0x000000a0 24090000 00000000 28000000 00000000 $.......(.......
  0x000000b0 05010000 02000000 4c210000 00000000 ........L!......
  0x000000c0 30000000 00000000 05010000 02000000 0...............
  0x000000d0 f41a0000 00000000 34000000 00000000 ........4.......
  0x000000e0 05010000 02000000 58210000 00000000 ........X!......
  0x000000f0 3c000000 00000000 05010000 02000000 <...............
  0x00000100 b41c0000 00000000 40000000 00000000 ........@.......
  0x00000110 05010000 02000000 64210000 00000000 ........d!......
  0x00000120 48000000 00000000 05010000 02000000 H...............
  0x00000130 e01e0000 00000000 4c000000 00000000 ........L.......
  0x00000140 05010000 02000000 70210000 00000000 ........p!......
  0x00000150 54000000 00000000 05010000 02000000 T...............
  0x00000160 341f0000 00000000 58000000 00000000 4.......X.......
  0x00000170 05010000 02000000 7c210000 00000000 ........|!......
  0x00000180 60000000 00000000 05010000 02000000 `...............
  0x00000190 28200000 00000000 64000000 00000000 ( ......d.......
  0x000001a0 05010000 02000000 88210000 00000000 .........!......
  0x000001b0 6c000000 00000000 05010000 02000000 l...............
  0x000001c0 84200000 00000000 70000000 00000000 . ......p.......
  0x000001d0 05010000 02000000 94210000 00000000 .........!......

可以看到该重定位节的每个结构体的 r_info 成员值都是:

0x02 00000105

其中 Relocation type = 0x105 = 261 即:

#define R_AARCH64_PREL32	261	/* PC-relative 32-bit.	*/

其中 symbol index = 0x02 ,即符号节 2 ,text节:

# readelf -S vfat.ko
  ......
  [ 2] .text             PROGBITS         0000000000000000  00000068
       00000000000021a0  0000000000000000  AX       0     0     8
  ......
/* AArch64 relocs.  */

/* LP64 AArch64 relocs.  */
#define R_AARCH64_ABS64         257	/* Direct 64 bit. */
#define R_AARCH64_ABS32         258	/* Direct 32 bit.  */
#define R_AARCH64_ABS16		259	/* Direct 16-bit.  */
#define R_AARCH64_PREL64	260	/* PC-relative 64-bit.	*/
#define R_AARCH64_PREL32	261	/* PC-relative 32-bit.	*/
#define R_AARCH64_PREL16	262	/* PC-relative 16-bit.	*/
......
# readelf -r vfat.ko | grep -A 25 .rela.altinstructions
重定位节 '.rela.altinstructions' at offset 0x37e0 contains 20 entries:
  偏移量          信息           类型           符号值        符号名称 + 加数
000000000000  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 4e0
000000000004  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2128
00000000000c  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 75c
000000000010  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2134
000000000018  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 854
00000000001c  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2140
000000000024  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 924
000000000028  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 214c
000000000030  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 1af4
000000000034  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2158
00000000003c  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 1cb4
000000000040  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2164
000000000048  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 1ee0
00000000004c  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2170
000000000054  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 1f34
000000000058  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 217c
000000000060  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2028
000000000064  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2188
00000000006c  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2084
000000000070  000200000105 R_AARCH64_PREL32  0000000000000000 .text + 2194

重定位类型 R_AARCH64_PREL32 是 ARM64 架构中的一种重定位类型,用于在链接时解析相对地址(PC-relative addressing)的32位偏移量。

ARM64 架构的指令集使用了相对地址寻址方式,即指令中使用相对于当前指令位置(PC)的偏移量来寻址目标地址。当需要在链接时解析这样的相对地址时,就需要使用重定位类型 R_AARCH64_PREL32。

这个重定位类型的含义是,将一个32位的相对偏移量添加到当前指令的地址上,从而计算出目标地址。这个重定位类型适用于绝大多数的相对地址寻址场景,例如函数调用、分支跳转等。

在链接过程中,链接器会扫描目标文件中的重定位表,找到所有使用 R_AARCH64_PREL32 的重定位项。然后,链接器会根据目标文件中的符号表信息和重定位项中的偏移量信息,计算出正确的目标地址,并将其填充到可执行文件或共享库中的相应位置。

通过使用 R_AARCH64_PREL32 重定位类型,ARM64 架构可以实现相对地址寻址的链接过程,确保指令的相对地址能够正确解析,使得程序能够在执行时正确地跳转到目标地址。

Linux ARM64架构 动态替换 altinstructions,系统安全,ARM 64,linux,c语言,系统安全

其中.text + 4e0、.text + 75、.text + 854等都是对应的 BL 函数跳转指令:
Linux ARM64架构 动态替换 altinstructions,系统安全,ARM 64,linux,c语言,系统安全
Linux ARM64架构 动态替换 altinstructions,系统安全,ARM 64,linux,c语言,系统安全
Linux ARM64架构 动态替换 altinstructions,系统安全,ARM 64,linux,c语言,系统安全
这里的__ll_sc___cmpxchg_case_acq_4函数和__ll_sc___cmpxchg_case_mb_8不属于vfat.ko内核模块中的函数,因此需要重定位来获取对应函数的位置。

# cat /proc/kallsyms | grep '\<__ll_sc___cmpxchg_case_acq_4\>'
ffff3c3a3fa01bf0 T __ll_sc___cmpxchg_case_acq_4
# cat /proc/kallsyms | grep '\<__ll_sc___cmpxchg_case_mb_8\>'
ffff3c3a3fa01d28 T __ll_sc___cmpxchg_case_mb_8

可以看到这两个函数都属于内核镜像中定义的函数。

aarch64一条指令四个字节,bl是函数调用指令,比如一个内核模块调用内核镜像或者其他模块的函数:
vfat.ko内核模块中函数调用内核的函数kmem_cache_alloc_trace:

94000000        bl      0 <kmem_cache_alloc_trace>
941001 0100

Linux ARM64架构 动态替换 altinstructions,系统安全,ARM 64,linux,c语言,系统安全

上面提到.altinstructions有10 个struct alt_instr结构体,.rela.altinstructions节中有20个Elf64_Rela结构体数据。

其中.text + 2128、.text + 2134、.text + 2140等对应的指令如下所示:
Linux ARM64架构 动态替换 altinstructions,系统安全,ARM 64,linux,c语言,系统安全
Linux ARM64架构 动态替换 altinstructions,系统安全,ARM 64,linux,c语言,系统安全
这10个重定位项都是在 .text 代码段的末尾,对应的指令都是:

MOV             X30, X1

对于 x30 寄存器:

在ARM64体系结构中,寄存器 x30 是通用寄存器之一,也称为"General Purpose Register"。ARM64体系结构共有31个通用寄存器,编号从x0到x30。

寄存器 x30 在ARM64体系结构中有一个特殊的角色,它被称为"链接寄存器"(Link Register),也经常以 “lr” 的缩写表示。链接寄存器用于存储函数的返回地址,在函数调用过程中起到重要的作用。

当一个函数被调用时,当前函数的返回地址(即调用该函数的指令的下一条指令的地址)会被保存在链接寄存器 x30 中。函数执行完毕后,通过将链接寄存器中的返回地址装载到程序计数器(PC)中,控制流程可以返回到调用函数的位置。

链接寄存器 x30 还可以在函数中用作通用寄存器,存储临时数据、地址计算和数据传输等。但需要注意的是,一旦在函数中使用链接寄存器存储其他数据,必须在函数返回之前将其恢复为正确的返回地址,以确保函数返回到正确的位置。

因此我们可以知道 .altinstructions 节中有10 个struct alt_instr结构体,也就是10处指令要替换,且都是BL函数调用替换,因此会有相应的 RET 函数返回,因此.rela.altinstructions节中有20个Elf64_Rela结构体数据。每一个 .altinstructions 节中的struct alt_instr结构体对应一个 BL和一个RET。

三、内核模块重定位源码分析

3.1 module_finalize

加载内核模块的阶段中,在module_finalize函数会指令替换:

// linux-4.19.90/kernel/module.c

SYSCALL_DEFINE3(init_module......)
	-->load_module()
		-->post_relocation()
			   /* Arch-specific module finalizing. */
			-->module_finalize()

其中module_finalize是一个与体系架构有关的函数,这里我们主要关注 aach64位架构:

typedef struct elf64_shdr {
  Elf64_Word sh_name;		/* Section name, index in string tbl */
  Elf64_Word sh_type;		/* Type of section */
  Elf64_Xword sh_flags;		/* Miscellaneous section attributes */
  Elf64_Addr sh_addr;		/* Section virtual addr at execution */
  Elf64_Off sh_offset;		/* Section file offset */
  Elf64_Xword sh_size;		/* Size of section in bytes */
  Elf64_Word sh_link;		/* Index of another section */
  Elf64_Word sh_info;		/* Additional section information */
  Elf64_Xword sh_addralign;	/* Section alignment */
  Elf64_Xword sh_entsize;	/* Entry size if section holds table */
} Elf64_Shdr;
// linux-4.19.90/arch/arm64/kernel/module.c

int module_finalize(const Elf_Ehdr *hdr,
		    const Elf_Shdr *sechdrs,
		    struct module *me)
{
	const Elf_Shdr *s, *se;
	const char *secstrs = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;

	for (s = sechdrs, se = sechdrs + hdr->e_shnum; s < se; s++) {
		if (strcmp(".altinstructions", secstrs + s->sh_name) == 0)
			apply_alternatives_module((void *)s->sh_addr, s->sh_size);
	}

	return 0;
}

3.2 __apply_alternatives

// linux-4.19.90/arch/arm64/kernel/alternative.c

void apply_alternatives_module(void *start, size_t length)
{
	struct alt_region region = {
		.begin	= start,
		.end	= start + length,
	};

	__apply_alternatives(&region, true);
}
struct alt_instr {
	s32 orig_offset;	/* offset to original instruction */
	s32 alt_offset;		/* offset to replacement instruction */
	u16 cpufeature;		/* cpufeature bit set for replacement */
	u8  orig_len;		/* size of original instruction(s) */
	u8  alt_len;		/* size of new instruction(s), <= orig_len */
};

struct alt_region {
	struct alt_instr *begin;
	struct alt_instr *end;
};

//回调函数,用来进行指令替换
typedef void (*alternative_cb_t)(struct alt_instr *alt,
				 __le32 *origptr, __le32 *updptr, int nr_inst);

#define __ALT_PTR(a,f)		((void *)&(a)->f + (a)->f)
//该宏来获取原始指令的地址
#define ALT_ORIG_PTR(a)		__ALT_PTR(a, orig_offset)
//该宏来获取替换指令的地址
#define ALT_REPL_PTR(a)		__ALT_PTR(a, alt_offset)

static void __apply_alternatives(void *alt_region, bool is_module)
{
	struct alt_instr *alt;
	struct alt_region *region = alt_region;
	__le32 *origptr, *updptr;
	alternative_cb_t alt_cb;

	for (alt = region->begin; alt < region->end; alt++) {
		int nr_inst;

		/* Use ARM64_CB_PATCH as an unconditional patch */
		if (alt->cpufeature < ARM64_CB_PATCH &&
		    !cpus_have_cap(alt->cpufeature))
			continue;

		if (alt->cpufeature == ARM64_CB_PATCH)
			BUG_ON(alt->alt_len != 0);
		else
			BUG_ON(alt->alt_len != alt->orig_len);

		pr_info_once("patching kernel code\n");

		//获取原始指令的地址
		origptr = ALT_ORIG_PTR(alt);
		//更新指令的指针的地址指向原始指令的地址
		updptr = is_module ? origptr : lm_alias(origptr);
		//要替换指令的数目
		nr_inst = alt->orig_len / AARCH64_INSN_SIZE;

		//根据替代指令的 cpufeature 值,选择相应的回调函数 alt_cb
		if (alt->cpufeature < ARM64_CB_PATCH)
			alt_cb = patch_alternative;
		else
			alt_cb  = ALT_REPL_PTR(alt);
			
		//调用相应的回调函数进行指令替换
		alt_cb(alt, origptr, updptr, nr_inst);
	
		......
	}
	
	......

}

函数的参数 alt_region 是一个指向替代指令区域的指针,is_module 是一个布尔值,表示是否为模块代码,这里传入的是 true ,表示是模块代码。

函数通过循环遍历 alt_region (.altinstructions)中的每个替代指令,每个替代指令存储在结构体 alt_instr 中,包含原始指令和替代指令的信息。

函数首先检查替代指令的 cpufeature 字段。如果该字段小于 ARM64_CB_PATCH 并且当前 CPU 不支持该特性,则跳过该替代指令。

如果替代指令的 cpufeature 等于 ARM64_CB_PATCH,则需要确保 alt_len(替代指令长度)为零。否则,需要确保 alt_len 和 orig_len(原始指令长度)相等。如果上述条件不满足,则会触发错误(使用 BUG_ON)。

在确定替代指令有效后,函数使用 pr_info_once 记录一条消息,指示正在对内核代码进行修补。

根据 is_module 的值,函数设置 origptr 和 updptr 变量。如果是模块代码,则 origptr 指向原始指令,否则通过 lm_alias 函数获取一个别名。这里是模块代码,origptr 指向原始指令。

通过将替代指令的长度 alt->orig_len 除以 ARM64 指令的大小 AARCH64_INSN_SIZE,计算出指令的数量 nr_inst。

/* A64 instructions are always 32 bits. */
#define	AARCH64_INSN_SIZE		4

根据替代指令的 cpufeature 值,选择相应的回调函数 alt_cb。如果 cpufeature 小于 ARM64_CB_PATCH,则选择 patch_alternative 函数作为回调函数;否则使用 ALT_REPL_PTR(alt) 来确定回调函数。

调用回调函数 alt_cb,传递替代指令、原始指令指针(origptr)、更新指令指针(updptr)和指令数量(nr_inst),进行指令替换操作。这是实际应用替代指令的地方。

函数__apply_alternatives的作用是根据提供的替代指令区域,对内核代码进行指令替换操作。它遍历替代指令区域中的每个替代指令,根据CPU的支持情况和替代指令的特性进行判断,然后调用相应的回调函数进行指令替换。从而修复或改进内核的行为。

3.3 patch_alternative

cpufeature小于ARM64_CB_PATCH,则使用patch_alternative作为回调函数:

#define ARM64_NCAPS				35
#define ARM64_CB_PATCH ARM64_NCAPS

static void __apply_alternatives(void *alt_region, bool is_module)
{
		......
		//根据替代指令的 cpufeature 值,选择相应的回调函数 alt_cb
		if (alt->cpufeature < ARM64_CB_PATCH)
			alt_cb = patch_alternative;
		else
			alt_cb  = ALT_REPL_PTR(alt);
		......
}
static void patch_alternative(struct alt_instr *alt,
			      __le32 *origptr, __le32 *updptr, int nr_inst)
{
	__le32 *replptr;
	int i;

	replptr = ALT_REPL_PTR(alt);
	for (i = 0; i < nr_inst; i++) {
		u32 insn;

		insn = get_alt_insn(alt, origptr + i, replptr + i);
		updptr[i] = cpu_to_le32(insn);
	}
}

用于实际执行指令替换操作。它接受alt参数,表示替代指令的结构体,origptr参数表示原始指令的指针,updptr参数表示更新后指令的指针,nr_inst参数表示指令数量。

该函数的功能如下:

(1)通过ALT_REPL_PTR(alt)获取替代指令的指针replptr。

(2)使用循环遍历每个指令,循环次数为nr_inst。

(3)在每次迭代中,调用get_alt_insn函数,传入alt、origptr + i和replptr + i作为参数,获取替代指令的值。

(4)使用cpu_to_le32将替代指令转换为小端字节序,并将转换后的值存储到updptr[i]中,完成指令替换。

函数patch_alternative的作用是根据提供的替代指令结构体和原始指令,将替代指令的值逐个替换到更新后的指令中。它通过循环遍历每个指令,并调用get_alt_insn函数获取替代指令的值,然后将转换后的值存储到更新后指令的对应位置。这样就完成了指令的替换操作。

3.4 get_alt_insn函数

static u32 get_alt_insn(struct alt_instr *alt, __le32 *insnptr, __le32 *altinsnptr)
{
	u32 insn;

	insn = le32_to_cpu(*altinsnptr);

	if (aarch64_insn_is_branch_imm(insn)) {
		s32 offset = aarch64_get_branch_offset(insn);
		unsigned long target;

		target = (unsigned long)altinsnptr + offset;

		/*
		 * If we're branching inside the alternate sequence,
		 * do not rewrite the instruction, as it is already
		 * correct. Otherwise, generate the new instruction.
		 */
		if (branch_insn_requires_update(alt, target)) {
			offset = target - (unsigned long)insnptr;
			insn = aarch64_set_branch_offset(insn, offset);
		}
	} else if (aarch64_insn_is_adrp(insn)) {
		s32 orig_offset, new_offset;
		unsigned long target;

		/*
		 * If we're replacing an adrp instruction, which uses PC-relative
		 * immediate addressing, adjust the offset to reflect the new
		 * PC. adrp operates on 4K aligned addresses.
		 */
		orig_offset  = aarch64_insn_adrp_get_offset(insn);
		target = align_down(altinsnptr, SZ_4K) + orig_offset;
		new_offset = target - align_down(insnptr, SZ_4K);
		insn = aarch64_insn_adrp_set_offset(insn, new_offset);
	} else if (aarch64_insn_uses_literal(insn)) {
		/*
		 * Disallow patching unhandled instructions using PC relative
		 * literal addresses
		 */
		BUG();
	}

	return insn;
}

get_alt_insn的函数,用于获取替代指令的值。它接受alt参数,表示替代指令的结构体,insnptr参数表示原始指令的指针,altinsnptr参数表示替代指令的指针。

该函数的功能如下:
(1)将altinsnptr指向的替代指令的值转换为主机字节序,并将结果存储到局部变量insn中。

(2)如果替代指令是一个立即数分支指令(branch_imm),则执行以下操作:
获取分支偏移量(offset)。
根据altinsnptr和偏移量计算目标地址(target)。
检查目标地址是否在替代指令序列内部,如果是,则不需要修改指令,返回原始指令的值。
否则,计算新的偏移量(offset)并使用aarch64_set_branch_offset函数将新的偏移量设置到指令中。

(3)如果替代指令是一个ADRP指令,则执行以下操作:
获取原始偏移量(orig_offset)。
根据altinsnptr和原始偏移量计算目标地址(target),确保目标地址按4K对齐。
根据insnptr和目标地址计算新的偏移量(new_offset)。
使用aarch64_insn_adrp_set_offset函数将新的偏移量设置到指令中。

(4)如果替代指令使用字面值(literal),则抛出一个错误(BUG),表示不支持对使用PC相对字面值地址的指令进行修补。

(5)返回经过处理的指令值。

函数get_alt_insn的作用是根据提供的替代指令结构体和指令指针,获取替代指令的值,并在必要时对指令进行修改。它根据指令类型执行不同的操作,包括处理分支指令的目标地址、调整ADRP指令的偏移量等。最后,它返回经过处理的指令值供后续使用。

参考资料

Linux 4.19.90文章来源地址https://www.toymoban.com/news/detail-634506.html

到了这里,关于Linux ARM64架构 动态替换 altinstructions的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux系统查看版本、位数(32位或64位)、架构(arm或amd)的命令

    这里是Ubuntu linux系统; 输入命令: 显示: 输入命令: 显示: 输入命令: 显示: 补充:amd (intelx86架构)和arm版本区别。 x86和arm架构定位不同: arm基于精简指令(RISC),本身定位于嵌入式平台,简化了硬件逻辑的设计,减少了晶体管,从而降低功耗,流水线等控制并不复杂

    2024年02月16日
    浏览(39)
  • Linux conan+cmake管理的项目如何进行多架构编译(x86_64及交叉编译arm64)

    Conan 和 CMake 是两个非常流行的跨平台开发工具,它们可以让开发者轻松管理依赖和构建项目,支持多种操作系统和架构。下面是一些关于 conan 和 cmake 的介绍: Conan Conan 是一个用于管理 C++ 依赖项的开源工具。它可以从公共或私有存储库中自动下载和安装依赖项。Conan 可以轻

    2024年02月09日
    浏览(35)
  • Linux上x86_64架构的动态链接器 ld-linux-x86-64.so.2

    /lib64/ld-linux-x86-64.so.2 是Linux操作系统上x86_64架构的动态链接器(也称为动态链接编辑器)。它负责加载和链接在运行时(即程序启动时或之后)被引用的动态库。现在,我们来深入了解其作用和重要性: 动态链接器的作用 : 当运行一个可执行程序时,该程序可能依赖于多个动

    2024年02月02日
    浏览(36)
  • 注意避坑:centos7官方版镜像不支持arm架构(docker请求的映像的平台(linux/aamd64)与检测到的主机平台(linux/alm64/v8)不匹配)fauria/vsftpd

    注意是centos7 docker官方版镜像不支持arm架构(FROM centos:7),不是centos7不支持arm 今天基于fauria/vsftpd在我们的arm盒子上做了个docker镜像,但是用镜像run容器的时候提示: 翻译就是: 警告:请求的映像的平台(linux/aamd64)与检测到的主机平台(linux/alm64/v8)不匹配,并且没有请求

    2024年02月08日
    浏览(35)
  • x86架构ubuntu 搭建arm64交叉编译环境及QT编译arm64架构工程

    背景:由于最近项目需要做国产系统适配,很多软件需要重新编译以适配不同架构CPU。 环境: 1、主机win10 64bit   vmware虚拟主机ubuntu1804 64bit 2、vmware虚拟主机已经安装了qt5.14.2及qt_create4.11.1 一、C/C++程序交叉编译 1、交叉编译环境搭建 ①选定编译工具aarch64-linux-gnu ②安装交叉

    2024年02月09日
    浏览(51)
  • Linux 下杀毒软件 clamav-1.0.0.linux.x86_64.rpm 离线安装及测试CentOS7,CentOS6.8,KylinV10 arm架构

    本文主要记录在centos7以及centos6.8版本上安装记录!废话不多说,直接开始操作!后添加KylinV10 arm架构安装记录 1、下载rpm包 https://www.clamav.net/downloads 官网地址 2、上传安装包到服务器并安装 3、创建目录 4、新建日志文件 5、编辑配置conf文件 6、配置库文件 7、创建clamav用户并

    2024年02月13日
    浏览(47)
  • ARM A64架构TrustZone学习

    本文翻译自文档 Learn the architecture - TrustZone for AArch64 原文链接:https://developer.arm.com/documentation/102418/0101/?lang=en 在本指南中,我们介绍了 TrustZone。TrustZone 通过内置于 CPU 中的硬件强制隔离提供了一种高效的、系统范围的安全方法。 我们涵盖了 TrustZone 添加到处理器架构中

    2024年02月06日
    浏览(35)
  • 交叉编译ARM64架构electron详解

    本文主要参考Electron官方文档中 构建说明 和 构建步骤(Linux) 在amd64环境内构建arm64的electron包。 如果是arm64环境请查看文章arm64架构编译electron长征路 操作系统版本:统信1060 操作系统架构:amd64 内存:32G 如下图: electron版本:v25.9.8 chromium版本:114.0.5735.289 由于llvm编译需要

    2024年02月02日
    浏览(30)
  • arm64架构编译electron长征路

    2024年01月21日
    浏览(47)
  • arm和x86架构服务器拉取arm64架构的docker镜像

    dockerhub提供的镜像部分支持arm64架构 Docker arm架构服务器拉取docker镜像,默认是arm架构  查看docker镜像的架构 x86平台拉取arm平台的docker镜像 对docker版本有限制 docker运行其他平台容器,需要使用--platform参数指定容器 docker19.03.9及以上版本才支持--platform参数 查看是否开启experi

    2024年01月20日
    浏览(54)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包