【问题标题】:Using dlsym() to stub malloc/free leads to segmentation fault使用 dlsym() 存根 malloc/free 会导致分段错误
【发布时间】:2020-06-14 18:05:28
【问题描述】:

我开始涉足单元测试 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.solibstubs.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 并且它工作。但是我不想浪费时间重新发明轮子,而且我确信我在链接方面做错了,因为我对编译/链接了解不多。

对于编译,我使用的是介子构建系统,所以我不知道如何获取确切的命令行参数,但我可以编写我的构建目标的 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')

【问题讨论】:

  • dlsym 可能会打电话给malloc
  • 是的,我注意到了。但是,如果不使用 dlsym 缓存真正的 malloc,我将如何处理 malloc。我读到 gnu 链接器有一个使用 __wrap_symbol 和 __real_symbol 包装符号的选项,但我不想将自己绑定到特定的链接器。
  • 您的实际目标是什么?你想用这个来完成什么?
  • 我的库使用 malloc 和 calloc 动态分配一些结构。我想强制他们返回 NULL 以检查是否正确处理了 OOM,而无需为每个单元测试填充计算机的 RAM。
  • 可以通过前身定义劫持malloc/calloc;或者您可以简单地使用 ulimit -v 限制可用内存

标签: c unit-testing linker meson-build dlsym


【解决方案1】:

尝试使用LD_PRELOAD trick。完成它的介子式方法是:

  test_env = environment()
  test_env.prepend('LD_PRELOAD', stubs.full_path())
  test('test', test_exe, suite: 'suite', env: test_env)

注意:不要将可执行文件与存根链接。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-21
    • 1970-01-01
    • 2019-07-31
    • 1970-01-01
    相关资源
    最近更新 更多