linux 内核模块重定位过程 简单的示例解析

示例

以test_module1.ko为例,进行说明。

.rela.text节内容

readelf -r test_module1.ko | head -n 10 得到如下内容,如下内容为 .rela.text 节的内容。

Relocation section '.rela.text' at offset 0x21ab0 contains 12 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000005  002a00000004 R_X86_64_PLT32    0000000000000000 __fentry__ - 4
000000000011  002e0000000b R_X86_64_32S      0000000000000000 test_weakref + 0
00000000001d  003800000004 R_X86_64_PLT32    0000000000000000 msleep - 4
000000000026  000200000002 R_X86_64_PC32     0000000000000000 .text.unlikely + 17
00000000002b  002e00000004 R_X86_64_PLT32    0000000000000000 test_weakref - 4
000000000030  002900000004 R_X86_64_PLT32    0000000000000000 kthread_should_stop - 4
000000000045  002a00000004 R_X86_64_PLT32    0000000000000000 __fentry__ - 4

.text节内容

此内容为反汇编 test_module1.ko 所得到的部分反汇编文件的 .text 节的部分内容。

Disassembly of section .text:

0000000000000000 <debug_1>:
{
        printk("weakref\n");
}
#endif
int debug_1(void *arg)
{
   0:   f3 0f 1e fa             endbr64
   4:   e8 00 00 00 00          callq  9 <debug_1+0x9>
   9:   55                      push   %rbp
   a:   48 89 e5                mov    %rsp,%rbp
   d:   53                      push   %rbx
        while(!kthread_should_stop()){

                msleep(5000);
                if(test_weakref)
   e:   48 c7 c3 00 00 00 00    mov    $0x0,%rbx
        while(!kthread_should_stop()){
  15:   eb 18                   jmp    2f <debug_1+0x2f>
                msleep(5000);
  17:   bf 88 13 00 00          mov    $0x1388,%edi
  1c:   e8 00 00 00 00          callq  21 <debug_1+0x21>
                if(test_weakref)
  21:   48 85 db                test   %rbx,%rbx
  24:   0f 84 00 00 00 00       je     2a <debug_1+0x2a>
                        test_weakref();
  2a:   e8 00 00 00 00          callq  2f <debug_1+0x2f>
}

节头信息

此内容为 readelf -S test_module1.ko 获得的各节的节头信息:

There are 40 section headers, starting at offset 0x3c788:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       00000000000000b0  0000000000000000  AX       0     0     16
  [ 2] .rela.text        RELA             0000000000000000  00021ab0
       0000000000000120  0000000000000018   I      37     1     8
......
  [37] .symtab           SYMTAB           0000000000000000  00021368
       0000000000000558  0000000000000018          38    36     8
  [38] .strtab           STRTAB           0000000000000000  000218c0
       00000000000001eb  0000000000000000           0     0     1
  [39] .shstrtab         STRTAB           0000000000000000  0003c600
       0000000000000181  0000000000000000           0     0     1


符号表:

此内容为 readelf -s test_module1.ko 获得的符号表

Symbol table '.symtab' contains 57 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
......
    54: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND unregister_module_notifie
    55: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND register_module_notifier
    56: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND msleep

解析

流程解析从 apply_relocations 函数开始。
模块函数调用关系:
init_module
->load_module
–>apply_relocations

static int apply_relocations(struct module *mod, const struct load_info *info)
{
	unsigned int i;
	int err = 0;

	/* Now do relocations. */
	for (i = 1; i < info->hdr->e_shnum; i++) {
		unsigned int infosec = info->sechdrs[i].sh_info;

		/* Not a valid relocation section? */
		if (infosec >= info->hdr->e_shnum)
			continue;

		/* Don't bother with non-allocated sections */
		if (!(info->sechdrs[infosec].sh_flags & SHF_ALLOC))
			continue;

		/* Livepatch relocation sections are applied by livepatch */
		if (info->sechdrs[i].sh_flags & SHF_RELA_LIVEPATCH)
			continue;

		if (info->sechdrs[i].sh_type == SHT_REL)
			err = apply_relocate(info->sechdrs, info->strtab,
					     info->index.sym, i, mod);
		else if (info->sechdrs[i].sh_type == SHT_RELA)
			err = apply_relocate_add(info->sechdrs, info->strtab,
						 info->index.sym, i, mod);
		if (err < 0)
			break;
	}
	return err;
}

apply_relocations 函数中的第第二次循环(i=2)为例,即处理的是 .rela.text 节。

在此基础上,apply_relocations 函数执行流程如下:

  • 从节头信息列表中可知,当前节的 sh_info 为 1 ,则循环中变量 infosec 的值为 1 。
  • info->sechdrs[infosec].sh_flags ,也就是 .rela.text 段中的 flags ,其值包含 SHF_ALLOC ,所以,程序继续向后执行。
  • info->sechdrs[i].sh_type 的值为 RELA 所以,进入到 apply_relocate_add 函数中。
  • 传入参数 :
    • symindex 为 37
    • relsec 为 2
int apply_relocate_add(Elf64_Shdr *sechdrs,
		   const char *strtab,
		   unsigned int symindex,
		   unsigned int relsec,
		   struct module *me)
{
	unsigned int i;
	Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr;
	Elf64_Sym *sym;
	void *loc;
	u64 val;

	for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
		/* This is where to make the change */
		loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr
			+ rel[i].r_offset;

		/* This is the symbol it is referring to.  Note that all
		   undefined symbols have been resolved.  */
		sym = (Elf64_Sym *)sechdrs[symindex].sh_addr
			+ ELF64_R_SYM(rel[i].r_info);

		DEBUGP("type %d st_value %Lx r_addend %Lx loc %Lx\n",
		       (int)ELF64_R_TYPE(rel[i].r_info),
		       sym->st_value, rel[i].r_addend, (u64)loc);

		val = sym->st_value + rel[i].r_addend;

		switch (ELF64_R_TYPE(rel[i].r_info)) {
		case R_X86_64_32S:
			if (*(s32 *)loc != 0)
				goto invalid_relocation;
			*(s32 *)loc = val;
			if ((s64)val != *(s32 *)loc)
				goto overflow;
			break;
		case R_X86_64_PC32:
		case R_X86_64_PLT32:
			if (*(u32 *)loc != 0)
				goto invalid_relocation;
			val -= (u64)loc;
			*(u32 *)loc = val;
			break;
		default:
			pr_err("%s: Unknown rela relocation: %llu\n",
			       me->name, ELF64_R_TYPE(rel[i].r_info));
			return -ENOEXEC;
		}
	}
	return 0;


}

假设,.text 段起始地址为 0x7000000.rela.text 段起始地址为 0x8000000 , .symtab 段的起始地址为 0x9000000
在次基础上,且以第三次循环(i=2)为例, apply_relocate_add 函数执行流程如下:

  • loc 变量的值。
    • 从节头信息列表中可知,sechdrs[relsec].sh_info ,也就是 .rela.textsh_info 的值为 1.
    • 则,sechdrs[sechdrs[relsec].sh_info].sh_addr 的值为 .text 节的起始地址,也就是 0x7000000 。其在 rewrite_section_headers 函数中完成重定位,位绝对地址,而非相对地址。
    • rel[i].rela.text 节的第三个条目,也就是 00000000001d 003800000004 R_X86_64_PLT32 0000000000000000 msleep - 4
    • rel[i].r_offset 的值为 00000000001d
    • 将该偏移值对比反汇编文件中的部分可知, 1c 地址行处为跳转指令 callq 。整个跳转指令包含两部分,条钻指令和目的地址。而 1d 的位置就是跳转指令的后三个字节,也就是跳转指令要跳转符号的具体地址。
  • sym 变量的值。
    • sechdrs[symindex].sh_addr 的值为符号表地址,也就是 0x9000000 。其同样在 rewrite_section_headers 函数中完成重定位,位绝对地址,而非相对地址。
    • ELF64_R_SYM(rel[i].r_info) 就是 003800000004 的高32位。即 0x38 ,也就是十进制的 56。
    • 查看符号表可以指导,符号表中,第56个条目正是 msleep
    • sym 的值就是符号表中,msleep 符号的信息。
  • val 的值。
    • sym->st_valuesimplify_symbols 函数中,已被重新定位过,即 sym[i].st_value +=info->sechdrs[sym[i].st_shndx].sh_addr; ,其值为符号所在的绝对地址。
    • rel[i].r_addend 为加数。
  • 根据 .rela.text 节的内容可知, ELF64_R_TYPE(rel[i].r_info 的值为 R_X86_64_PLT32
  • val -= (u64)loc; 的值为,跳转指令与目的符号的相对地址。
  • *(u32 *)loc = val; 将跳转指令,写入到 .text 节中。至此,完成重定位。

总结

在编译阶段,由于无法确定符号运行时的实际地址,只能将跳转语句的目的地址置空(即 e8 00 00 00 00),然后将条目信息记录到重定位表中(也就是 .rela.xxxx 或者 .rel.xxxx 中),等模块在加载时重新写入。
重定位的过程,就是在加载模块时,根据 ELF 头中的重定位节中的信息以及符号表信息,计算符号的实际地址,然后填入到跳转语句的目的地址部分,最终变成完整的跳转语句。

加数的作用

以下回答来自 chatgpt:
重定位过程中需要加上一个加数的主要原因是,目标文件中的代码和数据段并非总是以对齐的地址开始,这时候需要通过加数来校正引用地址。
在链接过程中,代码和数据段的起始地址往往用一个虚拟起始地址表示,在运行时需要将其映射到实际的物理地址中。而在映射过程中,因为不同目标文件的段起始地址不一定对齐,导致代码和数据的实际地址与虚拟地址存在偏移。因此,在重定位过程中需要通过加上合适的加数,才能够正确地修正符号的引用地址,映射到正确的实际地址中。
例如,在 ELF32 的静态重定位中,对于绝对符号的引用,重定位类型为 R_386_32,重定位表中会记录需要将该符号引用的地址加上一个绝对的数值(加数)来得到符号的真实地址。而对于相对符号的引用,重定位类型为 R_386_PC32,重定位表中会记录需要将该符号引用的地址加上另一个相对的地址值(加数),计算出地址最终的绝对值。实际上,加数中记录的是绝对地址和相对地址的差值,通过加上这个差值,才能实现符号的正确重定位。
综上,加数在重定位过程中起到了一个关键作用,用于校正引用地址和符号地址之间的偏移,确保引用的地址最终能够正确地映射到实际的物理地址中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值