6

我正在尝试覆盖 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() -> 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() -> 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. 如果有人可以指向一个支持 ARM 目标的好库,那么即使是像函数挂钩这样的运行时方法也是可以接受的。
4

6 回答 6

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()在其中编译“强”版本),都会调用包装版本。

于 2012-08-01T12:03:07.387 回答
0

如果你这样做怎么办:

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

于 2012-08-01T12:01:40.453 回答
0

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

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

于 2012-08-01T12:20:12.937 回答
0

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

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

于 2012-08-01T13:26:38.983 回答
0

此处可能提供了答案: Override a function call in C

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

希望这有帮助。

问候。

于 2012-08-01T11:54:51.427 回答
0

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

我看不到您可以在不以某种方式更改 display.c (或其中一个包含的头文件)的情况下解决此问题。最简单的方法是将 get_resolution 放入它自己的 C 源文件中,与 display.c 分开。

于 2012-08-01T14:04:13.997 回答