使用 dlsym() 存根 malloc/free 导致分段错误
Using dlsym() to stub malloc/free leads to segmentation fault
我开始涉足单元测试 C 代码(使用检查)和存根函数。我正在努力团结
测试我编写的一个小型数据结构库,想测试它对 OOM 的反应。所以我
编写了一个简单的 stubs.c
文件,其中包含:
#include <stdlib.h>
#include <errno.h>
#include <dlfcn.h>
static int malloc_fail_code = 0;
static int calloc_fail_code = 0;
void set_malloc_fail_code(int no) { malloc_fail_code = no; }
void set_calloc_fail_code(int no) { calloc_fail_code = no; }
void *malloc(size_t size)
{
static void *(*real_malloc)(size_t) = NULL;
if (!real_malloc)
real_malloc = (void *(*)(size_t)) dlsym(RTLD_NEXT, "malloc");
if (malloc_fail_code != 0) {
errno = malloc_fail_code;
malloc_fail_code = 0;
return NULL;
}
return real_malloc(size);
}
void *calloc(size_t nmemb, size_t size)
{
static void *(*real_calloc)(size_t, size_t) = NULL;
if (!real_calloc)
real_calloc = (void *(*)(size_t, size_t)) dlsym(RTLD_NEXT, "calloc");
if (calloc_fail_code != 0) {
errno = calloc_fail_code;
calloc_fail_code = 0;
return NULL;
}
return real_calloc(nmemb, size);
}
及其相关 stubs.h
包含两个 setter 的定义。然后我将 stubs.c 编译为
名为 libstubs.so
的共享对象。我还将我的库编译为一个名为
libmy_lib.so
.
我的测试代码在test.c
是这样的:
#include <stdlib.h>
#include <errno.h>
#include <check.h>
#include "my_lib.h"
#include "stubs.h"
START_TEST(my_test)
{
... // using the two setters I force malloc and calloc to return null and set errno to ENOMEM
}
END_TEST
... // check boilerplate to create suite and add tests
然后我将测试可执行文件链接到 libmy_lib.so
和 libstubs.so
。 运行 说可执行文件用段错误问候我。使用 gdb 检查崩溃让我相信我遇到了由于无限递归(gdb 回溯)导致的堆栈溢出:
#0 0x00007ffff7fc143c in calloc (
nmemb=<error reading variable: Cannot access memory at address 0x7fffff7feff8>,
size=<error reading variable: Cannot access memory at address 0x7fffff7feff0>)
at stubs.c
#1 0x00007ffff7db9c88 in _dlerror_run (operate=operate@entry=0x7ffff7db94f0 <dlsym_doit>,
args=args@entry=0x7fffff7ff030) at dlerror.c:148
#2 0x00007ffff7db9570 in __dlsym (handle=<optimized out>, name=<optimized out>) at dlsym.c:70
#3 0x00007ffff7fc1487 in calloc (nmemb=1, size=32) at stubs.c
...
我尝试将 stubs.c
直接包含到 test.c
中,但没有成功。我还尝试编写一个我自己的小型单元测试框架,它扩展了 stubs.c
并且它有效。但是,我不想浪费时间重新发明轮子,而且我确信我在链接时做错了什么,因为我对 compilation/linking.
知之甚少。
对于编译,我使用的是介子构建系统,所以我不知道如何获得确切的命令行参数,但我可以编写构建目标的 MWE:
lib = library(
'my_lib',
sources,
include_directories: includes,
install: true
)
stubs = shared_library(
'stubs',
'stubs.c',
c_args: ['-g'],
include_directories: test_includes,
link_args: ['-ldl']
)
test_exe = executable(
'test_exe',
c_args: ['-g'],
sources: 'test.c',
dependencies: check,
link_with: [stubs, lib],
include_directories: includes + test_includes
)
test('test', test_exe, suite: 'suite')
尝试使用 LD_PRELOAD trick。实现它的介子式方法是:
test_env = environment()
test_env.prepend('LD_PRELOAD', stubs.full_path())
test('test', test_exe, suite: 'suite', env: test_env)
注意:不要 link 使用存根执行。
我开始涉足单元测试 C 代码(使用检查)和存根函数。我正在努力团结
测试我编写的一个小型数据结构库,想测试它对 OOM 的反应。所以我
编写了一个简单的 stubs.c
文件,其中包含:
#include <stdlib.h>
#include <errno.h>
#include <dlfcn.h>
static int malloc_fail_code = 0;
static int calloc_fail_code = 0;
void set_malloc_fail_code(int no) { malloc_fail_code = no; }
void set_calloc_fail_code(int no) { calloc_fail_code = no; }
void *malloc(size_t size)
{
static void *(*real_malloc)(size_t) = NULL;
if (!real_malloc)
real_malloc = (void *(*)(size_t)) dlsym(RTLD_NEXT, "malloc");
if (malloc_fail_code != 0) {
errno = malloc_fail_code;
malloc_fail_code = 0;
return NULL;
}
return real_malloc(size);
}
void *calloc(size_t nmemb, size_t size)
{
static void *(*real_calloc)(size_t, size_t) = NULL;
if (!real_calloc)
real_calloc = (void *(*)(size_t, size_t)) dlsym(RTLD_NEXT, "calloc");
if (calloc_fail_code != 0) {
errno = calloc_fail_code;
calloc_fail_code = 0;
return NULL;
}
return real_calloc(nmemb, size);
}
及其相关 stubs.h
包含两个 setter 的定义。然后我将 stubs.c 编译为
名为 libstubs.so
的共享对象。我还将我的库编译为一个名为
libmy_lib.so
.
我的测试代码在test.c
是这样的:
#include <stdlib.h>
#include <errno.h>
#include <check.h>
#include "my_lib.h"
#include "stubs.h"
START_TEST(my_test)
{
... // using the two setters I force malloc and calloc to return null and set errno to ENOMEM
}
END_TEST
... // check boilerplate to create suite and add tests
然后我将测试可执行文件链接到 libmy_lib.so
和 libstubs.so
。 运行 说可执行文件用段错误问候我。使用 gdb 检查崩溃让我相信我遇到了由于无限递归(gdb 回溯)导致的堆栈溢出:
#0 0x00007ffff7fc143c in calloc (
nmemb=<error reading variable: Cannot access memory at address 0x7fffff7feff8>,
size=<error reading variable: Cannot access memory at address 0x7fffff7feff0>)
at stubs.c
#1 0x00007ffff7db9c88 in _dlerror_run (operate=operate@entry=0x7ffff7db94f0 <dlsym_doit>,
args=args@entry=0x7fffff7ff030) at dlerror.c:148
#2 0x00007ffff7db9570 in __dlsym (handle=<optimized out>, name=<optimized out>) at dlsym.c:70
#3 0x00007ffff7fc1487 in calloc (nmemb=1, size=32) at stubs.c
...
我尝试将 stubs.c
直接包含到 test.c
中,但没有成功。我还尝试编写一个我自己的小型单元测试框架,它扩展了 stubs.c
并且它有效。但是,我不想浪费时间重新发明轮子,而且我确信我在链接时做错了什么,因为我对 compilation/linking.
对于编译,我使用的是介子构建系统,所以我不知道如何获得确切的命令行参数,但我可以编写构建目标的 MWE:
lib = library(
'my_lib',
sources,
include_directories: includes,
install: true
)
stubs = shared_library(
'stubs',
'stubs.c',
c_args: ['-g'],
include_directories: test_includes,
link_args: ['-ldl']
)
test_exe = executable(
'test_exe',
c_args: ['-g'],
sources: 'test.c',
dependencies: check,
link_with: [stubs, lib],
include_directories: includes + test_includes
)
test('test', test_exe, suite: 'suite')
尝试使用 LD_PRELOAD trick。实现它的介子式方法是:
test_env = environment()
test_env.prepend('LD_PRELOAD', stubs.full_path())
test('test', test_exe, suite: 'suite', env: test_env)
注意:不要 link 使用存根执行。