[C++] 纯文本查看 复制代码
if (kernel_read)
read_dump(kernel_read, 1);
if (module_read)
read_dump(module_read, 0);
while (extsym_start) {
read_dump(extsym_start->file, 0);
extsym_iter = extsym_start->next;
free(extsym_start);
extsym_start = extsym_iter;
}
[C++] 纯文本查看 复制代码
void *grab_file(const char *filename, unsigned long *size)
{
struct stat st;
void *map;
int fd;
fd = open(filename, O_RDONLY);
if (fd < 0 || fstat(fd, &st) != 0)
return NULL;
*size = st.st_size;
map = mmap(NULL, *size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
close(fd);
if (map == MAP_FAILED)
return NULL;
return map;
}
[C++] 纯文本查看 复制代码
sechdrs = (void *)hdr + hdr->e_shoff;
info->sechdrs = sechdrs;
/* Check if file offset is correct */
if (hdr->e_shoff > info->size) {
fatal("section header offset=%lu in file '%s' is bigger than "
"filesize=%lu\n", (unsigned long)hdr->e_shoff,
filename, info->size);
return 0;
}
[C++] 纯文本查看 复制代码
/* Find symbol table. */
for (i = 1; i < hdr->e_shnum; i++) {
const char *secstrings
= (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
const char *secname;
int nobits = sechdrs.sh_type == SHT_NOBITS;
if (!nobits && sechdrs.sh_offset > info->size) {
fatal("%s is truncated. sechdrs.sh_offset=%lu > "
"sizeof(*hrd)=%zu\n", filename,
(unsigned long)sechdrs.sh_offset,
sizeof(*hdr));
return 0;
}
/*从节区里取出该节区的名字;sh_name 在节区头部表中,
是节区名字字符串的索引*/
secname = secstrings + sechdrs.sh_name;
if (strcmp(secname, ".modinfo") == 0) {
if (nobits)
fatal("%s has NOBITS .modinfo\n", filename);
info->modinfo = (void *)hdr + sechdrs.sh_offset;
info->modinfo_len = sechdrs.sh_size;
} else if (strcmp(secname, "__ksymtab") == 0)
info->export_sec = i;
else if (strcmp(secname, "__ksymtab_unused") == 0)
info->export_unused_sec = i;
else if (strcmp(secname, "__ksymtab_gpl") == 0)
info->export_gpl_sec = i;
else if (strcmp(secname, "__ksymtab_unused_gpl") == 0)
info->export_unused_gpl_sec = i;
else if (strcmp(secname, "__ksymtab_gpl_future") == 0)
info->export_gpl_future_sec = i;
/*SHT_SYMTAB表示此节区包含一个符号表,该类型用于链接编辑(ld)*/
/*不是 symtab 则继续查找*/
if (sechdrs.sh_type != SHT_SYMTAB)
continue;
/*找到symtab 或者是到达最后一个section 都没找到,这里是找到了symtab*/
/* sh_offset 节区的第一个字节与文件头之间的偏移*/
info->symtab_start = (void *)hdr + sechdrs.sh_offset;
info->symtab_stop = (void *)hdr + sechdrs.sh_offset
+ sechdrs.sh_size;
/* sh_link 头部表索引,链接到另一个section,这里是strtab */
/* sh_link 的值根据 sh_type 来决定。当sh_type 为 SHT_SYMTAB 时,sh_link由操作系统来指定 */
info->strtab = (void *)hdr +
sechdrs[sechdrs.sh_link].sh_offset; //获得strtab 节区指针
}
.sh_type != SHT_SYMTAB) 里判断是否为 symtab 节区,不是的话则跳过。若遇到 symtab 节区,那么将节区的头头部和尾部的地址分别记录在 symtab_start 和 symtab_stop 这两个指针中。
info->strtab 是 strtab 节区指针。strtab 节区从结构上来讲是一个字符串数组。它和 symtab 节区是紧密相关的,可以通过 symtab 节区里的相关索引成员定位到 symtab 中,找到相应的符号名。在 http://www.groad.net/bbs/read.php?tid-4228.html 这里也有一个相应的实例。
到此已经分析完 parse_elf() 这个函数的作用。
作者: beyes 时间: 2011-8-2 22:09
分析完 parse_elf() 后,回到 read_symbols() 中来,接下来会执行一个 new_module() 函数:mod = new_module(modname);
该函数的返回类型是一个 struct module ,该结构定义在 modpost.h 里:
[C++] 纯文本查看 复制代码
struct module {
struct module *next;
const char *name;
int gpl_compatible;
struct symbol *unres;
int seen;
int skip;
int has_init;
int has_cleanup;
struct buffer dev_table_buf;
char srcversion[25];
};
由定义可知,模块信息将是一个单项链表结构呈现。
new_module() 函数实现如下:
[C++] 纯文本查看 复制代码
static struct module *new_module(char *modname)
{
struct module *mod;
char *p, *s;
mod = NOFAIL(malloc(sizeof(*mod)));
memset(mod, 0, sizeof(*mod));
p = NOFAIL(strdup(modname));
/* strip trailing .o */
s = strrchr(p, '.');
if (s != NULL)
if (strcmp(s, ".o") == 0)
*s = '\\0';
/* add to list */
mod->name = p;
mod->gpl_compatible = -1;
mod->next = modules; //将新模块加到链表头
modules = mod;
return mod;
}
这里,我们传进的参数是 vmlinux.o ,函数中会为 vmlinux.o 分配一个 struct module 的结构,其中结构名是去掉 .o 这个后缀来存储的,即 vmlinux.o 变为 vmlinux 。设置好相关参数后,将这个结构加到链表头中。
接下来的一段程序不被执行:
[C++] 纯文本查看 复制代码
license = get_modinfo(info.modinfo, info.modinfo_len, "license");
if (info.modinfo && !license && !is_vmlinux(modname))
warn("modpost: missing MODULE_LICENSE() in %s\\n"
"see include/linux/module.h for "
"more information\\n", modname);
while (license) {
if (license_is_gpl_compatible(license))
mod->gpl_compatible = 1;
else {
mod->gpl_compatible = 0;
break;
}
license = get_next_modinfo(info.modinfo, info.modinfo_len,
"license", license);
}
由于 info.modinfo 和 info.modinfo_len 为空,所以 get_modinfo() 返回的 license 为空 。所以上面的一段程序在处理 vmlinux.o 时不予考虑。
继续往下:
[C++] 纯文本查看 复制代码
for (sym = info.symtab_start; sym < info.symtab_stop; sym++) {
symname = info.strtab + sym->st_name;
handle_modversions(mod, &info, sym, symname);
handle_moddevtable(mod, &info, sym, symname);
}
在上面这个循环里,遍历了整个 symtab 节区。在 symtab 节区里的每个结构单元都有一个 st_name 的成员,该成员用来索引 .strtab 这个节区,.strtab 节区是一个字符串数组,那么自然索引到的内容都是字符串,这些字符串表示了文件名,函数名以及全局变量名等。
handle_modversions() 函数与编译时配置 CONFIG_MODVERSIONS 有关。这里使用默认的内核配置,所以 CONFIG_MODVERSIONS 该项没有被选择。这里也先略过 handle_modversions() 和 handle_moddevtable() 的分析。
继续往下走:
[C++] 纯文本查看 复制代码
if (!is_vmlinux(modname) ||
(is_vmlinux(modname) && vmlinux_section_warnings))
check_sec_ref(mod, modname, &info);
由于我们的 modname 是 vmlinux.o 所以 is_vmlinux() 返回为真,且 vmlinux_section_warnings 变量会被初始化为 1 (vmlinux_section_warnings 用来统计 mismatch 的个数),所以这里会执行 check_sec_ref() 函数。
作者: beyes 时间: 2011-8-3 11:47
标题: check_sec_ref() 函数
先看 check_sec_ref() 函数前面的一段注释:/**
* A module includes a number of sections that are discarded
* either when loaded or when used as built-in.
* For loaded modules all functions marked __init and all data
* marked __initdata will be discarded when the module has been intialized.
* Likewise for modules used built-in the sections marked __exit
* are discarded because __exit marked function are supposed to be called
* only when a module is unloaded which never happens for built-in modules.
* The check_sec_ref() function traverses all relocation records
* to find all references to a section that reference a section that will
* be discarded and warns about it.
**/
一个模块在装载或被使用做 built-in 时,它们通常都会丢弃掉一些节区。对于可装载的模块,在初始化后,所有被标记以 "__init" 的函数和所有被标记以 "__initdata" 的数据也会被忽略。同样的,对于用作 built-in 的模块,那些被标记以 "__exit" 的节区最终也要被忽略,这是由于那些被标记以 "__exit" 且仅在模块卸载时才会调用的函数不会用在作为 built-in 的模块里面。check_sec_ref() 函数遍历所有的重定位记录,试图找到所有引用了会被忽略掉节区的引用。
check_sec_ref() 函数实现如下:
[C++] 纯文本查看 复制代码
static void check_sec_ref(struct module *mod, const char *modname,
struct elf_info *elf)
{
int i;
Elf_Shdr *sechdrs = elf->sechdrs;
/* Walk through all sections */
for (i = 0; i < elf->hdr->e_shnum; i++) {
check_section(modname, elf, &elf->sechdrs);
/* We want to process only relocation sections and not .init */
if (sechdrs.sh_type == SHT_RELA)
section_rela(modname, elf, &elf->sechdrs);
else if (sechdrs.sh_type == SHT_REL)
section_rel(modname, elf, &elf->sechdrs);
}
}
上面程序中,遍历整个节区头部表格,如果遇到 SHT_RELA 或 SHT_REL 类型的节区就会调用 section_rel() 函数做相应的处理。
SHT_RELA 和 SHT_REL 类型节区都是重定位表项节区。重定位是一种将符号引用和符号定义连接起来的处理过程。例如,当一个程序调用一个函数时,则调用指令就必须要求在执行时能够得到函数正确的地址。也就是说,可重定位文件必须包含能够描述如何修改节区内容的信息,这样就使得可执行文件和共享目标文件能够保存有正确进程映像信息。
SHT_RELA 和 SHT_REL 的区别是,前者可能会有补齐内容(addend),后者则没有补齐。两种节区的结构单元定义如下:
SHT_RELA 类型结构单元:
[C++] 纯文本查看 复制代码
/* Relocation table entry with addend (in section of type SHT_RELA). */
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
Elf32_Sword r_addend; /* Addend */
} Elf32_Rela;
SHT_REL 类型结构单元:
[C++] 纯文本查看 复制代码
/* Relocation table entry without addend (in section of type SHT_REL). */
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
两个结构都拥有 r_offset 和 r_info 这两个成员,不同的是 SHT_RELA 比 SHT_REL 多了一个补齐用的成员 r_addend 。
r_offset 成员给出了重定位时所适用的位置,对于一个可重定位的文件而言,该值是从节区开始处到将被重定位的存储单元之间的字节偏移。对于可执行文件或者共享目标文件而言,其取指是被重定位影响到的存储单元的虚拟地址。
r_info 成员给出了要进行重定位的符号表索引,以及将实施的重定位类型。例如,一个调用指令的重定位项就保存有被调函数的符号表索引。如果索引是 STN_UNDEF ,那么重定位就用 '0' 来作为 "符号值" (st_value) 。重定位类型是与处理器相关的。当程序代码引用一个重定位项的重定位类型或符号表索引,那么此时可对 r_info 成员应用 ELF32_R_TYPE 或 ELF32_R 宏来获取,如:
[C++] 纯文本查看 复制代码
/* How to extract and insert information held in the r_info field. */
#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)
最后 r_addend 是一个常量补齐,用来计算将被填充到可重定位字段的数值。
作者: beyes 时间: 2011-8-3 13:57
标题: section_rel()
section_rel() 被 check_sec_ref() 函数调用来处理重定位相关内容。在 section_rel() 里面:
[C++] 纯文本查看 复制代码
Elf_Rel *start = (void *)elf->hdr + sechdr->sh_offset;
Elf_Rel *stop = (void *)start + sechdr->sh_size;
上面 start 和 stop 两个变量是指向重定位结构单元的指针。在 check_sec_ref() 里会遍历所有的重定位表区,所以这里的两个指针就指向每次查找到的重定位节区的头部和尾部。
接下来:
[C++] 纯文本查看 复制代码
fromsec = sech_name(elf, sechdr);
fromsec += strlen(".rel");
/* if from section (name) is know good then skip it */
if (match(fromsec, section_white_list))
return;
上面代码中,fromsec 通过 sech_name() 函数获得重定位节区名的字符串,比如 rel.text, .rel.debug_frame 等前面有 .rel 前缀的重定位节区名;然后去掉前面的 .rel 前缀,变为相应的非重定位节区名;比如 .rel.text 变为 .text 。
在 match 函数中,section_white_list 参数是个字符串数组,如下定义:
[C++] 纯文本查看 复制代码
/* sections that we do not want to do full section mismatch check on */
static const char *section_white_list[] =
{
".comment*",
".debug*",
".mdebug*", /* alpha, score, mips etc. */
".pdr", /* alpha, score, mips etc. */
".stab*",
".note*",
".got*",
".toc*",
NULL
};
通过注释也知道,对于上面定义的节区(包括匹配前缀的节区,如 .comment* )不会做 mismatch 检查。也就是说,相关符号不会去用这些节区里的内容做重定位信息修正。对于默认配置的内核 .config,这里只有 .debug.frame 这个节区匹配。
接下来会利用一个 for 循环遍历所找到的可重定位节区里的每个结构单元,代码如下:
[C++] 纯文本查看 复制代码
for (rel = start; rel < stop; rel++) {
r.r_offset = TO_NATIVE(rel->r_offset);
#if KERNEL_ELFCLASS == ELFCLASS64
if (elf->hdr->e_machine == EM_MIPS) {
unsigned int r_typ;
r_sym = ELF64_MIPS_R_SYM(rel->r_info);
r_sym = TO_NATIVE(r_sym);
r_typ = ELF64_MIPS_R_TYPE(rel->r_info);
r.r_info = ELF64_R_INFO(r_sym, r_typ);
} else {
r.r_info = TO_NATIVE(rel->r_info);
r_sym = ELF_R_SYM(r.r_info);
}
#else
r.r_info = TO_NATIVE(rel->r_info);
r_sym = ELF_R_SYM(r.r_info);
#endif
r.r_addend = 0;
switch (elf->hdr->e_machine) {
case EM_386:
if (addend_386_rel(elf, sechdr, &r))
continue;
break;
case EM_ARM:
if (addend_arm_rel(elf, sechdr, &r))
continue;
break;
case EM_MIPS:
if (addend_mips_rel(elf, sechdr, &r))
continue;
break;
}
sym = elf->symtab_start + r_sym;
/* Skip special sections */
if (sym->st_shndx >= SHN_LORESERVE)
continue;
check_section_mismatch(modname, elf, &r, sym, fromsec);
}
上面代码中使用宏 ELF_R_SYM 获得 r_info 成员高 8 位,这部分内容是符号表的索引,通过这个索引可以从符号表中获得重定位符号的相关信息。
上面还有一个常数 SHN_LORESERVE ,它是保留索引的下界,在此会被忽略掉。
在上面的整个循环中,每找到一个可重定位结构单元,都会调用 check_section_mismatch() 来检查匹配情况。
所谓的 mismatch (不匹配),简单的说就是可重定位的相关项引用了不该引用的节区。
在 ELF 的头部中还有一个成员 e_machine ,它只是当前系统使用的是什么平台。这里是 x86 ,所以上面的那个 switch() 会进入到 addend_386_rel() 函数中,该函数实现如下:
[C++] 纯文本查看 复制代码
static int addend_386_rel(struct elf_info *elf, Elf_Shdr *sechdr, Elf_Rela *r)
{
unsigned int r_typ = ELF_R_TYPE(r->r_info);
unsigned int *location = reloc_location(elf, sechdr, r); //获得重定位位置
switch (r_typ) {
case R_386_32:
r->r_addend = TO_NATIVE(*location);
break;
case R_386_PC32:
r->r_addend = TO_NATIVE(*location) + 4;
/* For CONFIG_RELOCATABLE=y */
if (elf->hdr->e_type == ET_EXEC) //vmlinux.o 不是可执行文件
r->r_addend += r->r_offset;
break;
}
return 0;
}
上面使用 ELF_R_TYPE 宏从 r_info 成员里提取重定位类型。在 vmlinux.o 里的重定位类型分为两种,它们分别是 R_386_32 和 R_386_PC32 。
[C++] 纯文本查看 复制代码
static unsigned int *reloc_location(struct elf_info *elf,
Elf_Shdr *sechdr, Elf_Rela *r)
{
Elf_Shdr *sechdrs = elf->sechdrs;
int section = sechdr->sh_info;
return (void *)elf->hdr + sechdrs[section].sh_offset +
r->r_offset - sechdrs[section].sh_addr; //由于不会降 vmlinux.o 加载到内存中执行,所以这里 sh_addr 为 0 ,表示不会在进程内存中出现
}
在该函数中,看到了一个节区成员 sh_info,该成员的取值还要依据 sh_type 这个成员,当 sh_type 表示为 SHT_REL 或 SHT_RELA 时,该值表示重定位所使用节区的节区头部索引。所以该函数返回的是重定位信息所在可重定位节区中的位置。
欢迎光临 曲径通幽论坛 (http://www.groad.net/bbs/) |
Powered by Discuz! X3.2 |