【问题标题】:Rename a function without changing its references重命名函数而不更改其引用
【发布时间】:2020-02-16 00:58:12
【问题描述】:

我有一个使用 gcc 和 -ffunction-sections 选项编译的目标文件。我可以访问源文件,但不允许修改它。

file.c
void foo(void)
{
    bar();
}
void bar(void)
{
    abc();
}

我想要实现的是让所有对 bar 的引用都采用绝对地址(我将在链接器脚本中分配),而 bar 将被链接器放置在其他地址。

一种可能的解决方案是将 bar 重命名为 file_bar,而不更改 foo() 中对 bar 的调用。我尝试使用 objcopy -redefine-syms 但它似乎甚至重命名了对 bar 的调用。

busybee 提供的解决方案可以解决问题,除非函数在同一个编译单元中。 foo1.c

#include <stdio.h>
extern void bar1();
void foo1(){
printf("foo1\n");
}
int main(){
printf("main\n");
foo1();
bar1();
}

bar1.c

#include <stdio.h>
void bar1(){
printf("bar1\n");
}

包装器.c

#include <stdio.h>
void __wrap_foo1(){
printf("wrap_foo1\n");
}
void __wrap_bar1(){
printf("wrap_bar1\n");
}

现在,

$ gcc -c -ffunction-sections foo1.c bar1.c wrapper.c
$ gcc -Wl,--wrap=foo1 -Wl,--wrap=bar1 -o output foo1.o bar1.o wrapper.o
$ ./output
main
foo1
wrap_bar1

【问题讨论】:

  • 如果您将函数bar 重命名为file_bar 而不更改对其的调用和引用,那么链接应该会失败,因为要链接的源中不会有原始bar,对?并且新函数file_bar 可以是唯一的符号名称,因此重命名的函数将未被使用并且可能被删除。你为什么想这么做?这是XY问题吗?实际上你想替换bar函数内容而不改变其他任何东西?

标签: c gcc linker


【解决方案1】:

所有要重定向的函数都在自己的编译单元中

链接器具有选项“--wrap”,它将所有对符号“xxx”的引用替换为“__wrap_xxx”,并将符号本身替换为“__real_xxx”。它用于在调用和函数之间放置一个包装函数作为“拦截器”。

但是使用此选项,您可以对链接描述文件中的这些符号执行任何您喜欢的操作。您只需要使用符号定义“__wrap_xxx”,以便可以解析引用。

根据您的需要,您还可以编写一个名为“__wrap_xxx()”的虚拟函数,它甚至不调用“__real_xxx()”。或者你可以将“__real_xxx”放在一个向量表中,或者......任何你能想到的。

所有要重定向的函数都是非静态的(“全局”),修补立即值

我查看了the other question OP 在评论中发布的答案。这让我想到了削弱相关符号并用链接器的值覆盖它们。

这个例子可能会给你一些见解。我在具有address space layout randomization 的 Linux 上进行了测试,因此所有地址都是从随机基数偏移的。但是对于 OP 的目标系统,它应该可以按预期工作。

foo1.c

由于重定向地址的任意值,无法调用函数。但是程序可以打印他们的地址。

#include <stdio.h>

void foo1(void) {
}

extern void bar1(void);

int main(void) {
  printf("%p\n", main);
  printf("%p\n", foo1);
  printf("%p\n", bar1);
  return 0;
}

bar1.c

void bar1(void) {
}

wrapper.ld

这是为链接器提供要使用的地址的第一个替代方法,一个附加的链接器脚本。第二个见下文。标准链接器脚本将在此处进行扩充,无需复制和修补它。由于结构简单,这可能是提供许多可以轻松自动化的重定向地址的最简单方法。

foo1 = 0x1000;
bar1 = 0x2000;

注意:这不是 C!它是“链接器脚本”语法,恰好非常相似。

我是如何构建和测试的

此命令序列可以根据您的喜好自动化和排序。尤其是objcopy 的调用可以通过列表上的某个循环来完成。

gcc -c -ffunction-sections foo1.c
objcopy --weaken-symbol=foo1 foo1.o foo2.o

gcc -c -ffunction-sections bar1.c
objcopy --weaken-symbol=bar1 bar1.o bar2.o

gcc foo1.o bar1.o -o original
echo original
./original

gcc foo2.o bar2.o -o weakened
echo weakened
./weakened

gcc foo2.o bar2.o wrapper.ld -o redirected
echo redirected
./redirected

也可以在命令行上给出符号定义,而不是额外的链接描述文件。这是提到的第二种选择。

gcc foo2.o bar2.o -Wl,--defsym=foo1=0x1000 -Wl,--defsym=bar1=0x2000 -o redirected

顺便说一句,链接器理解@file 可以从文件file 中读取所有参数。所以链接器命令的大小“没有限制”。

所有要重定向的函数都是非静态的(“全局”),用新函数覆盖

您当然可以只提供替代函数,而不是提供即时值。这与上面一样工作,但您编写源文件而不是额外的链接描述文件或符号定义。

wrapper.c

是的,没错,名字和原作的名字是一样的!因为我们将原始函数的符号设为weak,所以当链接器用新函数的地址覆盖引用时,我们不会收到来自链接器的错误消息。

void foo1(void) {
}

void bar1(void) {
}

像这样构建重定向程序(仅显示新命令):

gcc -c -ffunction-sections wrapper.c

gcc foo2.o bar2.o wrapper.o -o redirected

要重定向的函数是static

嗯,根据您的目标架构,这可能是不可能的。这是因为引用的重定位条目。它将是某种相对的,告诉链接器通过函数部分的偏移量来解析,而不是通过函数的符号来解析。

我没有进一步调查。

【讨论】:

  • 只是想知道如果我们需要包装大量符号而不是在命令行中提及每个符号是否有更好的方法。
  • 所以归结为一个问题:为什么首先要分离调用和函数?顺便说一句,您应该能够自动生成命令行。大概有多少个符号?
  • 它可以自动化。可能有几百个符号。我只是在检查是否有某种方法可以提及要包装在文件中的符号列表并向链接器提及文件名。找不到类似的东西。无论如何,您的解决方案对我来说非常好。
  • 您可以在该文件中包含对链接器的调用,并将其作为脚本/批处理调用。至少 Linux 接受几千字节长度的命令行。
  • 该解决方案不适用于同一目标文件中的函数。有一个相关的帖子stackoverflow.com/questions/13961774/…,但那里提供的解决方案使用了强弱符号概念,而不是“包装”。
猜你喜欢
  • 2011-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-28
相关资源
最近更新 更多