如何测试 C 函数是否可链接
How to test if a C function is linkable
我刚刚在我使用的一个 C API 中发现了一个缺失的函数(它可以编译但不能 link)。我想测试整套 API 功能。这个想法是为了让事情变得简单,例如:
include <myapi.h>
void main(void) {
func1;
func2;
...
}
但这可以编译并且 link 很好,因为编译器 (gcc) 优化了未使用的函数指针。如果我改用 func1() ,如果确实缺少该函数,我会收到 linker 错误。但是所有的函数调用都很复杂,为所有函数编写显式参数需要几天时间。
我可以使用 nm 来查询库,但首先它是一整套 .so 文件,其次它可能取决于 API header.
中的#defines
有没有我可以在 C 文件中使用的简单语法?或者可能只是 gcc 的(未)优化选项?
一种可能的解决方法是使用函数指针来伪造用法:
include <myapi.h>
typedef void Func_T(void); // Function type for easier handling
Func_T * volatile Func; // Function pointer where functions are stored
/* Macro to do the assignment */
#define TEST(f) Func = (Func_T*)f
int main(void) {
TEST(func1);
TEST(func2);
...
}
链接器可能仍会删除函数,但值得一试。
编译器经常提供属性或编译指示来强制保留符号。如果链接器试图删除它,它可能对保留 Func
有用。
您非常不清楚的问题提到了 .so
个文件(但没有提到任何操作系统)和 nm
。所以我猜你在 Linux,我的回答是特定于 Linux。我不明白你是想在编译和构建时工作还是在运行时工作。
给定一个共享对象 /some/path/to/foo.so
您可以使用 dlopen(3) and dlsym(3) functions to find out (at runtime) if that shared object defines a given symbol. But be aware that in ELF 文件符号 几乎 未类型化(例如,您无法知道某个函数的签名ELF 共享对象的名称,没有一些 C 头文件声明它)。
或者您可能有更复杂的 software build procedure (e.g. by adding ad-hoc rules to your Makefile
). Remember that you could use metaprogramming techniques and have some specialized C code generator in your build. If your software is complex enough (e.g. worth spending weeks on such tools) you might even customize the GCC compiler using GCC MELT(或编写您自己的 GCC 插件)。
请注意,某些头文件(对于给定的库)可能会将函数定义为 inline
或者可能会定义带有参数的宏(例如,请参阅 waitpid(2), part of POSIX API; WIFEXITED
is practically a macro). In both cases, that function won't be a symbol of the ELF shared library but can be used from source code properly using that library (and correctly #include
-ing the appropriate headers). In other words, an API is not the same as一组ELF符号。
另请阅读 Drepper 的 Good Practices in Library Design, Implementation, and Maintenance and How To Write Shared Libraries and D.Wheeler Program Library HOWTO。
最后,如果您根据已知总是错误的条件添加一些代码,编译器将无法优化(阅读 opaque predicates),例如
int main(int argc, char**argv) {
// in practice, all the tests above are false,
// but the compiler is not clever enough to optimize
if (getpid()==0) funct1(); // always false
if (argc<0) funct2(); //always false
if (argv[0][0]==(char)0) funct3(); //always false
/// etc
如果函数的签名需要一些参数,您可以简单地测试它们的地址:
extern void func1(int); // actually, in some included header
if (argv[0]==NULL || (void*)func1 == NULL
|| (void*)func1 == (void*)3) abort();
(我相信 C 标准允许编译器优化 (void*)func1 == NULL
- 总是错误的 - 但它不会优化 (void*)func1 == (void*)3
在 practice 在 Linux 上总是 false...)
但是,API 不仅仅是一组 ELF 符号,API "function" 实际上可以是 inline
或宏。您可能对 weak symbols.
感兴趣
我刚刚在我使用的一个 C API 中发现了一个缺失的函数(它可以编译但不能 link)。我想测试整套 API 功能。这个想法是为了让事情变得简单,例如:
include <myapi.h>
void main(void) {
func1;
func2;
...
}
但这可以编译并且 link 很好,因为编译器 (gcc) 优化了未使用的函数指针。如果我改用 func1() ,如果确实缺少该函数,我会收到 linker 错误。但是所有的函数调用都很复杂,为所有函数编写显式参数需要几天时间。
我可以使用 nm 来查询库,但首先它是一整套 .so 文件,其次它可能取决于 API header.
中的#defines有没有我可以在 C 文件中使用的简单语法?或者可能只是 gcc 的(未)优化选项?
一种可能的解决方法是使用函数指针来伪造用法:
include <myapi.h>
typedef void Func_T(void); // Function type for easier handling
Func_T * volatile Func; // Function pointer where functions are stored
/* Macro to do the assignment */
#define TEST(f) Func = (Func_T*)f
int main(void) {
TEST(func1);
TEST(func2);
...
}
链接器可能仍会删除函数,但值得一试。
编译器经常提供属性或编译指示来强制保留符号。如果链接器试图删除它,它可能对保留 Func
有用。
您非常不清楚的问题提到了 .so
个文件(但没有提到任何操作系统)和 nm
。所以我猜你在 Linux,我的回答是特定于 Linux。我不明白你是想在编译和构建时工作还是在运行时工作。
给定一个共享对象 /some/path/to/foo.so
您可以使用 dlopen(3) and dlsym(3) functions to find out (at runtime) if that shared object defines a given symbol. But be aware that in ELF 文件符号 几乎 未类型化(例如,您无法知道某个函数的签名ELF 共享对象的名称,没有一些 C 头文件声明它)。
或者您可能有更复杂的 software build procedure (e.g. by adding ad-hoc rules to your Makefile
). Remember that you could use metaprogramming techniques and have some specialized C code generator in your build. If your software is complex enough (e.g. worth spending weeks on such tools) you might even customize the GCC compiler using GCC MELT(或编写您自己的 GCC 插件)。
请注意,某些头文件(对于给定的库)可能会将函数定义为 inline
或者可能会定义带有参数的宏(例如,请参阅 waitpid(2), part of POSIX API; WIFEXITED
is practically a macro). In both cases, that function won't be a symbol of the ELF shared library but can be used from source code properly using that library (and correctly #include
-ing the appropriate headers). In other words, an API is not the same as一组ELF符号。
另请阅读 Drepper 的 Good Practices in Library Design, Implementation, and Maintenance and How To Write Shared Libraries and D.Wheeler Program Library HOWTO。
最后,如果您根据已知总是错误的条件添加一些代码,编译器将无法优化(阅读 opaque predicates),例如
int main(int argc, char**argv) {
// in practice, all the tests above are false,
// but the compiler is not clever enough to optimize
if (getpid()==0) funct1(); // always false
if (argc<0) funct2(); //always false
if (argv[0][0]==(char)0) funct3(); //always false
/// etc
如果函数的签名需要一些参数,您可以简单地测试它们的地址:
extern void func1(int); // actually, in some included header
if (argv[0]==NULL || (void*)func1 == NULL
|| (void*)func1 == (void*)3) abort();
(我相信 C 标准允许编译器优化 (void*)func1 == NULL
- 总是错误的 - 但它不会优化 (void*)func1 == (void*)3
在 practice 在 Linux 上总是 false...)
但是,API 不仅仅是一组 ELF 符号,API "function" 实际上可以是 inline
或宏。您可能对 weak symbols.