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

贴一段源码:

ini 复制代码
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 的符号是:

css 复制代码
symtab[345] [U] _edata

0x15B 的符号是:

css 复制代码
symtab[347] [U] __bss_start

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

css 复制代码
symtab[346] [U] _end
相关推荐
桃园码工3 分钟前
15_HTML5 表单属性 --[HTML5 API 学习之旅]
前端·html5·表单属性
百万蹄蹄向前冲43 分钟前
2024不一样的VUE3期末考查
前端·javascript·程序员
轻口味1 小时前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami1 小时前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
吃杠碰小鸡2 小时前
lodash常用函数
前端·javascript
emoji1111112 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼2 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250032 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235952 小时前
web复习(三)
前端