Linux 内核模块加载过程之重定位

文章目录

  • 一、内核模块符号解析
    • 1.1 内核模块重定位函数调用
      • 1.1.1 struct load_info info
      • 1.1.2 copy_module_from_user
    • 1.2 simplify_symbols
      • 1.2.1 simplify_symbols
      • 1.2.2 resolve_symbol_wait
      • 1.2.3 resolve_symbol
      • 1.2.4 find_symbol
  • 二、 apply_relocations
    • 2.1 apply_relocations
      • 2.1.1 Elf64_Ehdr
      • 2.1.2 Elf64_Shdr
    • 2.2 x86_64
    • 2.3 Arm64
  • 参考资料

一、内核模块符号解析

1.1 内核模块重定位函数调用

1.1.1 struct load_info info

SYSCALL_DEFINE3(init_module,......)-->load_module()-->simplify_symbols()-->apply_relocations()-->post_relocation()

加载模块只需要读入模块的二进制代码即可,然后执行init_module系统调用。

我们先介绍下struct load_info info结构体。

SYSCALL_DEFINE3(init_module, void __user *, umod,unsigned long, len, const char __user *, uargs)
{int err;struct load_info info = { };......err = copy_module_from_user(umod, len, &info);if (err)return err;return load_module(&info, uargs, 0);
}
struct load_info {const char *name;/* pointer to module in temporary copy, freed at end of load_module() */struct module *mod;Elf_Ehdr *hdr;unsigned long len;Elf_Shdr *sechdrs;char *secstrings, *strtab;unsigned long symoffs, stroffs;struct _ddebug *debug;unsigned int num_debug;bool sig_ok;
#ifdef CONFIG_KALLSYMSunsigned long mod_kallsyms_init_off;
#endifstruct {unsigned int sym, str, mod, vers, info, pcpu;} index;
};

struct load_info 是一个用于加载模块时存储相关信息的数据结构。
该结构体包含以下成员:
name:模块的名称,以字符串形式存储。

mod:指向临时复制的模块结构体的指针,在 load_module() 结束时释放。

hdr:指向 ELF 文件头(Elf_Ehdr)的指针,用于存储模块的 ELF 文件头信息。

len:模块数据的长度(字节数)。

sechdrs:指向节头表(Elf_Shdr)的指针,用于存储模块的节头表信息。

secstrings:指向节头字符串表的指针,用于存储节头字符串表的内容。

strtab:指向字符串表的指针,用于存储字符串表的内容。

symoffs:符号表的偏移量。

stroffs:字符串表的偏移量。

debug:指向 _ddebug 结构体的指针,用于存储调试信息。

num_debug:调试信息的数量。

sig_ok:表示模块是否通过了数字签名验证。

mod_kallsyms_init_off:用于内核符号表中模块的初始化偏移量。

index:一个匿名结构体,包含以下成员:

sym:符号表索引。
str:字符串表索引。
mod:模块索引。
vers:版本索引。
info:信息索引。
pcpu:percpu 变量索引。

该结构体用于在加载模块时存储各种相关信息,包括模块的名称、ELF 文件头、节头表、字符串表、符号表等。这些信息在模块加载和处理过程中起着重要的作用,用于解析和定位模块的各个部分。

struct load_info 在模块加载过程中起着重要作用。它作为一个容器,用于存储与正在加载的模块相关的各种信息和数据。struct load_info 的目的是在加载过程中促进模块的解析、处理和初始化。以下是它的主要作用:

存储模块元数据:该结构体保存了关于模块的基本元数据,例如模块的名称(name)。这些信息有助于识别和区分正在加载的模块。

临时模块存储:mod 成员指向模块结构体(struct module)的临时副本。这个副本在加载过程中使用,并在 load_module() 函数结束时释放。它允许加载过程在模块完全初始化之前对模块结构进行操作和修改。

ELF 头和节头存储:hdr 成员是指向模块的 ELF 头部(Elf_Ehdr)的指针。它存储了关于 ELF 文件格式的信息,例如入口点和节头表的位置。sechdrs 成员指向节头表(Elf_Shdr),其中包含了关于模块每个节的详细信息。

字符串和符号表存储:secstrings 成员保存了节头字符串表,其中存储了各个节的名称。strtab 成员指向字符串表,它包含了模块使用的各种字符串,如符号名称。这些表对于符号解析和调试非常重要。

符号和字符串偏移量:symoffs 和 stroffs 成员分别存储了符号表和字符串表在模块中的偏移量。这些偏移量用于在符号解析和其他操作中定位和访问符号和字符串。

调试信息:debug 成员指向 _ddebug 结构体,它存储了模块的调试信息。这些信息对于调试和跟踪模块的行为非常有用。

其他辅助信息:该结构体还包括其他成员,如 num_debug(调试条目的数量)、sig_ok(指示模块是否通过了数字签名验证)、mod_kallsyms_init_off(模块在内核符号表中的初始化偏移量)和 index(一个嵌套的结构体,包含不同类型表的各种索引)。

1.1.2 copy_module_from_user

/* Sets info->hdr and info->len. */
static int copy_module_from_user(const void __user *umod, unsigned long len,struct load_info *info)
{int err;info->len = len;if (info->len < sizeof(*(info->hdr)))return -ENOEXEC;err = security_kernel_load_data(LOADING_MODULE);if (err)return err;/* Suck in entire file: we'll want most of it. */info->hdr = __vmalloc(info->len,GFP_KERNEL | __GFP_NOWARN, PAGE_KERNEL);if (!info->hdr)return -ENOMEM;if (copy_chunked_from_user(info->hdr, umod, info->len) != 0) {vfree(info->hdr);return -EFAULT;}return 0;
}

这是一个用于从用户空间复制模块数据到内核空间的函数 copy_module_from_user 的代码片段。该函数接受用户空间的模块数据 umod 和数据长度 len,并将其复制到内核空间中的 info->hdr 中。

函数的实现逻辑如下:
(1)首先,将数据长度 len 存储到 info->len 中。

(2)如果数据长度小于 info->hdr 的大小,表示数据长度不足以存储模块的头部信息,这种情况下返回错误码 -ENOEXEC。

(3)调用 security_kernel_load_data 函数来进行内核安全性检查。如果检查失败,返回相应的错误码。

int security_kernel_load_data(enum kernel_load_data_id id)
{int ret;ret = call_int_hook(kernel_load_data, 0, id);if (ret)return ret;return ima_load_data(id);
}
EXPORT_SYMBOL_GPL(security_kernel_load_data);

security_kernel_load_data是内核模块加载过程中的一个 LSM点。

(4)分配内核可执行的虚拟内存空间来存储模块的头部信息。使用 __vmalloc 函数来分配内存,分配的大小为 info->len 字节。如果内存分配失败,返回错误码 -ENOMEM。

(5)使用 copy_chunked_from_user 函数将用户空间的模块数据 umod 复制到内核空间的 info->hdr 中。如果复制过程中出现错误,释放之前分配的内存并返回错误码 -EFAULT。

(6)如果复制成功,返回 0 表示成功。

该函数的主要功能是从用户空间复制模块数据到内核空间,并将复制后的数据存储在 info->hdr 中供后续处理和加载使用。

static int copy_chunked_from_user(void *dst, const void __user *usrc, unsigned long len)
{do {unsigned long n = min(len, COPY_CHUNK_SIZE);if (copy_from_user(dst, usrc, n) != 0)return -EFAULT;cond_resched();dst += n;usrc += n;len -= n;} while (len);return 0;
}

这是一个用于从用户空间按块复制数据到内核空间的函数 copy_chunked_from_user 的代码片段。该函数接受目标内核地址 dst、源用户地址 usrc 和数据长度 len,并按照指定的块大小进行分块复制。

函数的实现逻辑如下:
进入一个循环,直到全部数据被复制完毕。

在每次循环中,计算当前块的大小,选择较小值作为复制的长度。min(len, COPY_CHUNK_SIZE) 中的 COPY_CHUNK_SIZE 是一个预定义的常量,表示每个块的大小。

使用 copy_from_user 函数将当前块的数据从用户空间复制到目标内核地址 dst。如果复制过程中出现错误,返回错误码 -EFAULT。

调用 cond_resched 函数,让出 CPU,允许其他任务执行,以提高系统的响应性。

更新目标内核地址 dst、源用户地址 usrc 和剩余长度 len,以便处理下一个块。

检查剩余长度 len 是否为 0,如果仍有剩余数据,则继续循环,否则退出循环。

如果数据复制成功,返回 0 表示成功。

该函数的作用是按照指定的块大小,从用户空间按块复制数据到目标内核地址。由于复制过程中可能会耗费较长时间,因此在每个块的复制结束后调用 cond_resched 函数以确保系统能够及时响应其他任务。

1.2 simplify_symbols

1.2.1 simplify_symbols

/* Change all symbols so that st_value encodes the pointer directly. */
static int simplify_symbols(struct module *mod, const struct load_info *info)
{Elf_Shdr *symsec = &info->sechdrs[info->index.sym];Elf_Sym *sym = (void *)symsec->sh_addr;unsigned long secbase;unsigned int i;int ret = 0;const struct kernel_symbol *ksym;for (i = 1; i < symsec->sh_size / sizeof(Elf_Sym); i++) {const char *name = info->strtab + sym[i].st_name;switch (sym[i].st_shndx) {case SHN_COMMON:/* Ignore common symbols */if (!strncmp(name, "__gnu_lto", 9))break;/* We compiled with -fno-common.  These are notsupposed to happen.  */pr_debug("Common symbol: %s\n", name);pr_warn("%s: please compile with -fno-common\n",mod->name);ret = -ENOEXEC;break;case SHN_ABS:/* Don't need to do anything */pr_debug("Absolute symbol: 0x%08lx\n",(long)sym[i].st_value);break;case SHN_LIVEPATCH:/* Livepatch symbols are resolved by livepatch */break;case SHN_UNDEF:ksym = resolve_symbol_wait(mod, info, name);/* Ok if resolved.  */if (ksym && !IS_ERR(ksym)) {sym[i].st_value = kernel_symbol_value(ksym);break;}/* Ok if weak.  */if (!ksym && ELF_ST_BIND(sym[i].st_info) == STB_WEAK)break;ret = PTR_ERR(ksym) ?: -ENOENT;pr_warn("%s: Unknown symbol %s (err %d)\n",mod->name, name, ret);break;default:/* Divert to percpu allocation if a percpu var. */if (sym[i].st_shndx == info->index.pcpu)secbase = (unsigned long)mod_percpu(mod);elsesecbase = info->sechdrs[sym[i].st_shndx].sh_addr;sym[i].st_value += secbase;break;}}return ret;
}

simplify_symbols是在 Linux 内核中用于简化符号表的函数。它的作用是修改符号表中的符号,使得 st_value 字段直接编码为指针值,以提高符号访问的效率。

(1)首先,通过 info 参数获取符号表的节头信息(symsec)和符号表的起始地址(sym)。
(2)在 for 循环中,遍历符号表中的每个符号(从索引 1 开始,索引 0 通常是一个特殊符号)。
(3)对于每个符号,从字符串表中获取符号的名称。名称存储在字符串表中的偏移量(st_name)指定的位置。
(4)根据符号的 st_shndx 字段的值,执行不同的操作。
这里只考虑 st_shndx 字段 = SHN_UNDEF 的情况:
SHN_UNDEF:表示未定义符号,需要解析其值。使用 resolve_symbol_wait 函数尝试解析符号。
如果成功解析符号,则将 st_value 设置为解析到的内核符号的值。
如果解析失败但是符号属于弱符号(weak symbol),则忽略解析失败,保留符号的原始值。
如果解析失败且符号不是弱符号,则将返回错误代码,并打印警告信息。

这段代码的目的是优化符号访问的效率,通过直接编码指针值到符号表中的 st_value 字段,避免了在运行时进行额外的计算。这对于内核符号的访问和解析过程非常重要,因为它们在内核的各个部分被广泛使用。

1.2.2 resolve_symbol_wait

static const struct kernel_symbol *
resolve_symbol_wait(struct module *mod,const struct load_info *info,const char *name)
{const struct kernel_symbol *ksym;char owner[MODULE_NAME_LEN];if (wait_event_interruptible_timeout(module_wq,!IS_ERR(ksym = resolve_symbol(mod, info, name, owner))|| PTR_ERR(ksym) != -EBUSY,30 * HZ) <= 0) {pr_warn("%s: gave up waiting for init of module %s.\n",mod->name, owner);}return ksym;
}

这段代码的目的是解析内核符号 – 使用 resolve_symbol 函数尝试解析符号。该函数根据给定的模块、加载信息和符号名称,查找并返回符号的内核表示。,并在解析完成前等待模块的初始化。它使用 resolve_symbol 函数来实际解析符号,并使用 wait_event_interruptible_timeout 函数来等待模块初始化完成。等待的目的是确保符号的解析是在模块完全初始化之后进行的,以避免潜在的竞争条件或使用未完全初始化的模块导致的错误。

1.2.3 resolve_symbol

/* Resolve a symbol for this module.  I.e. if we find one, record usage. */
static const struct kernel_symbol *resolve_symbol(struct module *mod,const struct load_info *info,const char *name,char ownername[])
{struct module *owner;const struct kernel_symbol *sym;const s32 *crc;int err;/** The module_mutex should not be a heavily contended lock;* if we get the occasional sleep here, we'll go an extra iteration* in the wait_event_interruptible(), which is harmless.*/sched_annotate_sleep();mutex_lock(&module_mutex);sym = find_symbol(name, &owner, &crc,!(mod->taints & (1 << TAINT_PROPRIETARY_MODULE)), true);if (!sym)goto unlock;if (!check_version(info, name, mod, crc)) {sym = ERR_PTR(-EINVAL);goto getname;}err = ref_module(mod, owner);if (err) {sym = ERR_PTR(err);goto getname;}getname:/* We must make copy under the lock if we failed to get ref. */strncpy(ownername, module_name(owner), MODULE_NAME_LEN);
unlock:mutex_unlock(&module_mutex);return sym;
}

函数 resolve_symbol,用于解析内核模块中未定义的符号引用,并记录符号的使用情况。

1.2.4 find_symbol

/* Find a symbol and return it, along with, (optional) crc and* (optional) module which owns it.  Needs preempt disabled or module_mutex. */
const struct kernel_symbol *find_symbol(const char *name,struct module **owner,const s32 **crc,bool gplok,bool warn)
{struct find_symbol_arg fsa;fsa.name = name;fsa.gplok = gplok;fsa.warn = warn;if (each_symbol_section(find_symbol_in_section, &fsa)) {if (owner)*owner = fsa.owner;if (crc)*crc = fsa.crc;return fsa.sym;}pr_debug("Failed to find symbol %s\n", name);return NULL;
}
EXPORT_SYMBOL_GPL(find_symbol);

函数 find_symbol,用于在内核中查找符号并返回它,同时可选地返回符号的校验和和拥有者模块。

调用 each_symbol_section 函数,对每个符号节调用 find_symbol_in_section 函数进行符号查找。

如果找到符号,将符号指针赋值给 fsa.sym,并可选地将符号所属的模块赋值给 fsa.owner,将符号的校验和赋值给 fsa.crc。
如果找到符号,返回 fsa.sym。

该函数用于在内核中查找给定名称的符号。它通过遍历每个符号节来查找符号,并在找到符号后返回相应的信息。函数的实现依赖于 each_symbol_section 和 find_symbol_in_section 函数。

二、 apply_relocations

2.1 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;
}

使用一个循环遍历模块加载信息中的每个重定位节。

获取当前重定位节的 sh_info 字段,表示关联的节索引。如果 sh_info 不是有效的节索引(大于等于节的总数),则跳过该重定位节。

检查当前重定位节的 sh_flags 字段是否包含 SHF_ALLOC 标志,判断是否为已分配的节。如果不是已分配的节,则跳过该重定位节。

检查当前重定位节的 sh_flags 字段是否包含 SHF_RELA_LIVEPATCH 标志,判断是否为 Livepatch 重定位节。如果是 Livepatch 重定位节,则跳过该重定位节。

根据重定位节的类型进行相应的重定位操作:
如果重定位节的类型是 SHT_REL,则调用 apply_relocate 函数应用重定位信息。
如果重定位节的类型是 SHT_RELA,则调用 apply_relocate_add 函数应用重定位信息。

对于内核模块重定位的类型基本都是SHT_RELA,所以我们关注的是apply_relocate_add 函数。

2.1.1 Elf64_Ehdr

typedef struct elf64_hdr {unsigned char	e_ident[EI_NIDENT];	/* ELF "magic number" */Elf64_Half e_type;Elf64_Half e_machine;Elf64_Word e_version;Elf64_Addr e_entry;		/* Entry point virtual address */Elf64_Off e_phoff;		/* Program header table file offset */Elf64_Off e_shoff;		/* Section header table file offset */Elf64_Word e_flags;Elf64_Half e_ehsize;Elf64_Half e_phentsize;Elf64_Half e_phnum;Elf64_Half e_shentsize;Elf64_Half e_shnum;Elf64_Half e_shstrndx;
} Elf64_Ehdr;

Elf64_Ehdr 是一个用于表示 ELF64(64位 ELF 文件格式)文件头的结构体类型。它包含以下成员:
e_ident:ELF 文件的标识符数组,也称为 “ELF 魔数”。它用于识别文件是否符合 ELF 文件格式的规范。

e_type:ELF 文件的类型。它表示文件的类型,如可执行文件、共享对象、目标文件等。

e_machine:目标体系结构的架构类型。它表示文件的目标运行环境所使用的体系结构。

e_version:ELF 文件的版本号。它指示 ELF 文件格式的版本。

e_entry:程序的入口点的虚拟地址。它表示程序执行的起始地址。

e_phoff:程序头表(Program Header Table)在文件中的偏移量。它指示程序头表的位置和大小。

e_shoff:节头表(Section Header Table)在文件中的偏移量。它指示节头表的位置和大小。

e_flags:特定标志位。它包含一些特定于体系结构或文件的标志。

e_ehsize:ELF 文件头的大小。它表示 ELF 文件头的字节大小。

e_phentsize:程序头表项的大小。它表示每个程序头表项的字节大小。

e_phnum:程序头表中的条目数。它表示程序头表中包含的程序头表项的数量。

e_shentsize:节头表项的大小。它表示每个节头表项的字节大小。

e_shnum:节头表中的条目数。它表示节头表中包含的节头表项的数量。

e_shstrndx:节头字符串表的索引。它表示节头字符串表在节头表中的索引,用于查找节名。

对于内核模块重定位过程中,对于Elf64_Ehdr我们需要关注的字段是e_shnum。
e_shnum:节头表中的条目数。它表示节头表中包含的节头表项的数量。

2.1.2 Elf64_Shdr

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;

Elf64_Shdr 结构体定义了 ELF64 格式中节头部的布局和字段的含义,具体解释如下:
sh_name:该字段是一个索引,指示节的名称在字符串表中的位置。字符串表是一个包含所有节名的字符串数组,它位于 ELF 文件的一个特殊节中。
sh_type:该字段指示节的类型,定义了节所包含数据或代码的语义。例如,常见的节类型包括代码段、数据段、符号表、字符串表等。
sh_flags:该字段包含了节的各种属性和标志。这些标志可以指示节的可写性、可执行性、对齐要求等属性。
sh_addr:该字段指示节在程序执行时的虚拟地址。对于可执行文件,这是节在内存中加载的地址。
sh_offset:该字段指示节在 ELF 文件中的偏移量,即该节的数据或代码在文件中的位置。
sh_size:该字段指示节的大小(以字节为单位),即该节所占用的文件空间大小。
sh_link:该字段是一个索引,指示该节相关联的其他节的索引。具体关联的节类型取决于 sh_type 字段的值。
sh_info:该字段包含附加的节信息。具体的含义取决于 sh_type 字段的值。
sh_addralign:该字段指示节的对齐要求,即节在内存中的起始地址需要满足的对齐条件。通常,节的对齐要求是根据节的特性和用途来确定的。
sh_entsize:该字段指示节中每个表项的大小。仅在节类型为表格型(如符号表)时才具有意义,用于计算表中条目的数量。

其中sh_info字段:
sh_info 字段是 ELF64 节头部中的一个字段,用于存储与特定节相关的附加信息。该字段的具体含义取决于节的类型(sh_type 字段)。

对于某些特定的节类型,sh_info 字段具有以下含义:
(1)对于符号表节(SHT_SYMTAB)和动态符号表节(SHT_DYNSYM),sh_info 字段存储了符号表中本地符号的数量。本地符号是指与当前目标文件或共享对象相关的符号。通过 sh_info 字段的值,可以确定符号表中本地符号的范围。

(2)对于重定位节(SHT_REL 和 SHT_RELA),sh_info 字段指示关联的节的索引。这个关联节包含了重定位所需的目标符号或节的信息。通过 sh_info 字段的值,可以确定重定位节与其关联的目标节。

对于这里我们主要关注 sh_type = SHT_RELA,通过 sh_info 字段的值,可以确定重定位节与其关联的目标节。

假设有一个 ELF 文件,其中包含以下两个节:

.text 节(类型为 SHT_PROGBITS):包含可执行代码的指令。
.rel.text 节(类型为 SHT_RELA):包含与 .text 节相关的重定位信息。

在这个示例中,.rel.text 节是关联于 .text 节的重定位节。sh_info 字段的值将提供有关如何解析 .rel.text 节的信息。

首先,查看 .rel.text 节的 sh_info 字段的值。假设 sh_info 字段的值为 4,这意味着 .rel.text 节与索引为 4 的节相关联。

接下来,查找索引为 4 的节,即关联节。假设关联节是 .symtab 节(符号表节)。

通过这个例子,我们可以得出以下结论:
.rel.text 节与 .text 节相关联,通过 sh_info 字段的值 4 来指示关联节的索引为 4。
关联节是 .symtab 节,它可能包含了重定位所需的目标符号信息。

通过 sh_info 字段的值,可以确定关联的节,进而了解到与重定位相关的目标符号或目标节的位置和信息。

2.2 x86_64

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 *///获取需要重定位的位置 Ploc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr+ rel[i].r_offset;/* This is the symbol it is referring to.  Note that allundefined symbols have been resolved.  *///从符号表中获取需要重定位符号的值 S//符号表中有多个符号,我们需要获取要重定位的符号,其在符号表中的位置索引:ELF64_R_SYM(rel[i].r_info)sym = (Elf64_Sym *)sechdrs[symindex].sh_addr+ ELF64_R_SYM(rel[i].r_info);//获取 S + Aval = sym->st_value + rel[i].r_addend;switch (ELF64_R_TYPE(rel[i].r_info)) {......case R_X86_64_PC32:case R_X86_64_PLT32:if (*(u32 *)loc != 0)goto invalid_relocation;val -= (u64)loc;*(u32 *)loc = val;......}}return 0;......
}

该函数遍历重定位节中的每个重定位项,并根据不同的重定位类型对指定位置进行修正。

其中 Elf64_Rela :

typedef struct elf64_rela {Elf64_Addr r_offset;	/* Location at which to apply the action */Elf64_Xword r_info;	/* index and type of relocation */Elf64_Sxword r_addend;	/* Constant addend used to compute value */
} Elf64_Rela;

Elf64_Rela 是一个用于表示 ELF64(64位 ELF 文件格式)重定位项的结构体类型。它包含以下成员:

(1)r_offset:重定位项的目标地址。它指示在哪个位置应用重定位操作。
(2)r_info:重定位项的索引和类型。索引和类型的组合指示了重定位操作应该如何执行。具体来说,它将索引和重定位类型编码为一个 64 位的无符号整数(Elf64_Xword 类型)。
(3)r_addend:用于计算修正值的常量加数。在执行重定位操作时,将目标地址的值与该常量加数相加,以计算最终的修正值。

重定位项的索引和类型的计算:
用于从 64 位的 r_info 值中提取符号索引和重定位类型的宏定义。

#define ELF64_R_SYM(i)			((i) >> 32)
#define ELF64_R_TYPE(i)			((i) & 0xffffffff)

ELF64_R_SYM(i) 宏将 64 位的 r_info 值右移 32 位,并返回高位部分,即符号索引。在 ELF64 格式中,符号索引占据了高 32 位。

ELF64_R_TYPE(i) 宏将 64 位的 r_info 值与 0xffffffff 进行按位与操作,返回低位部分,即重定位类型。在 ELF64 格式中,重定位类型占据了低 32 位。

这两个宏的作用是从 r_info 值中提取符号索引和重定位类型,以便在重定位过程中进行符号解析和类型判断。通过这样的宏定义,可以方便地从 r_info 值中获取所需的信息,而无需手动进行位操作。

其中 Elf64_Sym :
Elf64_Sym 是一个用于表示 ELF64(64位 ELF 文件格式)符号表项的结构体类型。它包含以下成员:
(1)st_name:符号的名称在字符串表中的索引。它指示了符号的名称在字符串表中的位置。
(2)st_info:符号的类型和绑定属性。它是一个无符号字符,用于编码符号的类型和绑定属性。
(3)st_other:没有定义的含义,通常为0。保留字段,未使用。
(4)st_shndx:关联的节(section)索引。它表示符号所属的节的索引,指示符号在哪个节中定义或引用。
(5)st_value:符号的值。它表示符号的地址或常量值。
(6)st_size:关联的符号大小。它表示符号的大小或长度,以字节为单位。

Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr; 将重定位节的起始地址转换为指向 Elf64_Rela 类型的指针,并将结果存储在 rel 变量中。

loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + rel[i].r_offset; 计算出当前重定位项指向的位置,根据重定位项的偏移量(r_offset)在指定节的起始地址上进行偏移。

sym = (Elf64_Sym *)sechdrs[symindex].sh_addr + ELF64_R_SYM(rel[i].r_info); 根据重定位项中的符号索引(ELF64_R_SYM(rel[i].r_info)),找到对应的符号表项,并将结果存储在 sym 变量中。

val = sym->st_value + rel[i].r_addend; 计算出修正后的值,将符号的值(sym->st_value)与重定位项的附加值(rel[i].r_addend)相加,并将结果存储在 val 变量中。

根据重定位项的类型进行不同的操作(这里我们只关注R_X86_64_PC32重定位类型):

		case R_X86_64_PC32:case R_X86_64_PLT32:if (*(u32 *)loc != 0)goto invalid_relocation;val -= (u64)loc;*(u32 *)loc = val;

对于 R_X86_64_PC32 和 R_X86_64_PLT32 类型,将修正后的值减去当前重定位项指向的位置,并将结果存储到当前重定位项指向的位置。

val -= (u64)loc; 将修正后的值 val 减去当前重定位项指向的位置 loc,得到相对于当前位置的偏移量。

*(u32 *)loc = val; 将修正后的偏移量存储到当前重定位项指向的位置。

这段代码的作用是将修正后的相对偏移量(val)存储到指定位置上,这些位置通常是指令中的相对跳转或调用目标地址。在这种情况下,修正的偏移量是相对于当前指令的位置计算得出的。

2.3 Arm64

int apply_relocate_add(Elf64_Shdr *sechdrs,const char *strtab,unsigned int symindex,unsigned int relsec,struct module *me)
{unsigned int i;int ovf;bool overflow_check;Elf64_Sym *sym;void *loc;u64 val;//获取重定位节的起始虚拟地址Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr;for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {//loc 对应于 AArch64 ELF 文档中的 P//通过 sh_info 字段的值,可以确定关联的节,获取重定位节的虚拟地址 //重定位节的虚拟地址 + rel[i].r_offset = 需要重定位的位置 Ploc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr+ rel[i].r_offset;/* sym is the ELF symbol we're referring to. *///获取该重定位项在符号表中的索引位置的符号项//将符号表的起始地址转化为 (Elf64_Sym *)指针,那么+一个索引值r_info,就表示获取符号表中第r_info位置的符号项sym = (Elf64_Sym *)sechdrs[symindex].sh_addr+ ELF64_R_SYM(rel[i].r_info);// val 对应于 AArch64 ELF 文档中的 (S + A) val = sym->st_value + rel[i].r_addend;/* Check for overflow by default. */overflow_check = true;/* Perform the static relocation. */switch (ELF64_R_TYPE(rel[i].r_info)) {/* Null relocations. */case R_ARM_NONE:case R_AARCH64_NONE:ovf = 0;break;/* Data relocations. */......case R_AARCH64_PREL32:ovf = reloc_data(RELOC_OP_PREL, loc, val, 32);break;......}}
}

apply_relocate_add的函数,它对一个ELF(可执行和可链接格式)内核模块二进制文件进行重定位。重定位是调整二进制文件中符号引用的过程,使其在加载到内存时指向正确的内存位置。

函数通过循环迭代每个重定位节的重定位项来进行处理。在循环内部,根据重定位项的信息计算重定位符号的位置 loc 和 重定位符号的值val 。
然后,根据重定位类型 ELF64_R_TYPE(rel[i].r_info) 执行特定类型的重定位。
根据重定位类型采取不同的处理方式,并相应地调用不同的辅助函数。

我们这里主要关注重定位类型 R_AARCH64_PREL32 。R_AARCH64_PREL32 对应的辅助函数reloc_data:

int ovf;
ovf = reloc_data(RELOC_OP_PREL, loc, val, 32);
enum aarch64_reloc_op {RELOC_OP_NONE,RELOC_OP_ABS,RELOC_OP_PREL,RELOC_OP_PAGE,
};static u64 do_reloc(enum aarch64_reloc_op reloc_op, __le32 *place, u64 val)
{switch (reloc_op) {......case RELOC_OP_PREL:return val - (u64)place;return 0;......}pr_err("do_reloc: unknown relocation operation %d\n", reloc_op);return 0;
}static int reloc_data(enum aarch64_reloc_op op, void *place, u64 val, int len)
{//调用 do_reloc 函数对传入的值进行修正,并将修正后的值存储在 sval 变量中。s64 sval = do_reloc(op, place, val);switch (len) {......//如果长度为 32,将 sval 强制转换为 s32 类型,并将其存储到 place 指针指向的位置上。case 32:*(s32 *)place = sval;if (sval < S32_MIN || sval > U32_MAX)return -ERANGE;break;......}return 0;
}

根据传入的操作类型和值,将修正后的数据存储到给定的位置指针处。即将重定位操作后修正后的数据写到 loc(待重定位的位置)。

修正后的数据 :

result = val - loc
result = S + A - P
S = sym->st_value
A = rel[i].r_addend
p = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + rel[i].r_offset;

(1)其中:

void *loc;
loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + rel[i].r_offset;

sechdrs[relsec].sh_info表示重定位条目所属的节的索引。
sechdrs[sechdrs[relsec].sh_info].sh_addr表示该节的起始地址。
(void *)表示将起始地址转换为void *类型的指针,以便与偏移量进行相加。
rel[i].r_offset表示重定位条目中记录的偏移量。
loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr + rel[i].r_offset;将起始地址与偏移量相加,得到重定位条目在内存中的绝对地址。

通过将计算得到的绝对地址赋值给loc,后续代码可以使用loc来引用该重定位条目在内存中的位置。
loc 对应于 AArch64 ELF 文档中的 P。

(2)其中:

Elf64_Sym *sym;
sym = (Elf64_Sym *)sechdrs[symindex].sh_addr + ELF64_R_SYM(rel[i].r_info);

是将符号表的起始地址(sechdrs[symindex].sh_addr)与重定位条目中的符号索引(ELF64_R_SYM(rel[i].r_info))相加,并将结果赋值给sym变量。

将符号表的起始地址转化为 (Elf64_Sym *)指针,那么加上一个索引值r_info,就表示获取符号表中第r_info位置的符号项。
这里是指针的特性,比如符号表是一个数组a,每一个数组成员都是Elf64_Sym类型,那么上述sym的值就等于 = a[rel[i].r_info]。获取符号表数组第rel[i].r_info个元素,元素类型是Elf64_Sym。

具体解释如下:
sechdrs[symindex].sh_addr表示指向符号表节的起始地址。
(Elf64_Sym *)表示将符号表的起始地址转换为指向Elf64_Sym类型的指针。
ELF64_R_SYM(rel[i].r_info)从重定位条目的r_info字段中提取出符号索引。
sym = (Elf64_Sym *)sechdrs[symindex].sh_addr + ELF64_R_SYM(rel[i].r_info);将符号索引与符号表的起始地址相加,得到对应符号的地址,并将结果赋值给sym。

通过这行代码,将符号的地址存储在sym变量中,后续代码可以使用sym来引用该符号的属性,例如sym->st_value表示符号的值。

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

这行代码的作用是计算出重定位后的值(val)。它将符号的值(sym->st_value)与重定位条目的附加值(rel[i].r_addend)相加,并将结果赋值给val变量。

具体解释如下:
sym->st_value表示符号的值,即符号在内存中的地址或相对地址。
rel[i].r_addend表示重定位条目的附加值,用于修正符号的值。
val = sym->st_value + rel[i].r_addend将符号的值与重定位条目的附加值相加,得到修正后的值,并将结果赋值给val。

val 对应于 AArch64 ELF 文档中的 (S + A)。

参考资料

Linux 4.19.90

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/56887.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2023年05月 C/C++(四级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;怪盗基德的滑翔翼 怪盗基德是一个充满传奇色彩的怪盗&#xff0c;专门以珠宝为目标的超级盗窃犯。而他最为突出的地方&#xff0c;就是他每次都能逃脱中村警部的重重围堵&#xff0c;而这也很大程度上是多亏了他随身携带的便于操作的滑翔翼。 有一天&#xff…

计算机毕设 基于机器视觉的二维码识别检测 - opencv 二维码 识别检测 机器视觉

文章目录 0 简介1 二维码检测2 算法实现流程3 特征提取4 特征分类5 后处理6 代码实现5 最后 0 简介 今天学长向大家介绍一个机器视觉的毕设项目&#xff0c;二维码 / 条形码检测与识别 基于机器学习的二维码识别检测 - opencv 二维码 识别检测 机器视觉 1 二维码检测 物体检…

一文便知 GO 中mongodb 的安装与使用

MONGDB 安装与使用 咱们来回顾一下上次分享的内容&#xff1a; 如何使用log 包log 包原理和具体实现自定义日志 要是对 GO 的日志包还有点兴趣的话&#xff0c;可以查看文章 GO的日志怎么玩 ? 今天咱们来玩个简单的 mongodb 的安装和使用 MONGODB介绍 MongoDB 是一个基于…

Prometheus关于微服务的监控

在微服务架构下随着服务越来越多,定位问题也变得越来越复杂,因此监控服务的运行状态以及针对异常状态及时的发出告警也成为微服务治理不可或缺的一环。服务的监控主要有日志监控、调用链路监控、指标监控等几种类型方式,其中指标监控在整个微服务监控中比重最高,也是实际生…

CTF-XXE(持续更新,欢迎分享更多相关知识点的题目)

知识 实例 BUU [PHP]XXE 进来看到 然后一起看 Write BUU XXE COURSE 1 进来看到 一起看 write NSS [NCTF2019]Fake XML cookbook 反正是XXE 直接整 write [NCTF 2019]True XML cookbook 不整花里胡哨&#xff0c;解题在最下面 write 与博主不同&#xff0c;我通过…

rabbitmq之Consumer Prefetch(消费者预取)

官方文档&#xff1a; https://www.rabbitmq.com/consumer-prefetch.html https://www.rabbitmq.com/confirms.html#channel-qos-prefetch 【问题】 测试”消息积压“场景&#xff1a;在消费者没有启动的情况下&#xff0c;生产者先生产很多消息。然后先开启一个a消费者&#…

Visual Studio 2022的MFC框架——WinMain函数

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天我们来重新审视一下Visual Studio 2022下开发工具的MFC框架知识。 大家还记得创建Win32应用程序是怎么弄的吗&#xff1f; Win32应用程序的建立到运行是有一个个关系分明的步骤的&#xff1a; 1.进入W…

C#获取DataTable的前N行数据然后按指定字段排序

获取DataTable的前N行数据然后按指定字段排序 可以使用以下三种代码&#xff1a; 第一种&#xff1a;使用Linq DataTable dtLast dataTable.AsEnumerable().Take(count).OrderBy(dataRow > Convert.ToInt32(dataRow["Sequence"])).CopyToDataTable(); 第二种…

redis相关

如果redis没有设置expire&#xff0c;他是否默认永不过期&#xff1f; 清理线上Redis没有设置过期时间的key_青苔小榭的博客-CSDN博客 如何给Redis中未设置过期时间key添加过期时间&#xff1f; - 知乎 Redis中的几种更新策略_如何实现redis数据的局部更新_LG_985938339的博客…

npm yarn pnpm npx nvm 命令怎么区分怎么用

npm​​​​​​​ 包管理器&#xff0c;可以用来安装、卸载、更新和管理各种包npm的package.json中文文档 参数 - install&#xff1a;安装一个或多个包。例如&#xff1a;npm install 。 uninstall&#xff1a;卸载一个包。例如&#xff1a;npm uninstall 。 update&#xf…

评估两个位置姿态之间的差异

评估两个位置姿态之间的差异是机器人学、计算机视觉和计算机图形学中的常见问题。位置姿态通常由平移(位置)和旋转(姿态)组成。为了评估两个位置姿态之间的差异,我们可以分别考虑平移和旋转: 平移差异: 这是最直观的。两个位置之间的差异可以通过欧氏距离来计算。给定两…

【Python】从入门到上头—Python基础(2)

文章目录 一.基础语法1.编码2.标识符3.保留字4.注释5.行与缩进6.多行语句7.数字(Number)类型8.字符串(String)9.空行10.等待用户输入11.同一行显示多条语句12.多个语句构成代码组13.print 输出14.import 与 from...import 二.基本数据类型1.变量和赋值2.多个变量赋值3.标准数据…

为什么说es是近实时搜索

首先要理解es的存储结构&#xff1a; 一个index的数据&#xff0c;分散在多个shard(分片)&#xff0c;一个分片又有很多segment(段)&#xff0c;es是数据不可变模型&#xff0c;更新数据只是新增一个版本。 es是怎么写数据的&#xff1f; 每次写的时候&#xff0c;首先会写到…

创建web应用程序,React和Vue怎么选?

React和Vue都是创建web应用程序的绝佳选择。React得到了科技巨头和庞大的开源社区的支持&#xff0c;代码库可以很大程度地扩展&#xff0c;允许你创建企业级web应用程序。React拥有大量合格甚至优秀的开发人员粉丝&#xff0c;可以解决你在开发阶段可能遇到的任何问题。 毫无疑…

力扣2201 |二维问题 |统计可以提取的工件| set、lambda

想看官方描述–>传送门 n 2, artifacts [[0,0,0,0],[0,1,1,1]], dig [[0,0],[0,1]] 就是讲&#xff0c;在子集网格中&#xff0c;如果所提供的dig 能把该子网格完全填充&#xff0c;则该子网格就是其中的结果&#xff0c;遍历所有的子网格&#xff0c;得到所有可以被填…

Oracle 19C RAC安装PSU oui-patch.xml权限错误

Oracle 19C RAC安装PSU时&#xff0c;节点2安装失败&#xff0c;经排查错误原因为oui-patch.xml文件权限错误。 Oracle官方建议oui-patch.xml文件权限&#xff0c;改成660或者666&#xff1a; chmod 660 oui-patch.xml权限修改完成后&#xff0c;安装psu还是失败&#xff0c;…

SpringBoot项目转为非Web项目

在微服务开发时&#xff0c;有时候某个服务可能并不需要是一个web项目&#xff0c;这时候应该怎么做呢&#xff1f; 去除pom中的web-starter 替换spring-boot-starter-web为spring-boot-starter&#xff0c;如果其他pom引入了web则需要逐一排除 <dependency><…

光伏+旅游景区

传统化石燃料可开发量逐渐减少&#xff0c;并且对环境造成的危害日益突出。全世界都把目光投向了可再生能源&#xff0c;希望可再生能源能够改变人类的能源结构。丰富的太阳能取之不尽、用之不竭&#xff0c;同时对环境没有影响&#xff0c;光伏发电是近些年来发展最快&#xf…

Nginx的搭建与核心配置

Nginx的搭建与核心配置 1、IO模型1.1I/O 模型相关概念1.2网络I/O模型1.2.1阻塞型I/O模型1.2.2非阻塞型I/O模型1.2.3多路复用I/O型1.2.4信号驱动式I/O模型1.2.5异步I/O模型 2、Nginx概述2.1Nginx功能介绍2.2基础特性2.3Web服务相关的功能2.4Nginx进程结构2.4Nginx模块2.5Nginx三…

腾讯云coding平台平台inda目录遍历漏洞复现

前言 其实就是一个python的库可以遍历到&#xff0c;并不能遍历到别的路径下&#xff0c;后续可利用性不大&#xff0c;并且目前这个平台私有部署量不多&#xff0c;大多都是用腾讯云在线部署的。 CODING DevOps 是面向软件研发团队的一站式研发协作管理平台&#xff0c;提供…