Использование dlsym() для заглушки malloc/free приводит к ошибке сегментации

Я начал баловаться модульным тестированием кода 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, содержащим определения для двух сеттеров. Затем я скомпилировал 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. Запуск указанного исполняемого файла встречает меня segfault. Проверка сбоя с помощью gdb заставляет меня поверить, что я столкнулся с переполнением стека из-за бесконечной рекурсии (gdb backtrace):

#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, и это сработало. Однако я не хочу тратить время на изобретение велосипеда, и я уверен, что что-то делаю неправильно при компоновке, так как я мало знаю о компиляции/связывании.

Для компиляции я использую систему сборки meson, поэтому я не знаю, как получить точные аргументы командной строки, но я могу написать 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')

person BreadyX    schedule 14.06.2020    source источник
comment
dlsym вполне может позвонить malloc   -  person Lorinczy Zsigmond    schedule 14.06.2020
comment
Да я заметил. Но как мне заглушить malloc, не используя dlsym для кэширования реального malloc. Я читал, что компоновщик gnu имеет возможность оборачивать символы с помощью __wrap_symbol и __real_symbol, но я не хочу привязывать себя к конкретному компоновщику.   -  person BreadyX    schedule 14.06.2020
comment
Какова ваша фактическая цель? Что вы хотите этим добиться?   -  person Lorinczy Zsigmond    schedule 15.06.2020
comment
Моя библиотека динамически выделяет некоторые структуры, используя malloc и calloc. Я хочу заставить их возвращать NULL, чтобы проверить, правильно ли обрабатывается OOM, не заполняя ОЗУ моего компьютера для каждого модульного теста.   -  person BreadyX    schedule 15.06.2020
comment
Вы можете перехватить malloc/calloc через определения препрецессора; или вы можете просто ограничить доступную память с помощью ulimit -v   -  person Lorinczy Zsigmond    schedule 15.06.2020


Ответы (1)


Попробуйте использовать трюк LD_PRELOAD. Мезонный способ сделать это:

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

примечание: не связывайте исполняемый файл с заглушками.

person pmod    schedule 16.06.2020