AddressSanitizer в ROSA Fresh
В GCC 4.8 появились новые полезные инструменты для поиска ошибок в приложениях, в частности:
- AddressSanitizer, как и Valgrind (точнее, Memcheck) выявляет различные варианты некорректного использования памяти, но значительно меньше замедляет работу анализируемой программы при этом;
- ThreadSanitizer выявляет «состояния гонки» («data races»).
Ещё раньше такие инструменты появились для LLVM/Clang.
AddressSanitizer (ASan)
Чтобы использовать AddressSanitizer для анализа приложения (или библиотеки), нужно собрать последнее специальным образом. Сам анализ проводится во время работы этого приложения (это динамический анализ, не статический).
Лучше всё делать на 64-битной системе. AddressSanitizer удавалось запустить и на 32-битной системе, а вот ThreadSanitizer работает только на 64-битных.
Для работы с AddressSanitizer потребуются следующие пакеты: lib64asan0 и lib64asan-devel (чтобы установить всё необходимое, можно просто выполнить urpmi asan-devel
).
Пример
Рассмотрим такой пример:
/* test.c */ 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int 5 main(int argc, char *argv[]) 6 { 7 int *p = malloc(10 * sizeof(int)); 8 if (!p) 9 return 1; 10 11 printf("Hello, World!\n"); 12 printf("Data: %d\n", p[10]); /// < Выход за границы блока памяти. 13 return 0; 14 }
При сборке приложения нужно добавить опцию компилятора -fsanitize=address и слинковать приложение с библиотекой libasan. Стоит также включить генерацию отладочной информации. Оптимизацию можно не отключать.
gcc -g -O2 -fsanitize=address -o test test.c -lasan
Если теперь запустить полученное приложение, будет выдано следующее:
$ ./test Hello, World! ================================================================= ==2772==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x40085f bp 0x7fff488e7aa0 sp 0x7fff488e7a98 READ of size 4 at 0x60400000dff8 thread T0 #0 0x40085e in main /home/user/work/asan_examples/test.c:12 #1 0x7ff1d50c4fbf in __libc_start_main (/lib64/libc.so.6+0x1ffbf) #2 0x4008a4 (/home/user/work/asan_examples/test+0x4008a4) 0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8) allocated by thread T0 here: #0 0x7ff1d54bea9f in __interceptor_malloc (/usr/lib64/libasan.so.1+0x54a9f) #1 0x40080a in main /home/user/work/asan_examples/test.c:7 SUMMARY: AddressSanitizer: heap-buffer-overflow /home/user/work/asan_examples/test.c:12 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 Contiguous container OOB:fc ASan internal: fe ==2772==ABORTING
То есть AddressSanitizer выявил некорректную операцию чтения из памяти в строке 12 файла test.c.
Определение места в исходном коде по адресу инструкции
Если приложение собрано с отладочной информацией (debug info), то, как правило, AddressSanitizer автоматически определяет места в исходном коде приложения, где произошли подозрительные события. Если же по каким-то причинам AddressSanitizer смог определить только адреса соотв. инструкций, нужные места в коде можно найти с помощью addr2line.
В отчёте AddressSanitizer указано, что инструкция, которая выполнила подозрительную операцию с памятью, находилась по адресу 0x40085f. Блок памяти был выделен здесь: 0x40080a.
Определить, каким строкам исходного кода соответствуют эти адреса, можно так:
$ addr2line -e test -i 0x40085f /home/user/work/asan_examples/test.c:12 $ addr2line -e test -i 0x40080a /home/user/work/asan_examples/test.c:7
Поиск утечек памяти
В AddressSanitizer есть и средства для поиска утечек памяти. Чтобы их включить, необходимо при запуске анализируемого приложения задать нужное значение в переменной среды ASAN_OPTIONS
:
ASAN_OPTIONS=detect_leaks=1 <анализируемое_приложение>
AddressSanitizer, похоже, не считает утечкой памяти ситуацию, когда блок памяти не освобожден самим приложением при выходе, но указатель на этот блок всё ещё есть («still reachable» memory, в терминологии Valgrind). Немного изменим приведённый выше пример:
/* test.c */ 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int 5 main(int argc, char *argv[]) 6 { 7 int *p = malloc(10 * sizeof(int)); 8 if (!p) 9 return 1; 10 11 printf("Hello, World!\n"); 12 printf("Data: %d\n", p[9]); 13 return 0; 14 }
Память, выделенная в строке 7, не освобождается самим приложением, но указатель на неё (p
) «живёт» до выхода из main
. AddressSanitizer не находит утечек памяти в данном случае.
Однако, если выделить ещё один блока памяти и адрес его сохранить в переменной p
, указателей на старый блок памяти не останется:
/* test.c */ 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int 5 main(int argc, char *argv[]) 6 { 7 int *p = malloc(10 * sizeof(int)); 8 if (!p) 9 return 1; 10 11 printf("Hello, World!\n"); 12 printf("Data: %d\n", p[9]); 13 14 p = malloc(10 * sizeof(int)); // Указатель на блок памяти перезаписан 15 printf("Data: %d\n", p[9]); 16 return 0; 17 }
AddressSanitizer сообщит об этом как об утечке памяти:
==18310==ERROR: LeakSanitizer: detected memory leaks Direct leak of 40 byte(s) in 1 object(s) allocated from: #0 0x7fc4a6a97a9f in __interceptor_malloc (/usr/lib64/libasan.so.1+0x54a9f) #1 0x400858 in main /home/eugene/work/asan_examples/test_leak.c:14 SUMMARY: AddressSanitizer: 40 byte(s) leaked in 1 allocation(s).
Полезные настройки для AddressSanitizer
В переменной среды ASAN_OPTIONS можно указать настройки для AddressSanitizer при запуске анализируемого приложения. Из часто используемого:
- detect_leaks=0/1 — если задать detect_leaks=1, AddressSanitizer будет также искать утечки памяти;
- log_path=<путь_к_файлу> — куда выводить отчёт AddressSanitizer’а вместо stderr; к имени указанного файла может автоматически добавляться pid соотв. процесса.
Опции разделяются двоеточием.
Как включить «всё и сразу»
ASAN_OPTIONS=detect_leaks=1:log_path=./asan.log my_application
Для приложения my_application будет выполнен поиск как некорректных обращений к памяти, так и утечек памяти. Отчёт AddressSanitizer будет выведен в файл asan.log.<pid> в текущем каталоге.
ThreadSanitizer (TSan)
Для использования ThreadSanitizer нужно выполнить похожие операции. Потребуются пакеты lib64tsan0 и lib64tsan-devel. Анализируемое приложение надо собирать так:
gcc -g -fPIE -fsanitize=thread -o test test.c -pie -ltsan
Без -fPIE и -pie сборка может не пройти.
Одновременно использовать оба инструмента (то есть задавать и -fsanitize=thread, и -fsanitize=address) не рекомендуется.
Если приложение использует Qt, то, вероятно, стоит и библиотеки Qt собрать с включенным TSan.
Suppressions
Иногда TSan выдаёт очень много сообщений о races, среди которых сложно отыскать нужные (допустим, часть сообщений относится к уже известным проблемам, а хочется найти именно новые).
Можно использовать suppressions, чтобы TSan не выводил сообщения о заданных races: http://code.google.com/p/thread-sanitizer/wiki/Suppressions
Настройки
Дополнительные настройки TSan, которые можно задать при запуске анализируемых приложений: https://code.google.com/p/thread-sanitizer/wiki/Flags#Runtime_Flags
Отладка под GDB приложений, собранных с включенным TSan
Под GDB приложение может отказаться запускаться и выдать такую ошибку:
FATAL: ThreadSanitizer can not mmap the shadow memory (something is mapped at 0x555555554000 < 0x7cf000000000).
Чтобы это обойти, достаточно запускать GDB так:
gdb -ex 'set disable-randomization off' <путь к приложению и т.п.>
Взято из FAQ по TSan
Сборка пакетов
Если нужно собрать пакет, для приложений и библиотек из которого при сборке включается AddressSanitizer или ThreadSanitizer, в .spec-файл надо добавить BuildRequires: asan-devel
или BuildRequires: tsan-devel
, соответственно.
Другие инструменты
В GCC 4.9 появились также инструменты для поиска утечек памяти (LeakSanitizer, LSan) и ситуаций с неопределённым поведением (Undefined Behaviour Sanitizer, UBSan).
Опции GCC, необходимые, чтобы включить всё это, а также краткое описание возможностей этих инструментов приведены тут: https://gcc.gnu.org/onlinedocs/gcc/Debugging-Options.html (см. опции вида -fsanitize=…).
Поиск утечек памяти выполняет и AddressSanitizer, так что LeakSanitizer отдельно нужен только тогда, когда использовать AddressSanitizer нежелательно по каким-то причинам.
TODO: более детально описать работу с UBSan.