20130429 dynamic c_c++_program_analysis-alexey_samsonov
Upcoming SlideShare
Loading in...5
×
 

20130429 dynamic c_c++_program_analysis-alexey_samsonov

on

  • 1,093 views

 

Statistics

Views

Total Views
1,093
Views on SlideShare
432
Embed Views
661

Actions

Likes
0
Downloads
0
Comments
0

3 Embeds 661

http://compsciclub.ru 655
http://logic.pdmi.ras.ru 4
http://www.compsciclub.ru 2

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

20130429 dynamic c_c++_program_analysis-alexey_samsonov 20130429 dynamic c_c++_program_analysis-alexey_samsonov Presentation Transcript

  • Динамический анализпрограмм на C++Алексей Самсонов, GoogleЕкатеринбург, УрФУ, 29.04.2013
  • Команда (Google RU-MSK)Константин Серебряный (TL)Дмитрий ВьюковТимур ИсходжановСергей МатвеевАлександр ПотапенкоАлексей СамсоновЕвгений Степанов
  • СодержаниеЧасть 1● Ошибки работы с памятью● AddressSanitizerЧасть 2● Гонки● ThreadSanitizer
  • В мире много кода на C и C++● Операционные системы● Браузеры (Chromium, Firefox, Safari)● Виртуальные машины (Java)● Базы данных (MySQL)● Backend веб-сервисов (поиск)● Веб-серверы (Apache, nginx)
  • Ошибки работы с памятью в C/C++● Кодеки и рендереры (ffmpeg, pdf)● Использование освобождённой памяти(use-after-free)● Использование неинициализированнойпамяти (uninitialized-memory-read)● Утечки памяти (memory leaks)● Неверный порядокинициализации/уничтожения глобальныхобъектов
  • Ошибки работы с памятью в C/C++● Выход за границы буфера (out-of-boundaccess)● Использование освобождённой память(use-after-free)● Использование неинициализированнойпамяти (uninitialized-memory-read)● Утечки памяти (memory leaks)● Неверный порядокинициализации/уничтожения глобальныхобъектов
  • Выход за границы буфера (стек)void authorize() {...char username[128];gets(username); // 128 bytes should be// enough for everyone....}
  • Использование освобожденнойпамяти~UserTable() {...delete all_users;LOG() << "Deleted " << all_users->size()<< " users!";}Можно ли сделать этот код ещё хуже?
  • Использование освобожденнойпамяти~UserTable() {...delete all_users;RunAction(all_users->action_on_delete);}
  • Всегда в моде: racy use-after-freeПоток 1void DeleteEntries() {have_entries = false;delete entries;}Поток 2Entry *GetEntry(int i) {if (have_entries)return entries[i];return 0;}
  • Инструменты для поиска ошибок● Valgrind/Memcheck● DynamoRIO/Dr. Memory● Purify● Insure++● Mudflap● Electric Fence / Page Heap / Guard MallocПроблемы:● Замедление программы (в 10 и более раз)● Поиск ошибок только для динамическивыделенной памяти (heap)
  • Статический анализ программ● Формальная верификация?● Clang Static Analyzer?Проблемы:● Баланс между эффективностью иполезностью● Баланс между false negative и falsepositive
  • Есть ли в программе ошибка?int array[10] = {...};int main(int argc, char *argv[]) {return array[2 + argc];}
  • Пример отчёта об ошибке (1)int global_array[100] = {-1};int main(int argc, char **argv) {return global_array[argc + 100];}==10538== ERROR: AddressSanitizer global-buffer-overflowREAD of size 4 at 0x000000415354 thread T0#0 0x402481 in main a.cc:3<...>0x000000415354 is located 4 bytes to the right of globalvariable global_array (0x4151c0) of size 400
  • Пример отчёта об ошибке (2)int main(int argc, char **argv) {int stack_array[100];stack_array[1] = 0;return stack_array[argc + 100];}==10589== ERROR: AddressSanitizer stack-buffer-overflowREAD of size 4 at 0x7f5620d981b4 thread T0#0 0x4024e8 in main a.cc:4Address 0x7f5620d981b4 is located at offset 436 in frame<main> of T0s stack:This frame has 1 object(s):[32, 432) stack_array
  • Пример отчёта об ошибке (3)int main(int argc, char **argv) {int *array = new int[100];int res = array[argc + 100];delete [] array;return res;}==10565== ERROR: AddressSanitizer heap-buffer-overflowREAD of size 4 at 0x7fe4b0c76214 thread T0#0 0x40246f in main a.cc:30x7fe4b0c76214 is located 4 bytes to the right of 400-byte region [0x7fe..., 0x7fe...)allocated by thread T0 here:#0 0x402c36 in operator new[](unsigned long)#1 0x402422 in main a.cc:2
  • Пример отчёта об ошибке (4)int main(int argc, char **argv) {int *array = new int[100];delete [] array;return array[argc];}==30226== ERROR: AddressSanitizer heap-use-after-freeREAD of size 4 at 0x7faa07fce084 thread T0#0 0x40433c in main a.cc:40x7faa07fce084 is located 4 bytes inside of 400-byteregionfreed by thread T0 here:#0 0x4058fd in operator delete[](void*)#1 0x404303 in main a.cc:3previously allocated by thread T0 here:#0 0x405579 in operator new[](unsigned long)#1 0x4042f3 in main a.cc:2
  • Что такое AddressSanitizer?● Компиляторная инструментация○ Проверка всех обращений к памяти○ Добавление редзон("redzones") вокругглобальных и локальных переменных● Runtime-библиотека○ Аллокатор (выделение/освобождение памяти)○ Отслеживание всех потоков○ Печать отчётов об ошибках○ Обёртки функций из стандартной библиотеки
  • Теневой байт (shadow byte)Если адреса всех переменных кратны 8, то у любыхвыровненных восьми байт есть 9 состояний:07654321-1
  • Тень адресного пространства (*)Приложение0xFFFFFFFF0x20000000Тень0x1FFFFFFF0x04000000Mprotect0x03FFFFF0x0000000
  • Инструментация доступа к памятиint *a = ...;*a = ...;char *shadow = a >> 3;if (*shadow != 0 &&*shadow < (a&7) + 4)ReportError(a);*a = ...;
  • Инструментация стекаvoid foo() {char a[328];<------------- CODE ------------->}
  • Инструментация стекаvoid foo() {char redzone1[32]; // 32-byte alignedchar a[328];char redzone2[24];char redzone3[32];int *shadow = (&rz1 >> 3);shadow[0] = 0xffffffff; // poison rz1shadow[11] = 0xffffff00; // poison rz2shadow[12] = 0xffffffff; // poison rz3<------------- CODE ------------->shadow[0] = shadow[11] = shadow[12] = 0;}
  • Инструментация глобальныхобъектовint global;struct {int original;char redzone[60];} global; // 32-byte aligned
  • LLVM спешит на помощь!
  • Интеграция в LLVM● Clang (или другой фронтенд) генерируетпромежуточное представлениепрограммы;● ASan добавляет инструментацию;● Бэкенд оптимизирует код и генерируетобъектный файл;● Исполняемый файл линкуется с runtime-библиотекой.
  • Runtime-библиотека● Аллокатор○ Добавление редзон вокруг блоков динамическивыделенной памяти○ Карантин для освобождённой памяти○ Хранение стека вызовов для каждой аллокации● Печать отчётов об ошибках● Перехват библиотечных функцийchar bad_string[2] = {h, i};int len = strlen(bad_string);
  • Производительность● Замедление программы: 2x● Использование памяти: 1,5-3x● "Just works" для большинства тестов иреальных программ● Незначительное замедление для GUI-программ (Chromium+ASan работает безпроблем)
  • Трофеи1000+ найденных ошибок повсюду:● Chromium, Firefox, Safari● Сервисы Google● Perl, PHP, MySQL, ffmpeg, webrtc, vim● LLVM, GCCGoogle заплатил $130000+ внешнимисследователям за security-ошибки,найденные с помощью AddressSanitizer.
  • Что будет дальше?● Поиск других видов ошибок (в ближайшеевремя: поиск утечек памяти)● Поиск ошибок в системных ипрекомпилированных библиотеках● AddressSanitizer для ядра Linux● AddressSanitizer для Windows (нуженкомпилятор).
  • Попробуйте AddressSanitizer!● LLVM/Clang 3.1+clang++ -fsanitize=address a.cc● GCC 4.8+g++ -fsanitize=address a.cc● Платформы:Linux, Mac OS X, Androidcode.google.com/p/address-sanitizer
  • Гонки (data races)
  • Что такое гонка?Гонка происходит, когда два потока одновременнообращаются к одной и той же переменной, и хотя быодно из обращений является записью.Если в программе на C++ могут возникнуть гонки, еёповедение не определено.Компилятор имеет право считать, что в программе нетгонок.
  • Что напечатает этот код?Поток 1cout << x << "n";x = 1;cout << y << "n";Поток 2cout << y << "n";y = 1;cout << x << "n";int x = 0, y = 0;
  • "Безвредные" гонки● Синхронизация замедляет программу.● Может возникнуть желание допуститьгонки на "не очень важных" данных.● Не нужно этого делать. В C++ не можетбыть "безвредных" (benign) гонок.H-J. Boehm "How to miscompile programs with "benign"data races"D. Vyukov "Benign data races: what could possibly gowrong?"
  • Пример "безвредной гонки"long longget_value() {return value;}void set_value(long long x) {value = x;}Является ли 64-битное чтение / запись атомарнойоперацией?
  • Пример "безвредной гонки" (2)...int my_counter = counter; // read globalif (my_counter > my_old_counter) {my_action = print_stats;}...if (my_counter > my_old_counter) {run_action(my_action);}
  • Пример "безвредной гонки" (3)my_action = launch_nuclear_rocket;...int my_counter = counter; // read globalif (my_counter > my_old_counter) {my_action = print_stats;}...my_counter = counter;if (my_counter > my_old_counter) {run_action(my_action);}
  • А как же настоящие гонки?
  • Метод-лжец// Returns the user with a given IDUser& getUser(int id) {MutexLock(&mutex);CHECK(0 <= id && id < users.size());return users[id];}
  • Метод-лжец// Returns the user with a given IDUser& getUser(int id) {MutexLock(&mutex);CHECK(0 <= id && id < users.size());return users[id];}
  • Что не так с этим кодом?class AbstractAction {public:AbstractAction(Executor *executor) {executor->add(this);...}...}
  • Что не так с этим кодом?class AbstractAction {public:AbstractAction(Executor *executor) {executor->add(this);...}...virtual void Run() = 0;}
  • Гонка на vptrclass AbstractAction {public:AbstractAction(Executor *executor) {executor->add(this);// переключение контекста}...virtual void Run() = 0;}Pure virtual call!
  • ThreadSanitizer спешит на помощьWARNING: ThreadSanitizer: data race on vptr (ctor/dtor vsvirtual call)Read of size 8 at 0x7fff103327f0 by thread T3:#0 ExecutorThread::RunAction() executor.cc:1112#1 ExecutorThread::Start() executor.cc:1283Previous write of size 8 at 0x7fff103327f0 by mainthread:#0 MyAction::MyAction()vptr_race.cc:48#1 main vptr_race.cc:59Location is stack of main thread.
  • Что такое ThreadSanitizer?● Компиляторная инструментация○ Проверка всех обращений к памяти○ Отслеживание событий: вход/выход в функцию,обновление vptr, атомарные операции● Runtime-библиотека○ Аллокатор (выделение/освобождение памяти)○ Отслеживание всех потоков○ Печать отчётов об ошибках○ Обёртки функций из стандартной библиотеки○ Поддержка синхронизационных примитивов
  • Инструментация (1)int *p = ...;*p = 42;int *p = ...;__tsan_write4(p);*p = 42;
  • Инструментация (2)void foo() {...}void foo() {__tsan_func_entry(__builtin_return_address(0));...__tsan_func_exit();}
  • Инструментация (3)A::~A() {// this->vptr = A::vptr;}A::~A() {__tsan_vptr_update(&this->vptr, A::vptr);// this->vptr = A::vptr;}
  • Runtime-библиотека● Проверка всех доступов в память● Выделение/освобождение памяти● Поддержка синхронизации (мьютексы,атомарные операции)● Перехват функций работы с потоками(libpthread)● Перехват функций из стандартнойбиблиотеки● Печать отчётов об ошибках
  • Абстрактное время потокаУвеличивается после каждого события:void foo() { // time = 1Lock(&mutex); // time = 2x = 1; // time = 3Unlock(&mutex); // time = 4} // time = 5
  • Векторные часы (vector clock)VC[1..N], где VC[i] - время i-го потока.Векторные часы есть у каждого потока:VC[1..N], где VC[i] - время i-го потока вмомент последней синхронизации с ним.Векторные часы есть у каждого мьютекса:VC[1..N], VC[i] - время i-го потока, когдаон в последний раз отпустил мьютекс.
  • Обновление векторных часовДоступ к памяти в потоке T:T->VC[T->id]++;
  • Обновление векторных часов (2)Взятие мьютекса M в потоке T:для всех i:T->VC[i] = max(T->VC[i], M->VC[i]);Освобождение мьютекса M в потоке T:для всех i:M->VC[i] = max(M->VC[i], T->VC[i]);
  • Как кодировать обращение кпамяти?Обращение к выровненным 8 байтамадресного пространства кодируется через:● ID потока (TID)● Время этого потока (TS)● позиция (0..7) и размер (1,2,4,8)обращения● является ли доступ чтением илизаписью?Вся эта информация помещается в 8 байт.
  • Пример обращений к памятиПриложение Тень
  • Запись из потока T1Приложение ТеньT1TS1W0:2
  • Чтение из потока T2Приложение ТеньT1TS1W0:2T2TS24:4R
  • Чтение из потока T3Приложение ТеньT1TS1W0:2T2TS24:4RT3TS30:4R
  • Гонка?ТеньT1TS1W0:2T2TS24:4RT3TS30:4R● Доступыпересекаются?● Разные потоки?● Хотя бы одно изобращений -запись?● Есть лисинхронизация?Гонка, если T3->VC[T1] < TS1
  • Как узнать стек вызовов длякаждого обращения к памяти?● Циклическая очередь всех событий вкаждом потоке:○ доступ к памяти○ вход/выход из функции○ взятие/освобождение мьютекса● "Повторение" событий перед печатьюотчёта:○ Через некоторое время события "забываются"○ Неограниченный размер стека вызовов в отчёте○ Отчёт содержит множество мьютексов, взятых вкаждом потоке.
  • Результаты● Замедление программы: 4-10x (в 10 разбыстрее аналогов).● Использование памяти: 5-8x● 600+ найденных ошибок● Работает для больших серверныхприложений
  • Что будет дальше?● Портирование на другие системы (сейчас- только 64-битный Linux).● Поиск ошибок в системных ипрекомпилированных библиотеках● Больше оптимизаций, большепользователей● Поиск гонок в коде на Java
  • Попробуйте ThreadSanitizer!● LLVM/Clang 3.2+clang++ -fsanitize=thread a.cc● GCC 4.8+g++ -fsanitize=thread a.cc● Gogo build -racecode.google.com/p/thread-sanitizer