Как стать автором
Обновить

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

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

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

Это происходит из-за того, что 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 (test.c):

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
        int *m = (int *) malloc(10 * sizeof (int));

        for (int i = 0; i < 11; i++) {
                m[i] = i;
        }

        return 0;
}

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

webconn@webconn-laptop:~/Projects/Testing/C/Sanitizer$ gcc -o ./main ./test.c -fsanitize=address
webconn@webconn-laptop:~/Projects/Testing/C/Sanitizer$ ./main 
=================================================================
==18098==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x557945c74922 bp 0x7fff94c67d40 sp 0x7fff94c67d38
WRITE of size 4 at 0x60400000dff8 thread T0
    #0 0x557945c74921 in main (/home/webconn/Projects/Testing/C/Sanitizer/main+0x921)
    #1 0x7fea6440b2b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0)
    #2 0x557945c747a9 in _start (/home/webconn/Projects/Testing/C/Sanitizer/main+0x7a9)

0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8)
allocated by thread T0 here:
    #0 0x7fea6484ad28 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.3+0xc1d28)
    #1 0x557945c748c8 in main (/home/webconn/Projects/Testing/C/Sanitizer/main+0x8c8)
    #2 0x7fea6440b2b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/webconn/Projects/Testing/C/Sanitizer/main+0x921) in main
Shadow bytes around the buggy address:
  0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa]
  0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==18098==ABORTING

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

Вывод в цвете (PNG, 78.5 KB)
image

Для того, чтобы это заработало с cgo, я добавил соответствующие спецкомментарии при подключении кода на C (на месте многоточий могли бы быть Ваши полезные флаги и директивы):

/*
#cgo linux LDFLAGS: ... -fsanitize=address
#cgo linux CFLAGS: ... -fsanitize=address
...
*/
import "C"

Что и дало желаемый результат — теперь модуль, написанный на С, выдаёт отладочную информацию при проблемах с памятью, но не мешает спокойно жить Go runtime.

Собственно, сами разработчики Go рекомендуют использовать Sanitizer в подобных ситуациях.

Надеюсь, информация окажется кому-то полезной.

UPD: Конечно же, если добавить флаг -g, вывод будет ещё чуть более читаемым.
Вывод после компиляции с флагом -g
webconn@webconn-laptop:~/Projects/Testing/C/Sanitizer$ ./main 
=================================================================
==12266==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x55d4f8d03922 bp 0x7ffc10a57370 sp 0x7ffc10a57368
WRITE of size 4 at 0x60400000dff8 thread T0
    #0 0x55d4f8d03921 in main /home/webconn/Projects/Testing/C/Sanitizer/main.c:14
    #1 0x7fa688d282b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0)
    #2 0x55d4f8d037a9 in _start (/home/webconn/Projects/Testing/C/Sanitizer/main+0x7a9)

0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8)
allocated by thread T0 here:
    #0 0x7fa689167d28 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.3+0xc1d28)
    #1 0x55d4f8d038c8 in main /home/webconn/Projects/Testing/C/Sanitizer/main.c:11
    #2 0x7fa688d282b0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202b0)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/webconn/Projects/Testing/C/Sanitizer/main.c:14 in main
Shadow bytes around the buggy address:
  0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa]
  0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==12266==ABORTING

Теги:
Хабы:
Всего голосов 26: ↑25 и ↓1+24
Комментарии8

Публикации

Истории

Работа

Программист С
51 вакансия
Go разработчик
150 вакансий

Ближайшие события

19 августа – 20 октября
RuCode.Финал. Чемпионат по алгоритмическому программированию и ИИ
МоскваНижний НовгородЕкатеринбургСтавропольНовосибрискКалининградПермьВладивостокЧитаКраснорскТомскИжевскПетрозаводскКазаньКурскТюменьВолгоградУфаМурманскБишкекСочиУльяновскСаратовИркутскДолгопрудныйОнлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
10 – 11 октября
HR IT & Team Lead конференция «Битва за IT-таланты»
МоскваОнлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн