Address Sanitizer, или Что делать, если не работает valgrind

Случилась непростая ситуация. Есть код, написанный на С, который активно используется через CGo в проекте, написанном на Go. В какой-то момент программа начала падать с ошибками от malloc: то segfault, то memory corruption.

Логичная мысль: нужен valgrind с его memcheck, чтобы проверить, кто лезет поперёк батьки в пекло в невалидную память. Однако, попытка скормить валгринду бинарник, полученный от go build, приведёт только к разочарованию — даже на простом Hello World валгринд разразится сотнями ошибок и отправит разработчика на известные координаты.

Это происходит из-за того, что go runtime довольно специфичен и неплохо отличается от такового в С. (Подробности можно спокойно нагуглить по запросу «golang valgrind»).

Так как же нам разобраться, что происходит?

Пристальный гуглёж показал мне, что у gcc (и у clang, кстати) есть очень удобный инструмент — Address Sanitizer. Удобен он тем, что умеет делать штуки, подвластные valgrind (как минимум, умеет ловить использование освобождённой памяти, всяческие переполнения и утечки), и при этом автоматически встраивается в бинарник без необходимости использовать внешние утилиты. Но главное — его можно спокойно использовать в CGo для отладки С-кода без помех для Go runtime (собственно, сами разработчики Go рекомендуют использовать этот инструмент).

Как его использовать?

  1.  Проверить, что gcc у нас версии не ниже 4.8.
  2. Компилировать нашу программу с флагом -fsanitize=address (в документации к gcc описано ещё много санитайзеров, смотри тут). Этот флаг надо добавить как при сборке, так и при линковке (и в CFLAGS, и в LDFLAGS).
  3. Запускать программу и радоваться разноцветному выводу санитайзера :)

Для проверки я написал простейшую программу, которая выделяет места на 10 интов, но заполняет 11.

Скомпилировал это с -fsanitize=address, после запуска увидел вот это:

На деле, это всё ещё довольно удобно подсвечивается цветами:

Для того, чтобы это заработало с CGo, я добавил соответствующие спецкомментарии при подключении кода на C:

И даже получил какой-то полезный вывод по моей ошибке :)

Беру инструмент на заметку.

Оставить комментарий