如何测试C函数是否可链接

时间:2016-11-04 09:19:46

标签: c gcc linker libraries

我刚刚在我使用的一个C API中发现了一个缺失的函数(它编译但它没有链接)。我想测试整套API函数。我们的想法是保持简单,例如:

include <myapi.h>
void main(void) {
  func1;
  func2;
  ...
}

但是编译和链接很好,因为编译器(gcc)优化掉了未使用的函数指针。如果我使用func1()代替,如果该函数确实缺失,我会收到链接器错误。但是所有函数调用都很复杂,为所有函数调用编写显式参数需要数天时间。

我可以使用nm来查询库,但首先它是一整套.so文件,其次它可能取决于API头中的#defines。

我可以在C文件中使用简单的语法吗?或者只是gcc的(非)优化选项?

2 个答案:

答案 0 :(得分:2)

一种可能的解决方法是使用函数指针伪造用法:

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可能很有用。

答案 1 :(得分:2)

您非常不清楚的问题是提到.so个文件(但未提及任何操作系统)和nm。所以我猜你是在Linux上,我的答案是针对Linux的。我不明白你是想在编译和构建时还是在运行时工作。

给定共享对象/some/path/to/foo.so,您可以使用dlopen(3)dlsym(3)函数找出(在运行时)该共享对象是否定义了给定符号。但请注意,在ELF文件中,符号几乎无类型(例如,您无法从其名称中知道ELF共享对象中某些函数的签名,而没有一些C头文件声明它)

或者,您可能有一个更复杂的software build程序(例如,通过向Makefile添加临时规则)。请记住,您可以使用metaprogramming技术并在构建中使用一些专门的C代码生成器。如果您的软件足够复杂(例如值得在这些工具上花费数周时间),您甚至可以使用GCC MELT自定义GCC编译器(或编写您自己的GCC插件)。

请注意,某些头文件(对于给定的库)可以将函数定义为inline,或者可以为其定义带有参数的宏(例如,请参阅waitpid(2),POSIX API的一部分; { {1}}实际上是一个宏)。在这两种情况下,该函数都不会成为ELF共享库的符号,但可以使用该库正确使用源代码(并正确地#include - 使用相应的头文件)。换句话说,API与一组ELF符号不同。

另请阅读Drepper的Good Practices in Library Design, Implementation, and MaintenanceHow To Write Shared Libraries以及D.Wheeler Program Library HOWTO

最后,如果添加一些代码,编译器无法优化,具体取决于已知总是错误的条件(阅读opaque predicates),例如

WIFEXITED

如果函数的签名需要一些参数,你可以简单地测试它们的地址:

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

(我相信C标准允许编译器优化 extern void func1(int); // actually, in some included header if (argv[0]==NULL || (void*)func1 == NULL || (void*)func1 == (void*)3) abort(); - 总是错误的 - 但它不会优化(void*)func1 == NULL 练习在Linux上总是假的......)

但同样,API不仅仅是一组ELF符号,还有一个API&#34;功能&#34;实际上可以是(void*)func1 == (void*)3或宏。您可能对weak symbols感兴趣。