GNU IFUNC,间接函数介绍


注:本文主体上是翻译了https://sourceware.org/glibc/wiki/GNU_IFUNC,个别地方有基于个人理解的解释

2005年,GNU IFUNC开始被支持,并通过IFUNC属性添加到了GCC中。但很长一段时间里这个特性都没有合适的文档,而且在实际使用中有很多的隐形问题,使用起来比较困难。时至今日,一些问题也是依然存在的。具体可查看相关讨论(见文末资源[5])。

目前来讲主要是glibc在使用该特性,当然个人感觉似乎也并不值得被推荐使用。。

GNU标准库glibc使用这个特性实现了一些内存和字符串函数的多个版本,包括memmove、memset、memcpy、strcmp、strstr等。

在开始之前,几个简单的概念如下:

  • 全局符号介入

    当一个符号需要被加入全局符号表时,如果相同的符号名已经存在,则后面加入的相同符号会被忽略

  • LD_PRELOAD

    Linux下的一个环境变量,其指定的共享库无论使用与否都会被优先装载

  • 延迟绑定(PLT)

    对符号的解析不在装载时进行,而是在函数实际被调用时进行。

    其实就是多了一个间接跳转,当调用函数时通过elf中的.plt来交由动态链接器对符号进行重定位与解析,之后将结果填入.got.plt。

间接函数简介(IFUNC)

glibc支持间接函数(IFUNC),其作为一个特性支持开发者来创建对一个既有函数的多个实现,并且通过开发者编写的解析函数(resolver)在运行时来选择具体的某个实现。

在程序启动时动态装载器会调用解析函数来解析该程序将选择的实现,一旦选择之后,该函数在整个进程的生命周期都将使用被选择的实现。

间接函数的设计目标

以下是设计中的意图声明,并不反映当前实现的内容。实际上整个文档都是可以讨论的orz。

  • 最好可以跨编译单元解析 — 实际不可以,可见 当前的局限性 小节

    IFUNC解析函数最好可以调用其他编译单元的代码以满足更复杂的返回表达。

    此外,此类函数可能存在对一些其他符号的隐式依赖关系,故而需要在调用之前先初始化依赖符号的定义。

    这就要求IFUNC的解析顺序应尽量与深度优先遍历DT_NEEDED和重定位数据的顺序一致,从而能够使得所依赖的对象会在调用IFUNC之前被初始化。

  • IFUNC应不受全局符号介入的影响 — 实际上仍会受其影响,若为模块内的静态类型则不受影响,可以参考文末资源[7]

    不管是通过环境变量LD_PRELOAD还是改变装载库的装载顺序,符号介入都是存在的,但是那种不是所期望的函数替换了原本想调用的函数的情况不应该对IFUNC造成影响。

    也就是说,IFUNC类型的函数,在运行时需要能够识别到其符号在装载时被提前填入了,并且能够重新调整合适的符号顺序以满足本来的需求。

  • IFUNC不应破坏延迟符号解析,但个别符号可能需要提前解析

    深度优先遍历装载依赖的共享库(DT_NEEDED)与重定位数据不应破坏延迟符号解析,但是由于IFUNC解析函数需要一些符号,故而可能使得一些原本不必在程序启动时就进行解析的符号被预先解析。

间接函数的工作原理

在传统的二进制程序中,我们可以在编译阶段或者运行阶段来选择哪些函数被调用。

但也有例外情况,比如一个源代码中调用了memcpy函数,这样的一种调用可能是指向文件内的函数或者库函数,此外,由于ELF符号介入的存在(ELF symbol interposition,即全局符号介入),意味着任何被预装载的共享库若定义了memcpy都会导致源代码中的memcpy调用被绑定到预装载的共享库中的memcpy(忽略可以再次进行选择的dlmopen独立命名空间),从而使得源代码调用的memcpy即调用的共享库中的memcpy

尽管ELF允许符号介入,但是默认是基于共享库的装载与符号解析的顺序,而IFUNC可以使所选择的符号进一步区分所选的实现。STT_GNU_IFUNC类型的符号与其他普通的符号不同,这类符号指向的是解析函数,并且所有该类函数的调用都会推迟到运行时。

对该类函数的引用是通过R_*_IRELATIVE 重定位间接处理的,该重定位返回运行解析器的结果,即返回指向所选实现的函数指针。

当调用该类函数时,动态装载器会运行解析函数来决定哪个具体的实现被使用,修改对应的符号.got.plt等之后,从解析函数返回后目标函数会被运行。

如何定义并使用间接函数?

在glibc里,关键字__attribute__ 用来在声明(函数、变量等)的过程中来定制编译属性,此关键字后面通过双括号来指定属性名称及赋值等(__attribute__(()))。

ifunc即其属性之一,该属性被用来标记一个函数在ELF中的函数符号是STT_GNU_IFUNC(可以通过readelf -s查看一个elf的符号信息),即间接函数。这种类型的函数使得符号值的重定位在装载时被动态决定,从而可以针对当时的特定处理器或其他系统特性等选择的函数版本。

要使用该属性,首先需要定义并实现至少一个有效的函数以及一个解析函数(resolver)用来返回被选择的函数指针。解析函数的声明则要求返回void函数指针的函数:

void *my_memcpy (void *dst, const void *src, size_t len)
{
...
}

static void (*resolve_memcpy (void)) (void)
{
	return my_memcpy; // we'll just always select this routine
}

第二步,需要在与解析器函数相同的编译单元中定义间接函数(IFUNC):

void *memcpy (void *, const void *, size_t)
    __attribute__ ((ifunc ("resolve_memcpy")));

最后,用户可以在其头文件中导入该类函数,使得用户在无需知道具体实现的情况下将其作为常规函数调用:

extern void *memcpy (void *, const void *, size_t);

间接函数不能是weak类型的符号。

该属性的运行环境:

  • 执行环境:glib 2.11.1及以上
  • 开发环境:在Binutils 2.20.1及以上以及4.6以上的GCC编译器

当前的局限性/使用建议

实际上对IFUNC的使用有很多限制,这些限制并不完全清楚,文档也需要更新。

首先,有两个明显的限制:(感觉翻译不准确故而保留)

  • Requirement (a): Resolver must be defined in the same translation unit as the implementations.
  • Requirement (b): Cannot be weakly defined functions.

由于IFUNC的解析可能在动态加载程序启动过程的早期发生,尤其是在使用-Wl,z,now编译应用程序时(要求在应用程序启动之前解析所有符号),故而实际使用时的产生的限制更多:

  • LD_BIND_NOW=1或这--Wl,z,now有效时,就意味着启动时必须立即解析符号。但是在需要进行外部函数调用的情况下,如果该调用尚未初始化,则可能失败(基于PLT的重定位会延迟处理)。

    例如,在用-Wl,z,now 构建的IFUNC解析器中调用strlen 可能会导致segmentation fault,因为PLT还没有被解析。

  • 由于-fstack-protector-all或类似的保护(例如asan)等编译选项可能需要完成一些早期配置,故而解析器不能与它们一起编译。

  • 延迟绑定的存在(没有设置LD_BIND_NOW=1-Wl,z,NOW)意味着任何时候解析器都可能被调用,因此必须与它实现的函数一样安全。

要是使用IFUNC的过程中有问题可以联系咨询libc-alpha@sourceware.org

优化方向

  • 在glibc手册中记录glibc支持的所有机器的所有解析器函数限制。

  • 在gcc手册中的文档说明环境要求。

  • 逐步增强IFUNCs以绕过上述一些限制。

资源

  • 1 http://www.x86-64.org/documentation_folder/abi-0.99.pdf (4.3 Symbol Table, STT_GNU_IFUNC)
  • 2 https://sites.google.com/site/x32abi/documents (ifunc.txt)
  • 3 https://gcc.gnu.org/onlinedocs/gcc-5.3.0/gcc/Function-Attributes.html#index-g_t_0040code_007bifunc_007d-function-attribute-3095 (ifunc attribute)
  • 4 https://sourceware.org/ml/libc-alpha/2015-11/msg00108.html (Szabolcs Nagy: using IFUNC outside the libc)
  • 5 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70082(相关讨论)
  • 6 https://jasoncc.github.io/gnu_gcc_glibc/gnu-ifunc.html(实践讨论)
  • 7 https://alittleresearcher.blogspot.com/2017/04/gnu-indirect-function-mechanism.html(实践讨论)
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值