Применение AddressSanitizer для поиска ошибок памяти программы

контрольная работа

AddressSanitizer.png

Как инженер C/C++, вы столкнетесь с различными проблемами в процессе разработки, наиболее распространенными из которых являютсяпроблемы с использованием памяти,Например,за границами,утечка. Обычно используемым инструментом в прошлом является Valgrind, но самая большая проблема с использованием Valgrind заключается в том, что он сильно замедлит скорость работы программы, а по предварительной оценке снизит скорость работы в 10 раз. Разработано GoogleAddressSanitizerЭтот инструмент очень хорошо решает проблемы с производительностью, вызванные Valgrind, он очень быстр и замедляет работу программы всего в 2 раза.

Обзор AddressSanitizer

AddressSanitizer — это инструмент тестирования на основе компилятора, который обнаруживает различные ошибки памяти в коде C/C++ во время выполнения. Строго говоря, AddressSanitizer — это подключаемый модуль компилятора, который разделен на два модуля: один — инструментальный модуль компилятора, а другой — динамическая библиотека, используемая для замены malloc/free.

Инструментарий в основном направлен на обработку операций доступа к памяти (сохранение, загрузка, выделение и т.д.) на уровне компилятора llvm. Динамические библиотеки в основном предоставляют некоторые сложные функции во время выполнения (такие как отравление/неотравление теневой памяти) и перехватывают функции системного вызова, такие как malloc/free.

Основное использование AddressSanitizer

Согласно AddressSanitizer Wiki, могут быть обнаружены следующие ошибки памяти.

  • Использовать после освобождения: доступ к памяти, которая была освобождена в куче
  • Переполнение буфера кучи: переполнение доступа к буферу кучи
  • Переполнение буфера стека: переполнение доступа к буферу в стеке
  • Переполнение глобального буфера: переполнение доступа к глобальному буферу
  • Использовать после возврата: получить доступ к освобожденной памяти в стеке
  • Использовать после области: объект стека используется за пределами определенной области.
  • Ошибки порядка инициализации: Ошибки порядка инициализации
  • Утечки памяти: утечки памяти

Здесь я лишь кратко расскажу об основном использовании.Для получения подробной документации по использованию обратитесь к официальной документации по использованию компилятора, такой какClangдокументация:Surf.lilumu.org/docs/add Res…

Используйте после бесплатного практического примера

Следующий код является очень простым примером Use after free:

//use_after_free.cpp
#include <iostream>
int main(int argc, char **argv) {
  int *array = new int[100];
  delete [] array;
  std::cout << array[0] << std::endl;
  return 1;
}

Скомпилируйте код и запустите его, здесь видно, что его нужно только привести при компиляции-fsanitize=addressвариант ладно.

clang++  -O -g -fsanitize=address ./use_after_free.cpp
./a.out

В итоге мы увидим следующий вывод:

==10960==ERROR: AddressSanitizer: heap-use-after-free on address 0x614000000040 at pc 0x00010d471df0 bp 0x7ffee278e6b0 sp 0x7ffee278e6a8
READ of size 4 at 0x614000000040 thread T0
    #0 0x10d471def in main use_after_free.cpp:6
    #1 0x7fff732c17fc in start (libdyld.dylib:x86_64+0x1a7fc)

0x614000000040 is located 0 bytes inside of 400-byte region [0x614000000040,0x6140000001d0)
freed by thread T0 here:
    #0 0x10d4ccced in wrap__ZdaPv (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x51ced)
    #1 0x10d471ca1 in main use_after_free.cpp:5
    #2 0x7fff732c17fc in start (libdyld.dylib:x86_64+0x1a7fc)

previously allocated by thread T0 here:
    #0 0x10d4cc8dd in wrap__Znam (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x518dd)
    #1 0x10d471c96 in main use_after_free.cpp:4
    #2 0x7fff732c17fc in start (libdyld.dylib:x86_64+0x1a7fc)

SUMMARY: AddressSanitizer: heap-use-after-free use_after_free.cpp:6 in main

Это видно с первого взгляда, который четко говорит нам, какая строка памяти освобождается, а какая снова используется.

Другим является утечка памяти, такая как следующий код, очевидно, что память, на которую указывает p, не была освобождена.

void *p;

int main() {
        p = malloc(7);
        p = 0; // The memory is leaked here.
        return 0;
}

скомпилировать и запустить

clang -fsanitize=address -g  ./leak.c
./a.out

Вы можете увидеть следующие результаты

=================================================================
==17756==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 7 byte(s) in 1 object(s) allocated from:
    #0 0x4ffc80 in malloc (/home/simon.liu/workspace/a.out+0x4ffc80)
    #1 0x534ab8 in main /home/simon.liu/workspace/./leak.c:4:8
    #2 0x7f127c42af42 in __libc_start_main (/usr/lib64/libc.so.6+0x23f42)

SUMMARY: AddressSanitizer: 7 byte(s) leaked in 1 allocation(s).

Но обратите внимание здесьОбнаружение утечек памяти будет выполнено только до того, как программа окончательно завершит работу., то есть, если вы постоянно выделяете память во время выполнения, а затем освобождаете память при выходе, AddressSanitizer не обнаружит утечки памяти.В это время вам может понадобиться другой инструмент JeMalloc/TCMalloc.

Основы AddressSanitizer

Вот краткое введение в реализацию AddressSanitizer.Более подробную реализацию алгоритма см. в разделе "AddressSanitizer: a fast address sanity checker":Уууу. Usenix.org/system/file…

AddressSanitizer заменит все ваши malloc и освобождает, тогда области памяти, которые были выделены (malloced), будут помечены какpoisoned(в основном для борьбы с переполнением), а освобожденная память будет помечена как отравленная (в основном для борьбы с использованием после освобождения). Каждый доступ к памяти в вашем коде транслируется компилятором следующим образом.

before:

*address = ...;  // or: ... = *address;

after:

shadow_address = MemToShadow(address);
if (ShadowIsPoisoned(shadow_address)) {
  ReportError(address, kAccessSize, kIsWrite);
}
*address = ...;  // or: ... = *address;

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

Причина такого перевода в том, что AddressSanitizer делит виртуальную память на две части:

  1. Основная память приложения (Mem) — это память, используемая самой текущей программой.
  2. Теневая память — это просто часть памяти, в которой хранится информация об основной памяти.Например, те области основной памяти, которые позиционируются, хранятся в теневой памяти.

AddressSanitizer по сравнению с другими инструментами обнаружения памяти

Следующий рисунокВот как AddressSanitizer сравнивается с некоторыми другими инструментами обнаружения памяти:

AddressSanitizer Valgrind/Memcheck Dr. Memory Mudflap Guard Page gperftools
technology CTI DBI DBI CTI Library Library
ARCH x86, ARM, PPC x86, ARM, PPC, MIPS, S390X, TILEGX x86 all(?) all(?) all(?)
OS Linux, OS X, Windows, FreeBSD, Android, iOS Simulator Linux, OS X, Solaris, Android Windows, Linux Linux, Mac(?) All (1) Linux, Windows
Slowdown 2x 20x 10x 2x-40x ? ?
Detects:
Heap OOB yes yes yes yes some some
Stack OOB yes no no some no no
Global OOB yes no no ? no no
UAF yes yes yes yes yes yes
UAR yes (see AddressSanitizerUseAfterReturn) no no no no no
UMR no (see MemorySanitizer) yes yes ? no no
Leaks yes (see LeakSanitizer) yes yes ? no yes

Описание параметра:

  • DBI: динамическая двоичная аппаратура
  • CTI: инструменты времени компиляции
  • UMR: чтение неинициализированной памяти (чтение неинициализированной памяти)
  • UAF: use-after-free (висячий указатель)
  • UAR: use-after-return (использовать значение после возврата)
  • OOB: за пределами (переполнение)
  • x86: includes 32- and 64-bit.

Видно, что AddressSanitizer замедляет работу программы всего в 2 раза по сравнению с Valgrind. В настоящее время AddressSanitizer поддерживает GCC и Clang, где GCC поддерживается с версии 4.8, а Clang — с версии 3.1.

Примечания по использованию AddressSanitizer

  1. Приложение не аварийно завершает работу автоматически, когда AddressSanitizer обнаруживает нарушение доступа к памяти. Это связано с тем, что когда используются инструменты фаззинга, они обычно обнаруживают такие ошибки, проверяя коды возврата. Конечно, мы также можем вызвать сбой программы перед фаззингом, изменив переменную среды ASAN_OPTIONS на следующую форму:
export ASAN_OPTIONS='abort_on_error=1'/
  1. AddressSanitizer требует довольно много виртуальной памяти (около 20 ТБ), не волнуйтесь, это всего лишь виртуальная память, вы все равно можете использовать свое приложение. Но инструменты фаззинга, такие как американский fuzzy lop, ограничат использование памяти программным обеспечением для фаззинга, но вы все равно можете решить эту проблему, отключив ограничение памяти. Единственное предостережение заключается в том, что это сопряжено с некоторыми рисками: тестовый образец может привести к тому, что приложение будет выделять большой объем памяти, что приведет к нестабильной работе системы или сбою других приложений. Так что не пытайтесь отключить ограничения памяти в той же системе, когда делаете какой-то важный фаззинг.

Включить AddressSanitizer в Nebula Graph

Мы также использовали AddressSanitizer в Nebula Graph, и это помогло нам найти много проблем. Открыть AddressSanitizer в Nebula Graph очень просто, просто укажите параметр ENABLE_ASAN в Cmake, например:

Cmake -DENABLE_ASAN=On

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

приложение