重写 C 中的函数调用
为了记录调用,我想覆盖对各种 API 的某些函数调用,但我也可能想在将数据发送到实际函数之前对其进行操作。
例如,假设我在源代码中使用了名为 getObjectName
的函数数千次。 有时我想暂时覆盖此函数,因为我想更改此函数的行为以查看不同的结果。
我创建了一个像这样的新源文件:
#include <apiheader.h>
const char *getObjectName (object *anObject)
{
if (anObject == NULL)
return "(null)";
else
return "name should be here";
}
我像平常一样编译所有其他源代码,但在链接到 API 库之前,我首先将其链接到此函数。 这工作得很好,只是我显然无法在我的重写函数中调用真正的函数。
有没有更简单的方法来“覆盖”函数而不会出现链接/编译错误/警告? 理想情况下,我希望能够通过编译和链接一个或两个额外的文件来覆盖该函数,而不是摆弄链接选项或更改程序的实际源代码。
I want to override certain function calls to various APIs for the sake of logging the calls, but I also might want to manipulate data before it is sent to the actual function.
For example, say I use a function called getObjectName
thousands of times in my source code. I want to temporarily override this function sometimes because I want to change the behaviour of this function to see the different result.
I create a new source file like this:
#include <apiheader.h>
const char *getObjectName (object *anObject)
{
if (anObject == NULL)
return "(null)";
else
return "name should be here";
}
I compile all my other source as I normally would, but I link it against this function first before linking with the API's library. This works fine except I can obviously not call the real function inside my overriding function.
Is there an easier way to "override" a function without getting linking/compiling errors/warnings? Ideally I want to be able to override the function by just compiling and linking an extra file or two rather than fiddle around with linking options or altering the actual source code of my program.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
使用 gcc,在 Linux 下您可以使用
--wrap
链接器标志,如下所示:并将您的函数定义为:
这将确保对
getObjectName()
的所有调用都重新路由到你的包装函数(在链接时)。 然而,Mac OS X 下的 gcc 中没有这个非常有用的标志。如果您使用 g++ 进行编译,请记住使用
extern "C"
声明包装函数。With gcc, under Linux you can use the
--wrap
linker flag like this:and define your function as:
This will ensure that all calls to
getObjectName()
are rerouted to your wrapper function (at link time). This very useful flag is however absent in gcc under Mac OS X.Remember to declare the wrapper function with
extern "C"
if you're compiling with g++ though.如果您只想捕获/修改调用的源代码,最简单的解决方案是将头文件 (
intercept.h
) 放在一起:然后按如下方式实现该函数(在 < code>intercept.c 其中不包含
intercept.h
):然后确保要拦截调用的每个源文件都具有以下内容顶部:
当您使用“
-DINTERCEPT
”进行编译时,所有文件都会调用您的函数而不是真正的函数,而您的函数仍然会调用真正的函数。不使用“
-DINTERCEPT
”进行编译将防止发生拦截。如果您想拦截所有调用(而不仅仅是来自源代码的调用),那就有点棘手了 - 这通常可以通过动态加载和解析实际函数来完成(使用 dlload- 和 dlsym - 类型调用)但我认为在你的情况下没有必要。
If it's only for your source that you want to capture/modify the calls, the simplest solution is to put together a header file (
intercept.h
) with:Then you implement the function as follows (in
intercept.c
which doesn't includeintercept.h
):Then make sure each source file where you want to intercept the call has the following at the top:
When you compile with "
-DINTERCEPT
", all files will call your function rather than the real one, whereas your function will still call the real one.Compiling without the "
-DINTERCEPT
" will prevent interception from occurring.It's a bit trickier if you want to intercept all calls (not just those from your source) - this can generally be done with dynamic loading and resolution of the real function (with
dlload-
anddlsym-
type calls) but I don't think it's necessary in your case.您可以使用
LD_PRELOAD
技巧覆盖函数 - 请参阅man ld.so
。 您可以使用您的函数编译共享库并启动二进制文件(您甚至不需要修改二进制文件!),例如LD_PRELOAD=mylib.so myprog
。在函数体中(在共享库中),您可以这样编写:
您可以覆盖共享库中的任何函数,甚至是 stdlib 中的函数,而无需修改/重新编译程序,因此您可以对没有的程序进行操作来源为. 不是很好吗?
You can override a function using
LD_PRELOAD
trick - seeman ld.so
. You compile shared lib with your function and start the binary (you even don't need to modify the binary!) likeLD_PRELOAD=mylib.so myprog
.In the body of your function (in shared lib) you write like this:
You can override any function from shared library, even from stdlib, without modifying/recompiling the program, so you could do the trick on programs you don't have a source for. Isn't it nice?
如果您使用 GCC,您可以使您的函数
弱
。 这些可以被覆盖< /a> 通过非弱函数:test.c:
它有什么作用?
test1.c:
它有什么作用?
遗憾的是,这不适用于其他编译器。 但是,您可以在自己的文件中包含包含可重写函数的弱声明,如果您使用 GCC 进行编译,则只需将 include 放入 API 实现文件中即可:
weakdecls.h:
functions.c< /strong>:
这样做的缺点是,如果不对 api 文件执行某些操作(需要这三行和weakdecls),它就无法完全工作。 但是,一旦进行了更改,就可以通过在一个文件中编写全局定义并将其链接到其中来轻松覆盖函数。
If you use GCC, you can make your function
weak
. Those can be overridden by non-weak functions:test.c:
What does it do?
test1.c:
What does it do?
Sadly, that won't work for other compilers. But you can have the weak declarations that contain overridable functions in their own file, placing just an include into the API implementation files if you are compiling using GCC:
weakdecls.h:
functions.c:
Downside of this is that it does not work entirely without doing something to the api files (needing those three lines and the weakdecls). But once you did that change, functions can be overridden easily by writing a global definition in one file and linking that in.
您可以将函数指针定义为全局变量。 调用者语法不会改变。 当您的程序启动时,它可以检查某些命令行标志或环境变量是否设置为启用日志记录,然后保存函数指针的原始值并将其替换为您的日志记录函数。 您不需要特殊的“启用日志记录”构建。 用户可以“在现场”启用日志记录。
您需要能够修改调用者的源代码,但不能修改被调用者的源代码(因此这在调用第三方库时可以工作)。
foo.h:
foo.cpp:
You can define a function pointer as a global variable. The callers syntax would not change. When your program starts, it could check if some command-line flag or environment variable is set to enable logging, then save the function pointer's original value and replace it with your logging function. You would not need a special "logging enabled" build. Users could enable logging "in the field".
You will need to be able to modify the callers' source code, but not the callee (so this would work when calling third-party libraries).
foo.h:
foo.cpp:
基于 @Johannes Schaub 的答案,提供适合您不拥有的代码的解决方案。
将要重写的函数别名为弱定义函数,然后自己重新实现它。
override.h
foo.c
override.c
使用 特定于模式的变量值,以添加编译器标志
-include override.h
。旁白:也许您还可以使用 -D 'foo(x) __attribute__((weak))foo(x)' 来定义宏。
编译该文件并将其与您的重新实现 (
override.c
) 链接。这允许您覆盖任何源文件中的单个函数,而无需修改代码。
这允许
缺点是您必须为要覆盖的每个文件使用单独的头文件。
缺点是
Building on @Johannes Schaub's answer with a solution suitable for code you don't own.
Alias the function you want to override to a weakly-defined function, and then reimplement it yourself.
override.h
foo.c
override.c
Use pattern-specific variable values in your Makefile to add the compiler flag
-include override.h
.Aside: Perhaps you could also use
-D 'foo(x) __attribute__((weak))foo(x)'
to define your macros.Compile and link the file with your reimplementation (
override.c
).This allows you to override a single function from any source file, without having to modify the code.
The downside is that you must use a separate header file for each file you want to override.
在涉及两个存根库的链接器中还有一种棘手的方法。
库 #1 与主库链接,并公开以另一个名称重新定义的符号。
库 #2 与库 #1 链接,拦截调用并调用库 #1 中重新定义的版本。
请务必小心此处的链接订单,否则将无法正常工作。
There's also a tricky method of doing it in the linker involving two stub libraries.
Library #1 is linked against the host library and exposes the symbol being redefined under another name.
Library #2 is linked against library #1, interecepting the call and calling the redefined version in library #1.
Be very careful with link orders here or it won't work.
以下是我的实验。 正文和最后有4个结论。
短版本
一般来说,要成功重写一个函数,你必须考虑:
长版本
我有这些源文件。
符号表:
在这两种情况下,
override.o
符号:所以结论是:
.o
文件中定义的函数可以覆盖.a
文件中定义的相同函数。 在上面的Makefile1中,override.o
中的func2()
和func3()
覆盖了中的对应部分>all_weak.a
。 我尝试使用两个.o
文件,但它不起作用。对于 GCC,您不需要将函数拆分为单独的
.o
文件,如 此处用于 Visual Studio 工具链。 我们可以在上面的示例中看到,func2()
(与func1()
在同一个文件中)和func3()
(在单独的文件中)文件)可以被覆盖。要覆盖某个函数,在编译其使用者的翻译单元时,您需要将该函数指定为弱函数。 这会在
consumer.o
中将该函数记录为弱函数。 在上面的示例中,当编译test_target.c
时,它使用func2()
和func3()
,您需要添加-包括weak_decl.h,它将
func2()
和func3()
声明为weak。func2()
也在test_target.c
中定义,但没问题。一些进一步的实验
仍然使用上面的源文件。 但稍微改变一下
override.c
:这里我删除了
func3()
的覆盖版本。 我这样做是因为我想回退到func3.c
中的原始func3()
实现。我仍然使用
Makefile1构建。 构建没问题。 但是运行时错误如下:
所以我检查了最终的main 的符号:
所以我们可以看到
func3
没有有效的地址。 这就是段错误发生的原因。那么为什么呢? 我没有将
func3.o
添加到all_weak.a
存档文件中吗?我对
func2
尝试了同样的操作,其中我从中删除了
。 但这次没有出现段错误。func2
实现ovrride.c我的猜测是,因为
func2
是在与func1
相同的文件/翻译单元中定义的。 因此,func2
始终与func1
一起引入。 因此,链接器始终可以解析func2
,无论是来自test_target.c
还是override.c
。但对于
func3
,它是在单独的文件/翻译单元(func3.c)中定义的。 如果它被声明为弱,消费者test_target.o
仍会将func3()
记录为弱。 但不幸的是,GCC 链接器不会检查同一.a
文件中的其他.o
文件来查找func3()
的实现. 虽然它确实存在。所以我必须在
override.c
中提供一个覆盖版本,否则func3()
无法解析。但我还是不明白为什么GCC会这样。 如果有人可以解释一下。
(2021 年 8 月 8 日上午 9:01 更新:
所以进一步的结论是:
Below are my experiments. There are 4 conclusions in the body and in the end.
Short Version
Generally speaking, to successfully override a function, you have to consider:
Long Version
I have these source files.
The symbol table:
In both cases, the
override.o
symbols:So the conclusion is:
A function defined in
.o
file can override the same function defined in.a
file. In above Makefile1, thefunc2()
andfunc3()
inoverride.o
overrides the counterparts inall_weak.a
. I tried with both.o
files but it don't work.For GCC, You don't need to split the functions into separate
.o
files as said in here for Visual Studio toolchain. We can see in above example, bothfunc2()
(in the same file asfunc1()
) andfunc3()
(in a separate file) can be overridden.To override a function, when compiling its consumer's translation unit, you need to specify that function as weak. That will record that function as weak in the
consumer.o
. In above example, when compiling thetest_target.c
, which consumesfunc2()
andfunc3()
, you need to add-include weak_decl.h
, which declaresfunc2()
andfunc3()
as weak. Thefunc2()
is also defined intest_target.c
but it's OK.Some further experiment
Still with the above source files. But change the
override.c
a bit:Here I removed the override version of
func3()
. I did this because I want to fall back to the originalfunc3()
implementation in thefunc3.c
.I still use
Makefile1
to build. The build is OK. But a runtime error happens as below:So I checked the symbols of the final
main
:So we can see the
func3
has no valid address. That's why segment fault happens.So why? Didn't I add the
func3.o
into theall_weak.a
archive file?I tried the same thing with
func2
, where I removed thefunc2
implementation fromovrride.c
. But this time there's no segment fault.My guess is, because
func2
is defined in the same file/translation unit asfunc1
. Sofunc2
is always brought in withfunc1
. So the linker can always resolvefunc2
, be it from thetest_target.c
oroverride.c
.But for
func3
, it is defined in a separate file/translation unit (func3.c). If it is declared as weak, the consumertest_target.o
will still recordfunc3()
as weak. But unfortunately the GCC linker will not check the other.o
files from the same.a
file to look for an implementation offunc3()
. Though it is indeed there.So I must provide an override version in
override.c
otherwise thefunc3()
cannot be resolved.But I still don't know why GCC behaves like this. If someone can explain, please.
(Update 9:01 AM 8/8/2021:
this thread may explain this behavior, hopefully.)
So further conclusion is:
您也可以使用共享库 (Unix) 或 DLL (Windows) 来执行此操作(会稍微降低性能)。 然后,您可以更改加载的 DLL/so(一种版本用于调试,一种版本用于非调试)。
我过去做过类似的事情(不是为了实现你想要实现的目标,但基本前提是相同的)并且效果很好。
[根据OP评论进行编辑]
有两种常见的方法(据我所知)可以处理这个问题,共享 lib/dll 方法或编写链接的不同实现。
对于这两种解决方案(共享库或不同的链接),您将拥有 foo_linux.c、foo_osx.c、foo_win32.c (或者更好的方法是 linux/foo.c、osx/foo.c 和 win32/foo.c),然后编译并链接到适当的版本。
如果您正在寻找针对不同平台的不同代码以及调试-与-发布,我可能会倾向于使用共享 lib/DLL 解决方案,因为它是最灵活的。
You could use a shared library (Unix) or a DLL (Windows) to do this as well (would be a bit of a performance penalty). You can then change the DLL/so that gets loaded (one version for debug, one version for non-debug).
I have done a similar thing in the past (not to achieve what you are trying to achieve, but the basic premise is the same) and it worked out well.
[Edit based on OP comment]
There are two common ways (that I know of) of dealing with that, the shared lib/dll way or writing different implementations that you link against.
For both solutions (shared libs or different linking) you would have foo_linux.c, foo_osx.c, foo_win32.c (or a better way is linux/foo.c, osx/foo.c and win32/foo.c) and then compile and link with the appropriate one.
If you are looking for both different code for different platforms AND debug -vs- release I would probably be inclined to go with the shared lib/DLL solution as it is the most flexible.