曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 10536|回复: 3
打印 上一主题 下一主题

[Kernel] vmlinux.o 与 modpost.c

[复制链接]

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
跳转到指定楼层
楼主
发表于 2011-8-2 18:56:29 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
在编译出 vmlinux 之前,构建系统会先链接出 vmlinux.o,但这一步骤的目的并不是要将 vmlinux.o 链接进 vmlinux 中。在链接 vmlinux.o 过程中主要做两件事情:

1. ELF section (节区) 的 mismatch 检查;
2. 生成内核导出符号文件 Module.symvers (Symbol version dump文件)

下面结合 modpost 工具的源码文件 modpost.c 粗略分析一下这两个过程:

http://www.groad.net/bbs/read.php?tid-4190.html 这篇文章的最后提到 modpost 命令的执行为:
scripts/mod/modpost -o /home/beyes/Downloads/linux-2.6.35.13/Module.symvers -S vmlinux.o
在上面命令中,设置了 -S 选项后,sec_mismatch_verbose 变量就会被设置为 0,这表示在发生 mismatch 时,不会打印出详细的说明信息,这时往往会看到如下的提示:
WARNING: modpost: Found 41 section mismatch(es).
To see full details build your kernel with:
'make CONFIG_DEBUG_SECTION_MISMATCH=y'

现在来到 modpost.c 中的 main() 函数,由于 kernel_read ,module_read 和 extsym_start 这 3 个变量都初始化为 NULL ,所以下面的程序片段不会执行:
[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;
    }

由于上面的命令只有一个选项 -S 及一个参数 vmlinux.o ,所以底下的 while() 里的 read_symbols() 函数只执行一次:
[C++] 纯文本查看 复制代码
while (optind < argc)
        read_symbols(argv[optind++]);

下面看下 read_symbols() 函数,传进来的只有 vmlinu.o 这个参数。

进到 read_symbols() 函数中会首先执行 parse_elf() 函数,该函数用来对 ELF 文件做个基本的解析,vmlinux.o 也是 ELF 类型文件。在 parse_elf() 里:
[C++] 纯文本查看 复制代码
static int parse_elf(struct elf_info *info, const char *filename)
{
    unsigned int i;
    Elf_Ehdr *hdr;
    Elf_Shdr *sechdrs;
    Elf_Sym  *sym;

    hdr = grab_file(filename, &info->size);    //hdr is a  'Elf32_Ehdr' struct pointer (elf.h)
    if (!hdr) {
        perror(filename);
        exit(1);
    }
... ...

在 grap_file() 函数里第一个参数即 vmlinux.o ,第 2 个参数用来存放 vmlinux.o 这个文件的大小。grap_file() 函数如下:

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

上面的代码主要做两件事:
1. 返回 vmlinux.o 的大小到 info 结构中的 size 成员;
2. 将 vmlinux.o mmap ,然后返回映射后的内存指针。

再返回到 read_symbols() 中,下面的程序段用来判断 ELF 文件的头部 ident[] 数组里的信息:
[C++] 纯文本查看 复制代码
if ((hdr->e_ident[EI_MAG0] != ELFMAG0) ||
        (hdr->e_ident[EI_MAG1] != ELFMAG1) ||
        (hdr->e_ident[EI_MAG2] != ELFMAG2) ||
        (hdr->e_ident[EI_MAG3] != ELFMAG3)) {
        /* Not an ELF file - silently ignore it */
        return 0;
}

上面就是判断头部是不是以固定的 " 0x7f, 'E', 'L', 'F' " 这几个字符开始,如果不是,那么表明不是 ELF 文件。

接下来的一连串赋值都是根据平台的不同(数据的大小端)而进行相关转换,这些数据都是 ELF 的头部数据:
[C++] 纯文本查看 复制代码
/* Fix endianness in ELF header */
    hdr->e_type      = TO_NATIVE(hdr->e_type);
    hdr->e_machine   = TO_NATIVE(hdr->e_machine);
    hdr->e_version   = TO_NATIVE(hdr->e_version);
    hdr->e_entry     = TO_NATIVE(hdr->e_entry);
    hdr->e_phoff     = TO_NATIVE(hdr->e_phoff);
    hdr->e_shoff     = TO_NATIVE(hdr->e_shoff);
    hdr->e_flags     = TO_NATIVE(hdr->e_flags);
    hdr->e_ehsize    = TO_NATIVE(hdr->e_ehsize);
    hdr->e_phentsize = TO_NATIVE(hdr->e_phentsize);
    hdr->e_phnum     = TO_NATIVE(hdr->e_phnum);
    hdr->e_shentsize = TO_NATIVE(hdr->e_shentsize);
    hdr->e_shnum     = TO_NATIVE(hdr->e_shnum);
    hdr->e_shstrndx  = TO_NATIVE(hdr->e_shstrndx);

再继续往下:
[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;
    }

上面,hdr 指向 vmlinux.o mmap() 出来后的空间的起始地址,e_shoff 用来指向 “节区头部表格”( sections header table );if 判断里检查这个偏移是否出错,正常情况下编译出来的 vmlinux.o 不会导致这样的错误,如若出错则返回退出。

继续,
[C++] 纯文本查看 复制代码
    for (i = 0; i < hdr->e_shnum; i++) {
        sechdrs[i].sh_name      = TO_NATIVE(sechdrs[i].sh_name);
        sechdrs[i].sh_type      = TO_NATIVE(sechdrs[i].sh_type);
        sechdrs[i].sh_flags     = TO_NATIVE(sechdrs[i].sh_flags);
        sechdrs[i].sh_addr      = TO_NATIVE(sechdrs[i].sh_addr);
        sechdrs[i].sh_offset    = TO_NATIVE(sechdrs[i].sh_offset);
        sechdrs[i].sh_size      = TO_NATIVE(sechdrs[i].sh_size);
        sechdrs[i].sh_link      = TO_NATIVE(sechdrs[i].sh_link);
        sechdrs[i].sh_info      = TO_NATIVE(sechdrs[i].sh_info);
        sechdrs[i].sh_addralign = TO_NATIVE(sechdrs[i].sh_addralign);
        sechdrs[i].sh_entsize   = TO_NATIVE(sechdrs[i].sh_entsize);
    }

在上面代码中,e_shnum 成员表示节区头部表里条目个数,换句话来说就是有多少个节区。这里遍历所有节区头部,主要做的事情仍然是根据平台的不同对数据进行相应的修正。一般情况下,在 x86 中,TO_NATIVE 宏什么都不做,那么在此就只是表示简单的一个赋值。

继续往下:
[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[i].sh_type == SHT_NOBITS;

        if (!nobits && sechdrs[i].sh_offset > info->size) {
            fatal("%s is truncated. sechdrs[i].sh_offset=%lu > "
                  "sizeof(*hrd)=%zu\n", filename,
                  (unsigned long)sechdrs[i].sh_offset,
                  sizeof(*hdr));
            return 0;
        }
        /*从节区里取出该节区的名字;sh_name 在节区头部表中,
           是节区名字字符串的索引*/
        secname = secstrings + sechdrs[i].sh_name;
        if (strcmp(secname, ".modinfo") == 0) {
            if (nobits)
                fatal("%s has NOBITS .modinfo\n", filename);
            info->modinfo = (void *)hdr + sechdrs[i].sh_offset;
            info->modinfo_len = sechdrs[i].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[i].sh_type != SHT_SYMTAB)    
            continue;

/*找到symtab 或者是到达最后一个section 都没找到,这里是找到了symtab*/

        /* sh_offset 节区的第一个字节与文件头之间的偏移*/
        info->symtab_start = (void *)hdr + sechdrs[i].sh_offset;
        info->symtab_stop  = (void *)hdr + sechdrs[i].sh_offset
                                     + sechdrs[i].sh_size;

        /* sh_link 头部表索引,链接到另一个section,这里是strtab */
        /* sh_link 的值根据 sh_type 来决定。当sh_type 为 SHT_SYMTAB 时,sh_link由操作系统来指定 */
        info->strtab       = (void *)hdr +
                         sechdrs[sechdrs[i].sh_link].sh_offset;    //获得strtab 节区指针
    }

上面代码主要用来查找 symtab 这个节区。symtab 节区。symtab 称之为 “符号表” (Symbol Table)。在目标文件中,符号表用来定位,重定位程序中符号定义以及引用的信息。符号表的整体结构是一个 Elf32_Sym 的结构体数组,Elf32_Sym 结构定义在 elf.h 文件中,如下表示:
[C++] 纯文本查看 复制代码
/* Symbol table entry.  */

typedef struct
{
  Elf32_Word    st_name;                /* Symbol name (string tbl index) */
  Elf32_Addr    st_value;               /* Symbol value */
  Elf32_Word    st_size;                /* Symbol size */
  unsigned char st_info;                /* Symbol type and binding */
  unsigned char st_other;               /* Symbol visibility */
  Elf32_Section st_shndx;               /* Section index */
} Elf32_Sym;


在查找 symtab 的代码块中,secstrings 变量指向 .shstrtab 节区首字节,.shstrtab 节区存储着各个节区的名字,在 http://www.groad.net/bbs/read.php?tid-4228.html 这里有一个遍历 .shstrtab 节区并输出各个节区符号的例子。

节区头部有一个 sh_type 成员,它表示对应的节区类型,比如众所周知的 .bss 节区的类型为 SHT_NOBITS 。另外,节区头部还有一个成员 sh_name, 它是节区名字字符串表( .strshtab )的索引。所以由此看出,secname 变量就是用来存放节区的名字的。

在 vmlinu.o 中没有 .modinfo 这个节区,所以上面的 if 判断不成立。通过 readelf 工具辅助可以看到,默认配置的 vmlinu.o 里存在 __ksymtab 和 __ksymtab_gpl 这两个节区,所以 info->export_sec = i; 和 info->export_gpl_sec = i; 分别将这两个节区的索引记录下来,以备后用。

在 if (sechdrs.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() 这个函数的作用。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
沙发
 楼主| 发表于 2011-8-2 22:09:59 | 只看该作者
分析完 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() 函数。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
板凳
 楼主| 发表于 2011-8-3 11:47:26 | 只看该作者

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[i]);
        /* We want to process only relocation sections and not .init */
        if (sechdrs[i].sh_type == SHT_RELA)     
            section_rela(modname, elf, &elf->sechdrs[i]);    
        else if (sechdrs[i].sh_type == SHT_REL)    
            section_rel(modname, elf, &elf->sechdrs[i]);
    }
}

上面程序中,遍历整个节区头部表格,如果遇到 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 是一个常量补齐,用来计算将被填充到可重定位字段的数值。

4918

主题

5880

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34387
地板
 楼主| 发表于 2011-8-3 13:57:39 | 只看该作者

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 时,该值表示重定位所使用节区的节区头部索引。所以该函数返回的是重定位信息所在可重定位节区中的位置。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-28 08:50 , Processed in 0.083705 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表