GNU gcc/ld - 使用在同一个目标文件中定义的调用者和被调用者包装对符号的调用

Posted

技术标签:

【中文标题】GNU gcc/ld - 使用在同一个目标文件中定义的调用者和被调用者包装对符号的调用【英文标题】:GNU gcc/ld - wrapping a call to symbol with caller and callee defined in the same object file 【发布时间】:2012-12-07 08:37:35 【问题描述】:

澄清一下,我的问题是指包装/拦截从一个函数/符号到另一个函数/符号的调用当调用者和被调用者使用 GCC 编译器和链接器在同一个编译单元中定义时 .

我的情况类似于以下:

/* foo.c */
void foo(void)

  /* ... some stuff */
  bar();


void bar(void)

  /* ... some other stuff */

我想包装对这些函数的调用,我可以用ld's --wrap option 做到这一点(在一定程度上)(然后我实现 __wrap_foo 和 __wrap_bar 依次调用 __real_foo 和 __real_bar,正如 ld 的 @ 的结果所期望的那样987654327@ 选项)。

gcc -Wl,--wrap=foo -Wl,--wrap=bar ...

我遇到的问题是,这只在此编译单元之外对 foo 和 bar from 的引用生效(并在链接时解决)。也就是说,在 foo.c 内的其他函数对 foo 和 bar 的调用不会被包装。

我尝试使用objcopy --redefine-sym,但这只会重命名符号及其引用。

我想将调用 foobar(在 foo.o 内)替换为 __wrap_foo__wrap_bar(就像它们在其他目标文件中通过链接器的 --wrap 选项解决一样)之前我将 *.o 文件传递​​给链接器的 --wrap 选项,而无需修改 foo.c 的源代码。

这样,对foobar 的所有调用都会进行包装/拦截,而不仅仅是在 foo.o 之外进行的调用。

这可能吗?

【问题讨论】:

如果您必须对目标文件执行此操作,您可能需要通过调用一些包装逻辑来覆盖函数的开头,但这需要了解特定于平台的函数调用、寄存器保存等序列并希望它不会改变。仅仅对地址进行查找和替换是行不通的,因为它们通常是相对的——你可以对你认为编译器将使用的任何调用指令进行模式匹配,计算出它们的目标并更改它们,但这很快就会变得丑陋。 如果您可以修改源代码/构建命令来实现您希望的修复,为什么不能简单地在源代码中的函数名级别解决它?还是将函数移动到自己的编译单元? 我不确定我是否看到了自动更改源工作副本的脚本与更难证明修改对象的脚本之间的区别。 ***.com/questions/617554/… 提供了一些变化。如果只是为了分析,你能用断点调试器功能做点什么吗? 这不是你问的,但我来这里是为了寻找一个稍微不同的问题:如何替换已经编译的目标文件中的函数,以便现有目标文件中的调用者引用另一个文件的新功能?答案是使用objcopy --weaken-symbol=called_function 并链接到定义called_function() 的新对象。 如果有人设法使用 --wrap 实现了目标,这很有趣?我没有。但我发现使用 LD_PRELOAD 运行时函数替换技术的运行时函数包装可以实现该目标。 【参考方案1】:

你必须使用 objcopy 弱化和全球化符号。

-W symbolname
--weaken-symbol=symbolname
    Make symbol symbolname weak. This option may be given more than once.
--globalize-symbol=symbolname
    Give symbol symbolname global scoping so that it is visible outside of the file in which it is defined. This option may be given more than once.

这对我有用

bar.c:

#include <stdio.h>
int foo()
  printf("Wrap-FU\n");

foo.c:

#include <stdio.h>

void foo()
printf("foo\n");


int main()
printf("main\n");
foo();

编译

$ gcc -c foo.c bar.c 

弱化 foo 符号并使其全局化,以便它再次可用于链接器。

$ objcopy foo.o --globalize-symbol=foo --weaken-symbol=foo foo2.o

现在您可以将新 obj 与 bar.c 中的 wrap 链接

$ gcc -o nowrap foo.o #for reference
$ gcc -o wrapme foo2.o bar.o

测试

$ ./nowrap 
main
foo

还有包装好的:

$ ./wrapme 
main
Wrap-FU

【讨论】:

我在以下情况下尝试了这个技巧: 1- 我有一个用于嵌入式平台的 SDK,它有一个需要用另一个减速代替的功能。 2- 我在编译后使用 gcc-objcopy 从目标库中的目标文件再次使符号变弱和全局化。构建过程包括制作包含旧库目标文件的存档文件(称为 core.a)的问题。 3-我添加了一个步骤来删除目标文件并使用来自 cora.a 的 gcc-ar 将其替换为新的(带有弱符号)。结果这个伎俩没有成功(..的多个定义)帮助?【参考方案2】:

您可以在被调用者的实现之前使用__attribute__((weak)),以便让某人重新实现它而无需 GCC 对多个定义大喊大叫。

例如,假设您想在以下 hello.c 代码单元中模拟 world 函数。您可以预先添加属性以便能够覆盖它。

#include "hello.h"
#include <stdio.h>

__attribute__((weak))
void world(void)

    printf("world from lib\n");


void hello(void)

    printf("hello\n");
    world();

然后您可以在另一个单元文件中覆盖它。对于单元测试/模拟非常有用:

#include <stdio.h>
#include "hello.h"

/* overrides */
void world(void)

    printf("world from main.c"\n);


void main(void)

    hello();
    return 0;

【讨论】:

这是个好主意。下次会用。不幸的是,当我问这个问题时,我正在处理我无法修改以添加这样一个属性的软件。不过,这很好,将来肯定会在我的工具箱中使用。 嗯,是的,如果您无法修改源代码,那么@PeterHuewe 的答案是使用 objcpy 的解决方案。如果您可以修改源,那么这个似乎更容易设置。【参考方案3】:
#include <stdio.h>
#include <stdlib.h>

//gcc -ggdb -o test test.c -Wl,-wrap,malloc
void* __real_malloc(size_t bytes);

int main()

   int *p = NULL;
   int i = 0;

   p = malloc(100*sizeof(int));

   for (i=0; i < 100; i++)
       p[i] = i;

   free(p);
   return 0;


void* __wrap_malloc(size_t bytes)

      return __real_malloc(bytes);

然后只需编译此代码并进行调试。调用realmalloc时,调用的函数会__wrap_malloc,__real_malloc会调用malloc。

我认为这是拦截电话的方式。

基本上是 ld 提供的 --wrap 选项。

【讨论】:

我知道这个选项。这几乎是我使用的。这在我提到的场景中不起作用。再次查看我原来的问题。 此答案中的示例显示了如何使用--wrap,但它没有显示包装函数(在本例中为malloc)与在同一编译单元中定义的情况call,这是原问题的核心。所以这不是问题的真正答案,我会否决这个答案。【参考方案4】:

这似乎按记录工作:

 --wrap=symbol
       Use a wrapper function for symbol. 
       Any undefined reference to symbol will be resolved to "__wrap_symbol". ...

注意上面的undefined。当链接器处理foo.o 时,bar() not 未定义,因此链接器不会包装它。我不确定为什么会这样做,但可能有一个用例需要这样做。

【讨论】:

我使用它来包装跨编译单元的调用(有关示例,请参阅我的原始问题)。但是,它不适用于从编译单元内拦截/包装所有内容(这是我对拦截感兴趣的内容。)显然,编译单元内,引用已解决。当链接器进入时,使用--wrap 链接器选项包装这些调用已经太晚了。 @luis.espinal “已经太晚了”——不,不是。链接器可以轻松更改调用目标;它只是没有(原因我不知道)。 好吧,当我说“为时已晚”时,我是在 GNU ld 的上下文中这么说的(一般不是在链接器的上下文中)。是的,a链接器可以轻松更改该调用目标。但是有问题的 the 链接器(GNU ld)没有。原因是它限制了自己替换/重写在编译单元内未解析的引用。正是由于最后一步,我说链接阶段对于 GN ld 来说已经太晚了(尽管对于更智能的链接器来说还不算太晚。)【参考方案5】:

如果你使用--undefined--wrap,你可以达到你想要的效果

  -u SYMBOL, --undefined SYMBOL
                              Start with undefined reference to SYMBOL

【讨论】:

你会在哪里添加这个选项?你能举一个更完整的例子吗?我快速尝试在链接器命令行上添加-u bar-Wl,--wrap=bar,但这似乎没有任何改变?它可能使 foo 在开始时未定义,但不是 inside foo.c...【参考方案6】:

我尝试了@PeterHuewe 中的solution,它可以工作,但它不允许从包装器调用原始函数。为此,我的解决方案如下:

foo.c


#include <stdio.h>

void foo()
    printf("This is real foo\n");


int main()
    printf("main\n");
    foo();

foo_hook.c

#include <stdio.h>

void real_foo();

int foo()
  printf("HOOK: BEFORE\n");
  real_foo();
  printf("HOOK: AFTER\n");

生成文件

all: link

link: hook
    gcc -o wo_hook foo.o
    gcc -o w_hook foo_hooked.o foo_hook.o

hook: build_o
    objcopy \
    foo.o \
    --add-symbol real_foo=.text:$(shell  objdump -t foo.o | grep foo | grep .text | cut -d' ' -f 1),function,global \
    --globalize-symbol=foo \
    --weaken-symbol=foo \
    foo_hooked.o

build_o:
    gcc -c foo.c foo_hook.c

clean:
    -rm w_hook wo_hook *.o

示例

virtualuser@virtualhost:~/tmp/link_time_hook$ make
gcc -c foo.c foo_hook.c
objcopy foo.o \
--add-symbol real_foo=.text:0000000000000000,function,global \
--globalize-symbol=foo \
--weaken-symbol=foo \
foo_hooked.o
gcc -o wo_hook foo.o
gcc -o w_hook foo_hooked.o foo_hook.o
virtualuser@virtualhost:~/tmp/link_time_hook$ ls
Makefile  foo.c  foo.o  foo_hook.c  foo_hook.o  foo_hooked.o  w_hook  wo_hook
virtualuser@virtualhost:~/tmp/link_time_hook$ ./w_hook
main
HOOK: BEFORE
This is real foo
HOOK: AFTER
virtualuser@virtualhost:~/tmp/link_time_hook$
virtualuser@virtualhost:~/tmp/link_time_hook$ ./wo_hook
main
This is real foo
virtualuser@virtualhost:~/tmp/link_time_hook$

【讨论】:

谢谢!我好久没碰过这个问题了:) 此脚本有一个错误,因为它仅使用 0 处的函数进行测试。即 objcopy 会将值解释为十进制,而 objdump 给出十六进制,因此必须在前面加上“0x”,例如--add-symbol real_foo=.text:0x$(shell objdump -t foo.o | grep foo | grep .text | cut -d' ' -f 1),function,global 和 --add-symbol real_foo=. text:0x0000000000000000,function,global 将使这个函数超出这个特殊的零情况。【参考方案7】:

带链接器

$ /usr/bin/ld --version
GNU ld (GNU Binutils for Ubuntu) 2.30

我能够使用defsym 选项解决问题:

--defsym SYMBOL=EXPRESSION  Define a symbol`

代替

gcc -Wl,--wrap=foo -Wl,--wrap=bar ...

试试

gcc -Wl,--defsym,foo=__wrap_foo -Wl,--defsym,bar=__wrap_bar ...

我也没有尝试定义__real_* 符号。

【讨论】:

有趣,似乎--defsym 只允许覆盖 .o 文件中的现有符号(即在 .o 文件中定义的 --defsymfoo 没有多重定义错误)。似乎--defsym 的处理方式与链接描述文件中的赋值基本相同,它们的行为可能相同。但是,我相信这种方法也不允许定义 __real_* 符号:一旦您覆盖 foo 符号,我认为您将无法访问原始符号...

以上是关于GNU gcc/ld - 使用在同一个目标文件中定义的调用者和被调用者包装对符号的调用的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 GCC 和 ld 删除未使用的 C/C++ 符号?

GCC + LD + NDISASM = 大量的汇编指令

1st.初识GCC——关于GCC编译器的相关语法与介绍

GCC LD NOLOAD 链接器部分生成可加载段

GNU 使用文件作为 SUFFIXES 中的目标

GNU make:规则专题