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

问题描述 投票:0回答:3

我刚刚发现我使用的一个 C API 中缺少一个函数(它可以编译,但不能链接)。我想测试整套 API 函数。我们的想法是让事情变得简单,例如:

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

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

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

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

c gcc linker libraries
3个回答
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


2
投票

您非常不清楚的问题提到了

.so
文件(但没有提到任何操作系统)和
nm
。所以我猜你用的是Linux,我的回答是针对Linux的。我不明白你想在编译和构建时还是在运行时工作。

给定一个共享对象

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

软件构建

过程(例如,通过向您的Makefile

添加临时规则)。请记住,您可以使用“元编程”技术,并在构建中使用一些专门的 C 代码生成器。如果您的软件足够复杂(例如值得在此类工具上花费数周时间),您甚至可以使用 
GCC MELT 自定义 GCC 编译器(或编写自己的 GCC 插件)。 请注意,某些头文件(对于给定库)可能会将函数定义为 inline 或可能为其定义带有参数的宏(例如,请参阅

waitpid(2)

,POSIX API 的一部分;

WIFEXITED
是实际上是一个宏)。在这两种情况下,该函数都不会是 ELF 共享库的符号,但可以从源代码中正确使用该库(并正确地使用适当的标头)。换句话说,API 与一组 ELF 符号不同。

另请阅读 Drepper 的
库设计、实施和维护的良好实践
如何编写共享库

和 D.Wheeler 程序库 HOWTO 最后,如果您根据已知总是错误的条件添加一些代码,编译器将无法优化(阅读不透明谓词),例如 #include 如果函数的签名需要一些参数,您可以简单地测试它们的地址:

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“函数”实际上可以是 
(void*)func1 == (void*)3
 或宏。您可能对
弱符号感兴趣。

这个问题很老了,但我写了一个 python 脚本,它采用函数签名并测试它是否链接..

参见这里https://gist.github.com/Kreijstal/b4a7528245c00625f80cf31fe38e9829


0
投票

inline

你这样使用它: import subprocess import tempfile import os import sys def test_link(function_signature, flags): function_name = function_signature.split('(')[0].split()[-1].strip() with tempfile.NamedTemporaryFile(delete=False, suffix='.c', mode='w') as temp_file: test_code = f''' #include <stdio.h> {function_signature} int main() {{ // Using the function address to bypass direct function call void *ptr = (void*){function_name}; return 0; }} ''' temp_file.write(test_code) temp_file_path = temp_file.name compile_command = f'gcc {temp_file_path} {" ".join(flags)} -o {temp_file.name}.out' try: subprocess.check_output(compile_command, shell=True, stderr=subprocess.STDOUT) result_message = "Linking succeeded." except subprocess.CalledProcessError as e: result_message = "Linking failed. Error:\n" + e.output.decode('utf-8') # Clean up os.remove(temp_file_path) out_path = f'{temp_file.name}.out' if os.path.exists(out_path): os.remove(out_path) return result_message def main(): if '--' in sys.argv: split_index = sys.argv.index('--') function_signature = sys.argv[1] flags = sys.argv[split_index+1:] else: print("Usage: canlink.py \"function signature\" -- [flags]") sys.exit(1) result = test_link(function_signature, flags) print(result) if __name__ == "__main__": main()

它可以制作出漂亮的衬垫。

© www.soinside.com 2019 - 2024. All rights reserved.