使用预处理器将外部函数指针更改为外部指针

问题描述

我正在使用库,我不应该更改它的文件,包括我的 h 文件。

库的代码看起来像:

#include "my_file"
extern void (*some_func)();
void foo()
{
    (some_func)();
}

我的问题是我希望 some_func 将是 extern 函数而不是指向函数的 extern 指针(我正在实现和链接 some_func)。以及 main 将如何调用它。

这样我就可以节省很少的运行时间和代码空间,而且没有人会错误地改变这个全局。

有可能吗?

我想在 my_file.h 中添加一些东西 #define *some_func some_func 但它不会编译,因为#define 中不允许使用星号。

编辑

该文件尚未编译,因此更改 my_file.h 会影响编译。

解决方法

首先,你说你不能改变库的来源。嗯,这很糟糕,有些“背叛”是必要的。

我的做法是让指针some_func的声明原样,一个非常量的可写变量,但将其实现为常量不可写变量,它将使用想要的地址一次性初始化.

这是最小的、可重现的例子。

库是按照您向我们展示的方式实现的:

// lib.c

#include "my_file"

extern void (*some_func)();

void foo()
{
    (some_func)();
}

因为您在库的源中有这个包含文件,所以我提供了一个。但它是空的。

// my_file

我使用一个头文件来声明库的公共 API。该文件仍然具有指针的可写声明,因此犯罪者相信他们可以更改它。

// lib.h

extern void (*some_func)();

void foo();

我分离了一个有问题的模块来尝试不可能的事情。它有一个头文件和一个实现文件。在源代码中标记了错误的分配,已经揭示了会发生什么。

// offender.h

void offend(void);
// offender.c

#include <stdio.h>

#include "lib.h"
#include "offender.h"

static void other_func()
{
    puts("other_func");
}

void offend(void)
{
    some_func = other_func; // the assignment gives a run-time error
}

测试程序由这个小源组成。为避免编译器错误,必须将声明归因于 const。在这里,我们可以在包含声明头文件的地方使用一些预处理器魔法。

// main.c

#include <stdio.h>

#define some_func const some_func
#include "lib.h"
#undef some_func

#include "offender.h"

static void my_func()
{
    puts("my_func");
}

void (* const some_func)() = my_func;

int main(void)
{
    foo();

    offend();
    foo();

    return 0;
}

诀窍是,编译器将指针变量放在可执行文件的只读部分。 const 属性只被编译器使用,并没有存储在中间目标文件中,链接器很乐意解析所有引用。对该变量的任何写访问都会产生运行时错误。

现在所有这些都编译成一个可执行文件,我在 Windows 上使用了 GCC。我没有费心去创建一个单独的库,因为它对效果没有任何影响。

gcc -Wall -Wextra -g main.c offender.c lib.c -o test.exe

如果我在“cmd”中运行可执行文件,它只会打印“my_func”。显然 foo() 的第二次调用从未执行过。 ERRORLEVEL 为 -1073741819,即 0xC0000005。查找此代码给出了“STATUS_ACCESS_VIOLATION”的含义,在其他系统上称为“分段错误”。

因为我故意用调试标志-g编译,所以我可以用调试器更深入地检查。

d:\tmp\StackOverflow\103> gdb -q test.exe
Reading symbols from test.exe...done.
(gdb) r
Starting program: d:\tmp\StackOverflow\103\test.exe
[New Thread 12696.0x1f00]
[New Thread 12696.0x15d8]
my_func

Thread 1 received signal SIGSEGV,Segmentation fault.
0x00000000004015c9 in offend () at offender.c:16
16          some_func = other_func;

好的,如我所愿,作业已被阻止。然而,系统的反应却相当激烈。

不幸的是,我们无法获得编译时或链接时错误。这是因为库的设计,正如你所说,这是固定的。

,

如果您使用 GCC 或相关,您可以查看 ifunc attribute。它应该在加载时修补一个小蹦床。因此,在调用该函数时,会使用已知的静态地址调用蹦床,然后在蹦床内部有一条用真实地址修补的跳转指令。所以在运行的时候,所有的跳转位置都直接在代码中,用指令缓存应该是高效的。请注意,它甚至可能比这更有效,但最多与调用函数指针一样糟糕。以下是您将如何实施它:

extern void (*some_func)(void); // defined in the header you do not have control about

void some_func_resolved(void) __attribute__((ifunc("resolve_some_func")));

static void (*resolve_some_func(void)) (void)
{
    return some_func;
}

// call some_func_resolved instead now

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...