Обнаружение определенных вызовов функций в модульных тестах

Я хотел бы быть в состоянии определить, будет ли моя функция (или любая другая функция, которую она вызывает) в конечном итоге вызывать некоторые конкретные функции (например, malloc а также free) в моих модульных тестах: некоторые небольшие части моего программного обеспечения предъявляют жесткие требования в реальном времени, и я хотел бы убедиться, что никто не добавляет что-то, что могло бы вызвать случайное распределение в этих функциях (и чтобы мой конвейер CI проверил это автоматически).

Я знаю, что могу просто поставить точку останова на gdb, но в идеале я хотел бы сделать что-то вроде:

void my_unit_test() {
my_object obj; // perform some initialization that will allocate

START_CHECKING_FUNCTION(malloc); // also, operator new or std::allocate would be nice
obj.perform_realtime_stuff();
STOP_CHECKING_FUNCTION(malloc);
}

в идеале, тест должен пройти не слишком грязно (например, не std::abort) если в какой-то момент malloc вызывается между двумя проверками.

В идеале, это будет работать в любой системе, но я могу жить с тем, что пока работает только в Linux. Возможно ли это как-то? Может быть, через взлом LD_PRELOAD, который заменит malloc, но я бы предпочел не делать это для всех функций, которые меня интересуют.

8

Решение

Модульные тесты вызывают функции, которые они тестируют. Вы хотите знать, может ли функция F, вызванная модульным тестом, в конечном итоге вызвать malloc (или new, или …). Похоже, что вы действительно хотите сделать, это построить граф вызовов для всей вашей системы, а затем спросить критические функции F, может ли F достичь malloc и т. Д. В графе вызовов. Это довольно легко вычислить, если у вас есть граф вызовов.

Получить график звонков не так просто. Обнаружение того, что модуль A вызывает модуль B напрямую, «технически легко», если у вас есть реальный пользовательский интерфейс, который выполняет разрешение имен. Узнать, что А вызывает косвенно, нелегко; вам нужен (указатель на функцию) точки для анализа, и это сложно. И, конечно, вы должны решить, собираетесь ли вы погрузиться в библиотеку (например, процедуры std: 🙂 или нет.

Ваш график вызовов должен быть консервативным (чтобы вы не пропустили потенциальные вызовы) и достаточно точным (чтобы вы не утонули в ложных срабатываниях) перед лицом указателей функций и вызовов методов.

Эта поддержка Doxygen утверждает, что создает графы вызовов: http://clang.llvm.org/doxygen/CallGraph_8cpp.html Я не знаю, обрабатывает ли он косвенные вызовы / вызовы методов или насколько он точен; Я не очень знаком с этим, и документация кажется тонкой. Doxygen в прошлом не имел репутации хорошо справляться с косвенным обращением или быть точным, но предыдущие версии не были основаны на Clang. Существует дальнейшее обсуждение этого в малом масштабе на http://stackoverflow.com/questions/5373714/generate-calling-graph-for-c-code

Ваш вопрос помечен c / c ++, но, похоже, касается C ++. Для C наш DMS Software Reengineering Toolkit с его общим анализом потока и поддержкой генерации графа вызовов в сочетании с DMS C Front End, был использован для анализировать системы C из примерно 16 миллионов строк / 50000 функций с помощью косвенных вызовов создавать консервативно правильные графы вызовов.

Мы специально не пытались создавать графы вызовов C ++ для больших систем, но тот же общий анализ потоков DMS и генерация графов вызовов были бы «технически простыми» для DMS. C ++ Front End. При построении статического анализа, который работает правильно и в масштабе, нет ничего тривиального.

3

Другие решения

Если вы используете библиотеки, которые вызывают malloc, вы можете взглянуть на Joint Strike Fighter C ++ Стандарты кодирования. Это стиль кодирования, ориентированный на критически важное программное обеспечение. Одним из предложений будет написать свой собственный распределитель (и). Другое предложение состоит в том, чтобы использовать что-то вроде jemalloc который имеет статистику, но гораздо более непредсказуемым, поскольку он ориентирован на производительность.


То, что вы хотите, это библиотека-шутник с шпионскими возможностями. Как это работает для каждого фреймворка, может отличаться, но вот пример с использованием Google:

static std::function<void*(size_t)> malloc_bridge;

struct malloc_mock {
malloc_mock() { malloc_bridge = std::bind(&malloc_mock::mock_, this, _1); }
MOCK_METHOD1(mock_, void*(size_t));
}

void* malloc_cheat(size_t size) {
return malloc_bridge(size);
}

#define malloc malloc_cheat

struct fixture {
void f() { malloc(...); }
};

struct CustomTest : ::testing::test {
malloc_mock mock_;
};

TEST_F(CustomTest, ShouldMallocXBytes) {
EXPECT_CALL(mock_, mock_(X))
.WillOnce(::testing::Return(static_cast<void*>(0)));
Fixture fix;
fix.f();
}

#undef malloc

ПРЕДУПРЕЖДЕНИЕ: Код не был затронут руками компилятора. Но ты получил идею.

1

Это не полный ответ, но вы можете попытаться использовать Valgrind для подсчета выделений и освобождений. Инструмент проверки по умолчанию Valgrind по умолчанию подсчитывает количество выделений, освобождает и печатает итоговый отчет в HEAP SUMMARYВот пример вывода:

$ valgrind ./a.out
==2653== Memcheck, a memory error detector
==2653== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2653== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==2653== Command: ./a.out
==2653==
==2653==
==2653== HEAP SUMMARY:
==2653==     in use at exit: 0 bytes in 0 blocks
==2653==   total heap usage: 2 allocs, 2 frees, 72,716 bytes allocated
==2653==
==2653== All heap blocks were freed -- no leaks are possible
==2653==
==2653== For counts of detected and suppressed errors, rerun with: -v
==2653== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Вы можете добавить еще один тест, ничего не делая, чтобы подсчитать количество базовых распределений:

void my_unit_test_baseline() {
my_object obj; // perform some initialization that will allocate
}

Теперь вы можете запустить реальный тест и сравнить количество выделений с базовым тестом. Если они не равны, в вашем тестированном коде произошли некоторые выделения. Вы можете зафиксировать этот факт или сообщить о нем другим способом.

1

Если вы используете библиотеку GNU C, вы можете использовать _malloc_hook () и подобные функции, чтобы пользовательская функция вызывалась всякий раз, когда одна из функций malloc семья используется.

Такая подключенная функция может анализировать трассировку вызова (используя backtrace()) чтобы найти malloc было разрешено в этой цепочке вызовов или нет и печатать сообщения о виновнике, если нет.

1
По вопросам рекламы [email protected]