我刚刚发现我使用的一个 C API 中缺少一个函数(它可以编译,但不能链接)。我想测试整套 API 函数。我们的想法是让事情变得简单,例如:
include <myapi.h>
void main(void) {
func1;
func2;
...
}
但是这可以很好地编译和链接,因为编译器(gcc)优化掉了未使用的函数指针。如果我使用 func1() 代替,如果该函数确实丢失,我会收到链接器错误。但所有的函数调用都很复杂,为所有函数编写显式参数需要花费几天的时间。
我可以使用 nm 来查询库,但首先它是一整套 .so 文件,其次它可能取决于 API 标头中的 #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) 和 dlsym(3) 函数来查明(在运行时)该共享对象是否定义了给定符号。但请注意,在 ELF 文件中,符号几乎是无类型的(例如,如果没有某个 C 头文件声明它,您无法从 ELF 共享对象中的某些函数的名称知道其签名)。
或者,您可能有一个更复杂的软件构建添加临时规则)。请记住,您可以使用“元编程”技术,并在构建中使用一些专门的 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
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/b4a7528245c00625f80cf31fe38e9829inline
你这样使用它:
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()
它可以制作出漂亮的衬垫。