ELF解析05 - hash表

发布时间:2024年01月17日

这个表叫 hash 表,它的作用是用于快速索引符号在符号表中的位置。

先看一张图:

查找一个符号的位置步骤如下:

  • 首先,计算出符号字符串的 hash 值

  • 然后,根据 hash 值获取 bucket 索引 index

  • bucket[index] 里面储存的是符号表的 index,但是由于 hash 冲突问题,所以又建立了一个 chain 来当链表结构。

    chain 里面储存的是符号的 hash 值,但是只使用了前 31 位,后 1 位是用于表示该链表是否结束,奇数表示结束,偶数表示后面还有元素

  • 拿到 sym_tab[index] 后,可得 chain[index],然后根据 chain[index] 的值来与符号 hash 值比较,相等则找到,未相等则判断是否为链表结尾,开始循环遍历。

结构分析

.gnu.hash节由4部分构成:

Header

谨记:header的内容决定下面3部分内容的大小。header又分别由4个条目构成,每个条目大小是固定的4个字节,这4个条目分别是:

  • nbuckets。4个字节,决定了第3部分即Hash Buckets的条目数量,1个Hash Buckets占4个字节,如果nbuckets是2,则Hash Bucekts就占8个字节,依次类推,所以说nbuckets的值决定了Hash Bucekts的大小。如上图我的示例文件nbuckets是2。

  • symndx。4个字节,symndx是动态符号表中可通过哈希表访问的第一个符号的索引。symndx决定了第4部分即Hash Values的条目数量,1个Hash Values占4个字节,(dynsymcount – symndx)*4的结果就是Hash Values的实际大小,dynsymcount如果有人不清楚是什么,这里我明确告诉你们,dynsymcount表示的是动态符号表的条目数量。

  • maskwords。4个字节,决定了第2部分即Bloom Filter的条目数量,1个Bloom Filter在32位系统占4个字节,在64位系统占8个字节,我是64位系统所以是8个字节,maskwords如果是1,那么Bloom Filter就是8个字节,如果maskwords是2,那么Blooom Filter就是2*8=16,那么Bloom Filter的实际大小就是16个字节,以此类推,所以说maskwords的值决定了Bloom Filter的大小。

  • shift2。4个字节,Bloom Filter使用的移位计数。

Bloom Filter

32位系统单个条目占4个字节,64位系统占8个字节,条目数量由第一部分即header里的maskwords的值决定,我这儿是64位系统,Bloom Filter的大小是maskwords * 8。

Hash Buckets

单个条目占4个字节,条目数量由第一部分即header里的nbuckets的值决定,Hash Buckets的大小是nbuckets * 4。

Hash Values

单个条目占4个字节,条目数量由第一部分即header里的symndx的值决定(准确说是由dynsymcount和symndx的值决定)。Hash Values的大小是(dynsymcount – symndx) * 4。

例子分析

我们去到 d_ptr 的位置,即 3090:

  • 第 1 个 4 字节,表示 bucket 的size,这里为3

  • 第 3 个 4 字节,值为 1,它用于计算 bucket 的起始位置。计算方式为0x3090 + 0x10 + 0x8

贴一段源码:

case?DT_GNU_HASH:
????????gnu_nbucket_?=?reinterpret_cast<uint32_t*>(load_bias?+?d->d_un.d_ptr)[0];
????????//?skip?symndx
????????gnu_maskwords_?=?reinterpret_cast<uint32_t*>(load_bias?+?d->d_un.d_ptr)[2];
????????gnu_shift2_?=?reinterpret_cast<uint32_t*>(load_bias?+?d->d_un.d_ptr)[3];

????????gnu_bloom_filter_?=?reinterpret_cast<ElfW(Addr)*>(load_bias?+?d->d_un.d_ptr?+?16);
????????gnu_bucket_?=?reinterpret_cast<uint32_t*>(gnu_bloom_filter_?+?gnu_maskwords_);
????????//?amend?chain?for?symndx?=?header[1]
????????gnu_chain_?=?gnu_bucket_?+?gnu_nbucket_?-
????????????reinterpret_cast<uint32_t*>(load_bias?+?d->d_un.d_ptr)[1];

所以 bucket 起始位置在下图黄色位置:

可以看到从黄色开始一共有 6 项,前面说过, bucket size 为 3,所以前 3 项是 bucket,由于 chain 的大小与 bucket 一样,所以后3项是 chain 。

bucket 的第一项是 00000159,这个就是 sym_tab 的索引,实际上我们看第二个 4 字节位置,它也是 00000159,这个值其实表示的是 chain 表的第一个有效索引,由于 sym_tab 与 chain 大小一致,所以这个索引的值也是一致的。

有一点需要注意,hash 表是用于linker快速索引符号的,而这些符号的合集是符号表的子表,所以只会使用到符号表的部分索引。这也是第一张图里面,在 sym_tab 与 chain 表中都留出了空白的原因。在我们的例子中,hash表里面就3个元素。

0x159 的符号是:

symtab[345]?[U]?_edata

0x15B 的符号是:

symtab[347]?[U]?__bss_start

由于 chain 的 0x159 位置储存的 hash 值是 ECD54542,是一个偶数。所以如果符号不是 _edata,还会去 chain 的 0x160 位置去寻找,其对应的符号是:

symtab[346]?[U]?_end

文章来源:https://blog.csdn.net/a5right/article/details/135643061
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。