diff --git a/articles/20220621-ifunc.md b/articles/20220621-ifunc.md new file mode 100644 index 0000000000000000000000000000000000000000..fb3e1da9eed0edc9a9bf2ac06585472cf6d0b75c --- /dev/null +++ b/articles/20220621-ifunc.md @@ -0,0 +1,353 @@ +![](./diagrams/linker-loader.png) + +文章标题:**GNU IFUNC 介绍(RISC-V 版)** + +- 作者:汪辰 +- 联系方式: / + +文章大纲 + + +- [1. 参考](#1-参考) +- [2. 什么是 IFUNC](#2-什么是-ifunc) +- [3. IFUNC 的用法](#3-ifunc-的用法) + - [3.1. 传统方式(非 IFUNC)](#31-传统方式非-ifunc) + - [3.2. GNU IFUNC](#32-gnu-ifunc) +- [4. IFUNC 的实现细节分析](#4-ifunc-的实现细节分析) + - [4.1. Preemptible IFUNC](#41-preemptible-ifunc) + - [4.2. Non-preemptible IFUNC](#42-non-preemptible-ifunc) + - [4.3. 简单总结](#43-简单总结) + + + + +# 1. 参考 + +本文主要参考了如下内容: + +- 【参考 1】[GNU_IFUNC from glibc wiki][1] +- 【参考 2】[6.33.1 Common Function Attributes from gcc 11.3.0 user manual][2] +- 【参考 3】[GNU indirect function 的運作機制][3] +- 【参考 4】[GNU indirect function][5] + +# 2. 什么是 IFUNC + +借鉴 [【参考 2】][2] 的原话: + +> The GNU indirect function support (IFUNC) is a feature of the GNU toolchain that allows a developer to create multiple implementations of a given function and to select amongst them at runtime using a resolver function which is also written by the developer. The resolver function is called by the dynamic loader during early startup to resolve which of the implementations will be used by the application. Once an implementation choice is made it is fixed and may not be changed for the lifetime of the process. + +IFUNC,全称为 indirect function,是由 GNU toolchain 实现的一个特性,特性描述可以通过以下关键词来掌握: + +- 可以为某个函数创建多个实现 +- 在运行时使用解析器函数(resolver function)进行选择。 +- 解析器函数由动态加载程序调用,选择实际的实现。(注:静态链接下同样也支持 IFUNC,但本文只分析动态链接下的处理) +- 选择完成后,函数实现就是固定的,在进程的生存期内不得更改。 + +下面就围绕这些关键词,并结合一些 RISC-V 的例子来理解 IFUNC 究竟是什么、如何使用它以及背后实现的机制。 + +# 3. IFUNC 的用法 + +## 3.1. 传统方式(非 IFUNC) + +假设我们要写一个程序,需要实现一个函数 myfunc,这个函数根据我们配置的不同执行不同的处理,一般情况下我们会写成如下这般: + +(注,具体代码实例参考 [这里][4],本文在讲解时为演示方便,截取的代码会稍有调整。) + +```cpp +static int myfunc_1 (int a) +{ + printf("myfunc_1 is called\n"); + return a + 100; +} + +static int myfunc_2 (int a){ + printf("myfunc_2 is called\n"); + return a + 200; +} + +int myfunc(int a) +{ + printf("myfunc is called\n"); + if (1 == get_configure()) { + return myfunc_1(a); + } else { + return myfunc_2(a); + } +} + +void test_myfunc() +{ + for (int i = 0; i < 3; i++) { + printf("myfunc returns %d\n", myfunc(1)); + } +} +``` + +`myfunc()` 中根据 `get_configure()` 函数返回的配置情况选择执行 `myfunc_1()` 或者 `myfunc_2()`。我们将这个函数实现在一个共享库中,并封装了一个 `test_myfunc()` 来反复调用 `myfunc()` 这个函数三次。然后写一个可执行程序来调用 `test_myfunc()`。之所以再写一个 `test_myfunc()` 来封装调用 `myfunc()`,主要是为了后面演示 IFUNC 的需要。读者可以读完本文后自行体会。 + +```cpp +extern void test_myfunc(); + +int main() +{ + printf("main ......\n"); + test_myfunc(); + return 0; +} +``` + +然后编译执行,我这里使用的 riscv gcc 版本是 v11.1.0,qemu 用的是 v6.2.0。 + +```console +$ riscv64-unknown-linux-gnu-gcc -shared -fPIC no_ifunc.c -o no_ifunc.so +$ riscv64-unknown-linux-gnu-gcc main.c -DMY_CONFIG=1 no_ifunc.so -o a.out +``` +`MY_CONFIG` 定义为 1 后 `get_configure()` 返回 1,所以调用 `myfunc()` 就会最终触发 `myfunc_1()` + +如果我们希望 `myfunc()` 触发 `myfunc_2()`,可以在编译 `a.out` 时指定 `MY_CONFIG=2` + +输入如下命令执行程序,其中 `-L` 指定动态链接器的路径, `-E LD_LIBRARY_PATH=.` 指示动态链接器到当前路径下去加载 `a.out` 所依赖的 `no_ifunc.so`。 + +```console +$ qemu-riscv64 -L /home/u/ws/test-gcc/install/sysroot/ -E LD_LIBRARY_PATH=. ./a.out +main ...... +myfunc is called +myfunc_1 is called +myfunc returns 101 +myfunc is called +myfunc_1 is called +myfunc returns 101 +myfunc is called +myfunc_1 is called +myfunc returns 101 +``` + +我们主要来关注一下程序的输出,我们看到三次执行 myfunc 导致 "myfunc is called" 被打印了三次,可见 `myfunc()` 函数中的 if-else 逻辑也被执行了三次。那么有没有更快的处理机制呢,假设 `MY_CONFIG` 的信息在程序启动后是不变的,那么最好的方法是让程序在第一次执行 myfunc 后就记住 myfunc 实际对应的是 `myfunc_1()` 或者 `myfunc_2()`,后面再执行时就不用每次都判断了,这对于那种经常要被调用的函数来说是一种运行上的优化。 + +## 3.2. GNU IFUNC + +在实际工作中此类需求也很广泛,譬如我们在应用态编程经常使用的 `memcpy()` 这类函数,在不同的架构下针对不同的处理器指令扩展存在多个优化版本,那么这个 `memcpy()` 函数就好比这里的 `myfunc()` 函数。 + +为了解决这样的现象,从 2009 年起,GNU C 函式库(glibc,其中包含了动态链接器)开始对 ELF 格式进行扩展,增加新的符号类型 `STT_GNU_IFUNC`,以及新的重定位类型 `R_*_IRELATIVE`(这里的 `*` 表示根据硬件处理器架构不同存在变化,譬如如果是 RISC-V,那就是 `R_RISCV_IRELATIVE`,如果是 64 位的 X86,那就是`R_X86_64_IRELATIVE`。本文以 RISC-V 为例,所以后面就直接写成 `R_RISCV_IRELATIVE`,但读者需要知道针对不同的 ARCH,这个 `*` 是会变化的);同时为了支持 IFUNC 这个新特性,GNU Binutils(包括链接器 ld 和汇编器 as)也对此进行了扩展。 + +从历史上来看,在 Glibc 之后,编译器 GCC 也从版本 4.6 开始增加了对 IFUNC 的支持,并在版本 4.8 提供了内建函数 `__builtin_cpu_is()` 和 `__builtin_cpu_supports()`,来协助应用程序的作者判断 CPU 的类型。 + +下面我们来根据 GNU IFUNC 的设计要求,实现一个利用 IFUNC 特性的 `myfunc()`。为了让 `myfunc()` 函数能够达到我们前面希望只做一次选择,后面反复调用时记住选择直接调用已经选择好的目标函数。我们必须提供一个函数来选择实际要使用的函数,在 IFUNC 的术语中我们称呼这个函数为解析器(resolver)。也就是下面代码中的 `myfunc_resolver()`。同时我们利用编译器 GCC 提供的函数属性(function attribute)关键字 `ifunc` 来修饰 `myfunc()` 函数,作用相当于将这个 resolver 和我们的 `myfunc()` 目标函数实现了绑定。新的代码如下,我们会把它编译成共享库 `ifunc.so` + +```cpp +int myfunc(int) __attribute__((ifunc ("myfunc_resolver"))); + +int (*myfunc_resolver(void))(int) +{ + printf("myfunc_resolver is called\n"); + if (1 == get_configure()) { + return myfunc_1; + } else { + return myfunc_2; + } +} +``` + +具体的代码和构建命令我就不在文章中粘贴了,感兴趣的读者可以去看本文附带的 [代码仓库][4],执行 `make ifunc && make run` 就会看到运行结果。观察程序输出如下: + +```console +main ...... +myfunc_resolver is called +myfunc_1 is called +myfunc returns 101 +myfunc_1 is called +myfunc returns 101 +myfunc_1 is called +myfunc returns 101 +``` + +可见 `myfunc_resolver()` 只会被调用一次,后面显示三次调用 `myfunc()` 直接调用了 `myfunc_1()`。 + +从编写代码的角度来说,详细的请参考 [【参考2】][2] 中针对 `ifunc` 章节的说明。其中特别需要注意的是 resolver 函数的定义方式,摘抄如下: + +> To use this attribute, first define the implementation functions available, and a resolver function that returns a pointer to the selected implementation function. The implementation functions' declarations must match the API of the function being implemented, the resolver's declaration is be a function returning pointer to void function returning void: + +上面的意思是: + +- 首先我们要定义一个 `implementation functions`。这对应于我们例子中的 `myfunc_1()` 和 `myfunc_2()`,这些函数的定义要和我们要实现的函数原型 `int myfunc(int)` 保持一致。同时为让大家深刻理解上面话中后半句的意思,我们例子中特别写了一个和 [【参考2】][2] 上不一样的原型, `int myfunc(int)`,接受一个 int 的参数,返回值是 int 类型。 +- 关键是我们要定义一个 resolver 函数,即我们例子中的 `myfunc_resolver()`。这个函数的参数是 void,此函数的返回值是一个函数指针类型,且这个函数指针所指向的函数原型要符合 `implementation functions` 定义的形式。这个函数的原型声明用 C 语言写起来还是蛮费解的,详细的描述可以参考代码中的注释。 + +# 4. IFUNC 的实现细节分析 + +接下来,我们来看看 GNU IFUNC 的实现细节,这部分需要有一些对于重定位机制的背景知识。 + +在实现上要分两种情况讨论,Preemptible IFUNC 和 Non-preemptible IFUNC。所谓 Preemptible,指的是在一个 ELF 文件格式的 shared object 中,一个 non-local 的 STB_DEFAULT 符号默认为 preemptible(interposable),即运行时可被替换。 + +## 4.1. Preemptible IFUNC + +参考 [ifunc.c](./code/20220621-ifunc/ifunc.c),当我们构建 ifunc.so 时如果定义了 `IFUNC_PREEMPTIBLE` 则 myfunc 这个符号就是 Preemptible,而且又被 ifunc 这个 attribute 所修饰,所以就是我们所谓的 Preemptible IFUNC 场景,等价如下代码: + +```cpp +int myfunc(int) __attribute__((ifunc ("myfunc_resolver"))); +``` + +从链接器的角度来看,Preemptible IFUNC 的处理和常规函数调用没有什么不同。 + +- 【1】链接器在生成的 so 中将 myfunc 这个符号的符号类型定义为 `STT_GNU_IFUNC`,以示和其他普通符号类型 `STT_FUNC` 的区别,这个标识会被加载器或者动态链接器所使用。同时因为在 Preemptible IFUNC 场景下 myfunc 这个符号是外部可见的,所以我们可以使用 `readelf --dyn-syms` 在 `.dynsym` 中看到这个标志为 `STT_GNU_IFUNC` 的 myfunc。同时注意记住其对应的 Value 字段的值是 `00000000000005d6`。这个地址指向谁,我们在后面 【4】中会涉及。 + + ```console + $ riscv64-unknown-linux-gnu-readelf --dyn-syms ifunc.so + + Symbol table '.dynsym' contains 11 entries: + Num: Value Size Type Bind Vis Ndx Name + 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND + 1: 00000000000004f0 0 SECTION LOCAL DEFAULT 10 .text + 2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...] + 3: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND get_configure + 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.27 (2) + 5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.27 (2) + 6: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.27 (2) + 7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...] + 8: 00000000000005d6 62 FUNC GLOBAL DEFAULT 10 myfunc_resolver + 9: 0000000000000614 76 FUNC GLOBAL DEFAULT 10 test_myfunc + 10: 00000000000005d6 62 IFUNC GLOBAL DEFAULT 10 myfunc + ``` + +- 【2】链接器(ld)为 myfunc 这个符号设置 `R_RISCV_JUMP_SLOT` 重定位条目,我们可以执行 `readelf -r` 命令看一下 GOT 中的重定位项: + + ```console + $ riscv64-unknown-linux-gnu-readelf -r ifunc.so + [略过一些输出] + Relocation section '.rela.plt' at offset 0x428 contains 4 entries: + Offset Info Type Sym. Value Sym. Name + Addend + 000000002018 000300000005 R_RISCV_JUMP_SLOT 0000000000000000 get_configure + 0 + 000000002020 000400000005 R_RISCV_JUMP_SLOT 0000000000000000 puts@GLIBC_2.27 + 0 + 000000002028 000500000005 R_RISCV_JUMP_SLOT 0000000000000000 printf@GLIBC_2.27 + 0 + 000000002030 000a00000005 R_RISCV_JUMP_SLOT myfunc() myfunc + 0 + ``` + +- 【3】链接器(ld)同时为 myfunc 这个函数创建 PLT 条目。我们可以执行 `objdump -d` 命令看一下 myfunc 的 PLT 条目,从反汇编的 ld 指令中可以看到,的确是会从 Relocation section 中的 2030 所对应的内存中加载 myfunc 的地址,而这个地址 2030 就对应着上面的重定位项。现在编译链接阶段的准备工作都做好了,就等加载器和动态链接器上场完成剩下的工作。 + + ```console + $ riscv64-unknown-linux-gnu-objdump -d ifunc.so + ifunc.so: file format elf64-littleriscv + + Disassembly of section .plt: + [略过一些输出] + 00000000000004e0 : + 4e0: 00002e17 auipc t3,0x2 + 4e4: b50e3e03 ld t3,-1200(t3) # 2030 + 4e8: 000e0367 jalr t1,t3 + 4ec: 00000013 nop + ``` + +- 【4】加载程序在运行时会执行动态链接器,因为是 PLT,按照延迟绑定的原理,在第一次调用 `myfunc()` 的时候触发重定位过程,通过检查 `R_RISCV_JUMP_SLOT` 对应的重定位项发现对应的符号 myfunc 的符号类型是 `STT_GNU_IFUNC`。如果是这样,动态链接器就不会简单地像处理 `STT_FUNC` 那样用加载后的实际地址填充 GOT 条目,而是会把 `STT_GNU_IFUNC` 的符号所指向的位址理解成 resolver 函数的地址,它会根据该地址调用这个 resolver 函数,并把该函数返回的结果(也是一个地址),用来做为符号绑定的的地址,并将其放入 GOT 条目中。 + + 再次运行 `objdump -d` 命令,这次我们关注一下 resolver 函数,即例子中的 `myfunc_resolver()` 的地址,正是 00000000000005d6。 + + ```console + $ riscv64-unknown-linux-gnu-objdump -d ifunc.so + [略过一些输出] + 00000000000005d6 : + 5d6: 1141 addi sp,sp,-16 + 5d8: e406 sd ra,8(sp) + 5da: e022 sd s0,0(sp) + [略过一些输出] + ``` + + 所以大家回头可以去看一下 IFUNC 例子的打印输出,我又复制一份在下面。`"myfunc_resolver is called"` 这句话就是动态链接器重定位打印出来的,第一次绑定成功后,`myfunc_resolver()` 返回的地址,譬如这里是 `myfunc_1()` 的地址被填入 GOT,所以后面两次调用 `myfunc()` 就直接执行 `myfunc_1()`,不会再走 `myfunc_resolver()` 的逻辑了。 + + ```console + main ...... + myfunc_resolver is called + myfunc_1 is called + myfunc returns 101 + myfunc_1 is called + myfunc returns 101 + myfunc_1 is called + myfunc returns 101 + ``` + +## 4.2. Non-preemptible IFUNC + +Non-preemptible 的 IFUNC 工作机制和 Preemptible 的稍有不同。首先我们改造一下原来的例子,如果要让 myfunc 这个符号成为 Non-preemptible 的,最简单的方法就是定义 myfunc 为 local 的,即加上关键字 static,如下: + +参考 [ifunc.c](./code/20220621-ifunc/ifunc.c),当我们构建 ifunc.so 时不定义 `IFUNC_PREEMPTIBLE` 则 myfunc 这个符号就是 Non-Preemptible,而且又被 ifunc 这个 attribute 所修饰,所以就是我们所谓的 Non-preemptible IFUNC 场景。代码等同如下: + +```cpp +static int myfunc(int) __attribute__((ifunc ("myfunc_resolver"))); +``` + +我们先关心一下符号表中 myfunc 的信息,由于现在它是一个 local 符号,很容易就猜到我们在 `.dynsym` section 中是看不到这个符号了,执行一下 `readelf --dyn-syms` 验证一下我们的推理: +```console +$ riscv64-unknown-linux-gnu-readelf --dyn-syms ifunc_static.so + +Symbol table '.dynsym' contains 10 entries: + Num: Value Size Type Bind Vis Ndx Name + 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND + 1: 00000000000004d0 0 SECTION LOCAL DEFAULT 10 .text + 2: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...] + 3: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND get_configure + 4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND puts@GLIBC_2.27 (2) + 5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.27 (2) + 6: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.27 (2) + 7: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMC[...] + 8: 00000000000005f4 76 FUNC GLOBAL DEFAULT 10 test_myfunc + 9: 00000000000005b6 62 FUNC GLOBAL DEFAULT 10 myfunc_resolver +``` + +虽然我们用 `readelf -s` 查看 `.symtab` section 还可以看到 myfunc。 + +```console +$ riscv64-unknown-linux-gnu-readelf -s ifunc_static.so +[略过一些输出] +Symbol table '.symtab' contains 48 entries: + Num: Value Size Type Bind Vis Ndx Name +[略过一些输出] + 31: 00000000000005b6 62 IFUNC LOCAL DEFAULT 10 myfunc +[略过一些输出] +``` +但是需要注意的是:考虑到 `.symtab` 可能会被 strip 掉,那么这会带来一个问题,如果我们还是在重定位表中用 `R_RISCV_JUMP_SLOT` 标识 myfunc,则因为在 `.dynsym` 中无法查询到这是一个 `STT_GNU_IFUNC` 项,这会导致我们在前面描述的绑定过程中的处理逻辑失效,回忆一下我们执行 resolver 的前提是 `R_RISCV_JUMP_SLOT` 和 `STT_GNU_IFUNC` 同时存在。 + +所以为了解决 Non-preemptible 给我们带来的问题,解决的方法是继续改进 ELF,增加一个新的重定位类型 `R_RISCV_IRELATIV`,这个问题就解决了。 + +我们现在来看一下重定位表的内容 +```console +$ riscv64-unknown-linux-gnu-readelf -r ifunc_static.so +[略过一些输出] +Relocation section '.rela.plt' at offset 0x408 contains 4 entries: + Offset Info Type Sym. Value Sym. Name + Addend +000000002018 000300000005 R_RISCV_JUMP_SLOT 0000000000000000 get_configure + 0 +000000002020 000400000005 R_RISCV_JUMP_SLOT 0000000000000000 puts@GLIBC_2.27 + 0 +000000002028 000500000005 R_RISCV_JUMP_SLOT 0000000000000000 printf@GLIBC_2.27 + 0 +000000002030 00000000003a R_RISCV_IRELATIVE 5b6 +``` + +果然这里出现了一项 `R_RISCV_IRELATIVE`,但注意这里对应这项的 Sym. Name 字段显示的并不是 'myfunc',而是 5b6,而这个值正是 `myfunc_resolver()` 函数的地址。之所以要把这个地址直接放在这里道理也很简单,因为在 Non-preemptible 场景下,我们不可能再像 Preemptible 那样从 `.dynsym` 中获取 myfunc 符号对应的 resolver 的地址,所以就把 resolver 的地址直接填写在这里,方便使用。再次运行 `objdump -d` 验证一下,可以看到 5b6 正是 `myfunc_resolver()` 的地址。 + +```console +$ riscv64-unknown-linux-gnu-objdump -d ifunc_static.so +[略过一些输出] +00000000000005b6 : + 5b6: 1141 addi sp,sp,-16 + 5b8: e406 sd ra,8(sp) + 5ba: e022 sd s0,0(sp) +[略过一些输出] +``` + +现在可以总结一下 Non-preemptible 场景下,GNU 工具链又是如何协作完成对 IFUNC 的处理的呢: + +- 【1】链接器在生成的 so 中为 myfunc 这个符号设置 `R_RISCV_IRELATIVE` 重定位条目,考虑到 myfunc 这个符号是一个 local 符号,不可 export 到 `.dynsym` section 中,所以重定位表中 `R_RISCV_IRELATIVE` 直接对应的就是 resolver 的地址。 +- 【2】链接器(ld)同时为 myfunc 这个函数创建 PLT 条目,这个和 Preemptible 场景没啥区别 +- 【3】加载程序在运行时会执行动态链接器,因为是 PLT,按照延迟绑定的原理,在第一次调用 `myfunc()` 的时候触发重定位过程,通过检查 `R_RISCV_IRELATIVE` 对应的重定位项立即知道对应的符号是所谓的 IFUNC 类型,并根据重定位表中存放的 resolver 地址直接计算映射并调用这个 resolver 函数,并把该函数返回的结果(也是一个地址),用来做为符号绑定的的地址,并将其放入 GOT 条目中。 + +## 4.3. 简单总结 + +讲到这里,让我们结合例子来总结整理一下 GNU IFUNC 实现的机制: + +由于必须在运行时由 resolver 函数来决定最终要使用的函数地址,因此在调用 myfunc 时,无论 Preemptible 还是 Non-preemptible,都会利用 GOT 来预留一项重定位项,用来放置 resolver 函数返回的实际函数的地址,之后再以类似 PLT 的机制来运行。 + +而在进行重定位的时候,如果 IFUNC 函数是 Preemptible 的,则会在 `.dynsym` section 中将该函数的符号标识为 `STT_GNU_IFUNC` 并配合传统的 `R_RISCV_JUMP_SLOT` 来进行重定位;如果 IFUNC 函数是 Non-preemptible 的,则直接标识为 `R_RISCV_IRELATIVE` 方式进行重定位。以上两种情况下的重定位的过程和传统的重定位不同,不是简单地绑定符号映射的地址,而是将符号映射的地址理解为 resolver 的函数地址并根据该地址调用这个 resolver 函数,同时把该函数返回的结果(实际最终选择的地址),用来和符号进行绑定。 + + + +[1]: https://sourceware.org/glibc/wiki/GNU_IFUNC +[2]: https://gcc.gnu.org/onlinedocs/gcc-11.3.0/gcc/Common-Function-Attributes.html#Common-Function-Attributes +[3]: https://alittleresearcher.blogspot.com/2017/04/gnu-indirect-function-mechanism.html +[4]: ./code/20220621-ifunc/ +[5]: http://maskray.me/blog/2021-01-18-gnu-indirect-function diff --git a/articles/code/20220621-ifunc/Makefile b/articles/code/20220621-ifunc/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..175cda287e53ea09d7c936c0c06e7fdd789b03ce --- /dev/null +++ b/articles/code/20220621-ifunc/Makefile @@ -0,0 +1,25 @@ +CROSS_COMPILE = riscv64-unknown-linux-gnu- + +MY_CONFIG ?= 1 + +.DEFAULT_GOAL := all + +no-ifunc: clean + ${CROSS_COMPILE}gcc -g -shared -fPIC no_ifunc.c -o no_ifunc.so + ${CROSS_COMPILE}gcc -g -DMY_CONFIG=${MY_CONFIG} main.c no_ifunc.so -o a.out + +ifunc: clean + ${CROSS_COMPILE}gcc -g -shared -fPIC -DIFUNC_PREEMPTIBLE ifunc.c -o ifunc.so + ${CROSS_COMPILE}gcc -g -DMY_CONFIG=${MY_CONFIG} main.c ifunc.so -o a.out + +ifunc-static: clean + ${CROSS_COMPILE}gcc -g -shared -fPIC ifunc.c -o ifunc_static.so + ${CROSS_COMPILE}gcc -g -DMY_CONFIG=${MY_CONFIG} main.c ifunc_static.so -o a.out + +.PHONY : clean +clean: + rm -rf *.so a.out + +.PHONY : run +run: + @qemu-riscv64 -L /home/u/ws/test-gcc/install/sysroot/ -E LD_LIBRARY_PATH=. ./a.out diff --git a/articles/code/20220621-ifunc/ifunc.c b/articles/code/20220621-ifunc/ifunc.c new file mode 100644 index 0000000000000000000000000000000000000000..fe4537bc7c28021a6f52b8b8b6d4bfec1677c7da --- /dev/null +++ b/articles/code/20220621-ifunc/ifunc.c @@ -0,0 +1,46 @@ +#include +#include +#include + +extern int get_configure(void); + +static int myfunc_1 (int a) +{ + printf("myfunc_1 is called\n"); + return a + 100; +} + +static int myfunc_2 (int a){ + printf("myfunc_2 is called\n"); + return a + 200; +} + +#ifndef IFUNC_PREEMPTIBLE +static +#endif +int myfunc(int) __attribute__((ifunc ("myfunc_resolver"))); + +// myfunc_resolver -- a symbol to +// myfunc_resolver(void) -- a function taking no arguments +// *myfunc_resolver(void) -- and returning a pointer +// (*myfunc_resolver(void)) (int) -- to a function taking an argument of int +// int (*myfunc_resolver(void)) (int) -- and returning int +// static int (*myfunc_resolver(void)) (int) -- and is not exported to the linker +// so myfunc_resolver, as a function itself, with no argument, will return +// a pointer to a function, which protype is "int func(int)" +int (*myfunc_resolver(void))(int) +{ + printf("myfunc_resolver is called\n"); + if (1 == get_configure()) { + return myfunc_1; + } else { + return myfunc_2; + } +} + +void test_myfunc() +{ + for (int i = 0; i < 3; i++) { + printf("myfunc returns %d\n", myfunc(1)); + } +} diff --git a/articles/code/20220621-ifunc/main.c b/articles/code/20220621-ifunc/main.c new file mode 100644 index 0000000000000000000000000000000000000000..ec6d332fbf0aff0d74562cdc3ae18a10c689a6dd --- /dev/null +++ b/articles/code/20220621-ifunc/main.c @@ -0,0 +1,21 @@ +#include + +extern void test_myfunc(); + +#ifndef MY_CONFIG +#define MY_CONFIG 1 +#endif + +static int g_configure = MY_CONFIG; + +int get_configure() +{ + return g_configure; +} + +int main() +{ + printf("main ......\n"); + test_myfunc(); + return 0; +} \ No newline at end of file diff --git a/articles/code/20220621-ifunc/no_ifunc.c b/articles/code/20220621-ifunc/no_ifunc.c new file mode 100644 index 0000000000000000000000000000000000000000..5ed4842be3600d9e88d0f52c8992273df3040290 --- /dev/null +++ b/articles/code/20220621-ifunc/no_ifunc.c @@ -0,0 +1,33 @@ +#include +#include +#include + +extern int get_configure(void); + +static int myfunc_1 (int a) +{ + printf("myfunc_1 is called\n"); + return a + 100; +} + +static int myfunc_2 (int a){ + printf("myfunc_2 is called\n"); + return a + 200; +} + +int myfunc(int a) +{ + printf("myfunc is called\n"); + if (1 == get_configure()) { + return myfunc_1(a); + } else { + return myfunc_2(a); + } +} + +void test_myfunc() +{ + for (int i = 0; i < 3; i++) { + printf("myfunc returns %d\n", myfunc(1)); + } +} diff --git a/articles/diagrams/linker-loader.png b/articles/diagrams/linker-loader.png new file mode 100644 index 0000000000000000000000000000000000000000..8dda798f630694c1428549074f8ac9911eb5e38b Binary files /dev/null and b/articles/diagrams/linker-loader.png differ