如何触发 C 中测试的错误代码路径?
How to trigger erroneous code paths for tests in C?
我经常为我的程序编写测试以确保它们在任何情况下都能 100% 地工作(这样以后我就不必花几个小时调试它们)。
然而,到目前为止我所有的测试都只是测试在正常情况下我可以到达的代码行,所以例如我没有测试如果 malloc()
失败会发生什么。不过我真的也想这样做,所以最近我开始尝试并寻求有关如何覆盖任何函数的答案,以便它 returns 任何预定义的值并可能将 errno 设置为某个值。
我了解了链接器的 dlsym、LD_PRELOAD 和 --wrap
。到目前为止,我在 dlsym 上取得了很多成功,甚至覆盖 malloc()
并且它不会陷入无限循环,但是我用来查找内存泄漏的 Valgrind 似乎很高兴地忽略了我包装的任何函数dlsym 等 malloc()
在我不希望它在 Valgrind 下运行时开始正常运行(因此人为返回 NULL
不起作用)。
LD_PRELOAD 几乎与 dlsym 一起使用,它本身并没有真正做任何事情。
--wrap
很好,但它的缺点是您需要知道要为每个文件覆盖的功能。这很烦人 - 使用 dlsym 我可以在我的代码中选择函数,仅此而已,但是 --wrap
会使我的 Makefile 变得更加复杂。
我也尝试过使用 libc 的隐藏定义(通常是 __libc_malloc()
、__libc_function_name()
)来覆盖 malloc()
、strxxx()
和其他函数的弱符号Valgrind 喜欢覆盖然后使用 dlsym 来处理其他所有内容,有点像这样:
void* __libc_malloc(size_t);
void* malloc(size_t size) {
if(malloc_fail) {
errno = ENOMEM;
return NULL;
}
return __libc_malloc(size);
}
但话又说回来,Valgrind 也很乐意覆盖这个 malloc 实现,让我别无选择。
我也尝试过将我的内存泄漏检测器切换到 libasan(gcc -fsanitize=address
)之类的东西,但不幸的是,我对 gcc 附带的任何消毒剂都有非常糟糕的体验,因为它们的错误实现使用 pthread_cancel()
(like here) 时实例崩溃,结果我的代码需要很好地测试一些 pthread 函数,因为我将它们包装在我自己的函数和结构中。
有没有其他方法可以触发错误的代码路径?我做错了什么吗?
Valgrind 为 malloc
、calloc
和 free
提供了自己的包装器,这对其操作至关重要。这些取代了您尝试为这些函数指定的任何包装器,我一点也不感到惊讶。主要的选择是相反的:你的包装器取代了 valgrind 的。
我建议将您的测试简单地分为那些依赖于您注入包装器的测试和那些不依赖于您注入包装器的测试。仅将 Valgrind 用于后一组的测试。
执行此操作的方法是调用您的函数而不是 malloc
等(例如 my_malloc
)。
如果你这样做了并且你仍然希望 Valgrind 分析由 my_malloc
返回的内存,你将需要使用 Valgrind 客户端机制来告诉 Valgrind 你的内存池,分配等
看看这个 article 的例子。
我经常为我的程序编写测试以确保它们在任何情况下都能 100% 地工作(这样以后我就不必花几个小时调试它们)。
然而,到目前为止我所有的测试都只是测试在正常情况下我可以到达的代码行,所以例如我没有测试如果 malloc()
失败会发生什么。不过我真的也想这样做,所以最近我开始尝试并寻求有关如何覆盖任何函数的答案,以便它 returns 任何预定义的值并可能将 errno 设置为某个值。
我了解了链接器的 dlsym、LD_PRELOAD 和 --wrap
。到目前为止,我在 dlsym 上取得了很多成功,甚至覆盖 malloc()
并且它不会陷入无限循环,但是我用来查找内存泄漏的 Valgrind 似乎很高兴地忽略了我包装的任何函数dlsym 等 malloc()
在我不希望它在 Valgrind 下运行时开始正常运行(因此人为返回 NULL
不起作用)。
LD_PRELOAD 几乎与 dlsym 一起使用,它本身并没有真正做任何事情。
--wrap
很好,但它的缺点是您需要知道要为每个文件覆盖的功能。这很烦人 - 使用 dlsym 我可以在我的代码中选择函数,仅此而已,但是 --wrap
会使我的 Makefile 变得更加复杂。
我也尝试过使用 libc 的隐藏定义(通常是 __libc_malloc()
、__libc_function_name()
)来覆盖 malloc()
、strxxx()
和其他函数的弱符号Valgrind 喜欢覆盖然后使用 dlsym 来处理其他所有内容,有点像这样:
void* __libc_malloc(size_t);
void* malloc(size_t size) {
if(malloc_fail) {
errno = ENOMEM;
return NULL;
}
return __libc_malloc(size);
}
但话又说回来,Valgrind 也很乐意覆盖这个 malloc 实现,让我别无选择。
我也尝试过将我的内存泄漏检测器切换到 libasan(gcc -fsanitize=address
)之类的东西,但不幸的是,我对 gcc 附带的任何消毒剂都有非常糟糕的体验,因为它们的错误实现使用 pthread_cancel()
(like here) 时实例崩溃,结果我的代码需要很好地测试一些 pthread 函数,因为我将它们包装在我自己的函数和结构中。
有没有其他方法可以触发错误的代码路径?我做错了什么吗?
Valgrind 为 malloc
、calloc
和 free
提供了自己的包装器,这对其操作至关重要。这些取代了您尝试为这些函数指定的任何包装器,我一点也不感到惊讶。主要的选择是相反的:你的包装器取代了 valgrind 的。
我建议将您的测试简单地分为那些依赖于您注入包装器的测试和那些不依赖于您注入包装器的测试。仅将 Valgrind 用于后一组的测试。
执行此操作的方法是调用您的函数而不是 malloc
等(例如 my_malloc
)。
如果你这样做了并且你仍然希望 Valgrind 分析由 my_malloc
返回的内存,你将需要使用 Valgrind 客户端机制来告诉 Valgrind 你的内存池,分配等
看看这个 article 的例子。