从同一编译单元覆盖C中的函数调用

时间:2012-08-01 11:43:13

标签: c function gcc linker override

我正在尝试Override a function call in C,但是当在同一个编译单元中使用该函数时,我遇到了问题。在下面的代码中,我试图替换函数get_resolution(),但我只能在test.c中完成而不是从display.c中实现它。

// display.c -------------------------------------------------------------

#include <stdio.h>

void get_resolution()
{
    printf("Original get_resolution\n");
}

void display()
{
    get_resolution();
}

// test.c ----------------------------------------------------------------

#include <stdio.h>

void __wrap_get_resolution()
{
    printf("Mock get_resolution\n");
    // __real_get_resolution(); // Should be possible to call original
}

int main()
{
    display();         // **ISSUE** Original get_resolution() is called
    get_resolution();  // __wrap_get_resolution() is called
    return 0;
}

// gcc -Wl,--wrap,get_resolution display.c test.c

我的要求是,当我从main()调用display()时,我希望执行__wrap_get_resolution(),但我总是看到正在调用原始的get_resolution()。对拆卸的一点分析表明函数get_resolution的调用方式不同:

在display() - &gt;中get_resolution()的地址已经解决

00000000 <_get_resolution>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 18                sub    $0x18,%esp
   6:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
   d:   e8 00 00 00 00          call   12 <_get_resolution+0x12>
  12:   c9                      leave  
  13:   c3                      ret    

00000014 <_display>:
  14:   55                      push   %ebp
  15:   89 e5                   mov    %esp,%ebp
  17:   83 ec 08                sub    $0x8,%esp
  1a:   e8 e1 ff ff ff          call   0 <_get_resolution>
  1f:   c9                      leave  
  20:   c3                      ret    
  21:   90                      nop
  22:   90                      nop
  23:   90                      nop

在main() - &gt; get_resolution的地址尚未解决

00000000 <___wrap_get_resolution>:
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   83 ec 18                sub    $0x18,%esp
   6:   c7 04 24 00 00 00 00    movl   $0x0,(%esp)
   d:   e8 00 00 00 00          call   12 <___wrap_get_resolution+0x12>
  12:   c9                      leave  
  13:   c3                      ret    

00000014 <_main>:
  14:   55                      push   %ebp
  15:   89 e5                   mov    %esp,%ebp
  17:   83 e4 f0                and    $0xfffffff0,%esp
  1a:   e8 00 00 00 00          call   1f <_main+0xb>
  1f:   e8 00 00 00 00          call   24 <_main+0x10>
  24:   e8 00 00 00 00          call   29 <_main+0x15>
  29:   b8 00 00 00 00          mov    $0x0,%eax
  2e:   c9                      leave  
  2f:   c3                      ret    

现在的问题是,如何防止编译器解析函数display()中使用的get_resolution()的地址,而是使用重定位表,以便在链接阶段可以覆盖get_resolution()函数?

修改

  1. 根据hroptatyr的回复,添加void get_resolution() __attribute__((weak));解决了使用mingw-gcc但在我的目标平台QNX / ARM / gcc(4.4.2)
  2. 时无法解决的问题
  3. 如果有人可以指向支持ARM目标的好库,那么即使像函数钩子这样的运行时方法也是可以接受的。

6 个答案:

答案 0 :(得分:5)

只需使用预处理器:

void __wrap_get_resolution()
{
    /* calling the real one here */
    get_resolution();
}

#define get_resolution   __wrap_get_resolution

int main()
{
    /* the __wrap... gets called */
    get_resolution();
    ...
}

我们的想法是在您将所有需要查看原始函数的代码放入之后,将函数调用“重命名”到您的包装函数

更脏的版本可能是在本地遮蔽函数地址,如下所示:

int main()
{
    void(*get_resolution)() = __wrap_get_resolution;
    get_resolution();
    ...
}

这个可行,但可以给你一些讨厌的警告。

修改
尽管在评论中指出不希望更改display.c,但此处weak属性解决方案来自http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

  

ELF目标支持弱符号,使用GNU汇编器和链接器时也支持a.out目标。

/* in display.c */
void __real_get_resolution()
{
    ...
}
void get_resolution() __attribute__((weak, alias("__real_get_resolution")));

/* in test.c */
void get_resolution()
{
    /* this version will take precedence over get_resolution() in display.c */
    ...
    /* lastly call the real thing */
    __real_get_resolution();
}

从现在开始,无论你在哪里调用get_resolution()都编译了“强”版本),都会调用包装版本。

答案 1 :(得分:0)

这里可能提供了答案: Override a function call in C

如果要覆盖库中的函数,可以使用(unser linux)LD_PRELOAD

希望得到这个帮助。

问候。

答案 2 :(得分:0)

如果你这样做会怎么样:

gcc -Wl, - wrap,get_resolution test.c display.c

答案 3 :(得分:0)

如果在汇编文件中定义符号,汇编器将始终直接调用符号;使用GNU as无法改变这一点(据我所知)。这假设编译器还没有决定内联函数调用!

解决方案是在get_resolution内导致display.c未定义。为此,我建议将其拆分为2个文件,一个用get_resolution,另一个用于源文件的其余部分。您可以在不实际拆分文件的情况下执行此操作,方法是放入#ifdef块并使用不同的定义对其进行两次编译。

答案 4 :(得分:0)

我认为除了使用shell代码之外很难实现。你可以看到this的原因。

作者给了我们一个C ++代码的例子,我用类似的方法尝试了你的问题,但是我没能实现你想要的。所以我认为解决它的唯一方法可能是使用shell代码,但是ASLR linux让它很难。这就是我所知道的,希望它可以帮到你

答案 5 :(得分:0)

gcc执行的'wrap'功能是在链接器阶段完成的,而不是由编译器完成的。编译器知道get_resolution在哪里,因为它在同一个编译单元中,因此不需要链接器来解析它(这就是魔术发生的地方)。

我看不出你可以用某种方式改变display.c(或其中一个包含的头文件)来解决这个问题。最简单的方法是将get_resolution放入它自己的C源文件中,与display.c分开。