ELF解析05 - hash表

这个表叫 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

  • 34
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值