Динамический анализ
программ на 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-bound
access)
● Использование освобождённой память
(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
Поток 1
void DeleteEntries() {
have_entries = false;
delete entries;
}
Поток 2
Entry *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 и false
positive
Есть ли в программе ошибка?
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-overflow
READ of size 4 at 0x000000415354 thread T0
#0 0x402481 in main a.cc:3
<...>
0x000000415354 is located 4 bytes to the right of global
variable '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-overflow
READ of size 4 at 0x7f5620d981b4 thread T0
#0 0x4024e8 in main a.cc:4
Address 0x7f5620d981b4 is located at offset 436 in frame
<main> of T0's 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-overflow
READ of size 4 at 0x7fe4b0c76214 thread T0
#0 0x40246f in main a.cc:3
0x7fe4b0c76214 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-free
READ of size 4 at 0x7faa07fce084 thread T0
#0 0x40433c in main a.cc:4
0x7faa07fce084 is located 4 bytes inside of 400-byte
region
freed by thread T0 here:
#0 0x4058fd in operator delete[](void*)
#1 0x404303 in main a.cc:3
previously 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 состояний:
0
7
6
5
4
3
2
1
-1
Тень адресного пространства (*)
Приложение
0xFFFFFFFF
0x20000000
Тень
0x1FFFFFFF
0x04000000
Mprotect
0x03FFFFF
0x0000000
Инструментация доступа к памяти
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 aligned
char a[328];
char redzone2[24];
char redzone3[32];
int *shadow = (&rz1 >> 3);
shadow[0] = 0xffffffff; // poison rz1
shadow[11] = 0xffffff00; // poison rz2
shadow[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, GCC
Google заплатил $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, Android
code.google.com/p/address-sanitizer
Гонки (data races)
Что такое гонка?
Гонка происходит, когда два потока одновременно
обращаются к одной и той же переменной, и хотя бы
одно из обращений является записью.
Если в программе на C++ могут возникнуть гонки, её
поведение не определено.
Компилятор имеет право считать, что в программе нет
гонок.
Что напечатает этот код?
Поток 1
cout << x << "n";
x = 1;
cout << y << "n";
Поток 2
cout << 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 go
wrong?"
Пример "безвредной гонки"
long long
get_value() {
return value;
}
void set_value(
long long x) {
value = x;
}
Является ли 64-битное чтение / запись атомарной
операцией?
Пример "безвредной гонки" (2)
...
int my_counter = counter; // read global
if (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 global
if (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 ID
User& getUser(int id) {
MutexLock(&mutex);
CHECK(0 <= id && id < users.size());
return users[id];
}
Метод-лжец
// Returns the user with a given ID
User& 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;
}
Гонка на vptr
class AbstractAction {
public:
AbstractAction(Executor *executor) {
executor->add(this);
// переключение контекста
}
...
virtual void Run() = 0;
}
Pure virtual call!
ThreadSanitizer спешит на помощь
WARNING: ThreadSanitizer: data race on vptr (ctor/dtor vs
virtual call)
Read of size 8 at 0x7fff103327f0 by thread T3:
#0 ExecutorThread::RunAction() executor.cc:1112
#1 ExecutorThread::Start() executor.cc:1283
Previous write of size 8 at 0x7fff103327f0 by main
thread:
#0 MyAction::MyAction()vptr_race.cc:48
#1 main vptr_race.cc:59
Location 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 = 1
Lock(&mutex); // time = 2
x = 1; // time = 3
Unlock(&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
Приложение Тень
T1
TS1
W
0:2
Чтение из потока T2
Приложение Тень
T1
TS1
W
0:2
T2
TS2
4:4
R
Чтение из потока T3
Приложение Тень
T1
TS1
W
0:2
T2
TS2
4:4
R
T3
TS3
0:4
R
Гонка?
Тень
T1
TS1
W
0:2
T2
TS2
4:4
R
T3
TS3
0:4
R
● Доступы
пересекаются?
● Разные потоки?
● Хотя бы одно из
обращений -
запись?
● Есть ли
синхронизация?
Гонка, если 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
● Go
go build -race
code.google.com/p/thread-sanitizer

20130429 dynamic c_c++_program_analysis-alexey_samsonov

  • 1.
    Динамический анализ программ наC++ Алексей Самсонов, Google Екатеринбург, УрФУ, 29.04.2013
  • 2.
    Команда (Google RU-MSK) КонстантинСеребряный (TL) Дмитрий Вьюков Тимур Исходжанов Сергей Матвеев Александр Потапенко Алексей Самсонов Евгений Степанов
  • 3.
    Содержание Часть 1 ● Ошибкиработы с памятью ● AddressSanitizer Часть 2 ● Гонки ● ThreadSanitizer
  • 4.
    В мире многокода на C и C++ ● Операционные системы ● Браузеры (Chromium, Firefox, Safari) ● Виртуальные машины (Java) ● Базы данных (MySQL) ● Backend веб-сервисов (поиск) ● Веб-серверы (Apache, nginx)
  • 5.
    Ошибки работы спамятью в C/C++ ● Кодеки и рендереры (ffmpeg, pdf) ● Использование освобождённой памяти (use-after-free) ● Использование неинициализированной памяти (uninitialized-memory-read) ● Утечки памяти (memory leaks) ● Неверный порядок инициализации/уничтожения глобальных объектов
  • 6.
    Ошибки работы спамятью в C/C++ ● Выход за границы буфера (out-of-bound access) ● Использование освобождённой память (use-after-free) ● Использование неинициализированной памяти (uninitialized-memory-read) ● Утечки памяти (memory leaks) ● Неверный порядок инициализации/уничтожения глобальных объектов
  • 7.
    Выход за границыбуфера (стек) void authorize() { ... char username[128]; gets(username); // 128 bytes should be // enough for everyone. ... }
  • 8.
    Использование освобожденной памяти ~UserTable() { ... deleteall_users; LOG() << "Deleted " << all_users->size() << " users!"; } Можно ли сделать этот код ещё хуже?
  • 9.
  • 10.
    Всегда в моде:racy use-after-free Поток 1 void DeleteEntries() { have_entries = false; delete entries; } Поток 2 Entry *GetEntry(int i) { if (have_entries) return entries[i]; return 0; }
  • 11.
    Инструменты для поискаошибок ● Valgrind/Memcheck ● DynamoRIO/Dr. Memory ● Purify ● Insure++ ● Mudflap ● Electric Fence / Page Heap / Guard Malloc Проблемы: ● Замедление программы (в 10 и более раз) ● Поиск ошибок только для динамически выделенной памяти (heap)
  • 12.
    Статический анализ программ ●Формальная верификация? ● Clang Static Analyzer? Проблемы: ● Баланс между эффективностью и полезностью ● Баланс между false negative и false positive
  • 13.
    Есть ли впрограмме ошибка? int array[10] = {...}; int main(int argc, char *argv[]) { return array[2 + argc]; }
  • 15.
    Пример отчёта обошибке (1) int global_array[100] = {-1}; int main(int argc, char **argv) { return global_array[argc + 100]; } ==10538== ERROR: AddressSanitizer global-buffer-overflow READ of size 4 at 0x000000415354 thread T0 #0 0x402481 in main a.cc:3 <...> 0x000000415354 is located 4 bytes to the right of global variable 'global_array' (0x4151c0) of size 400
  • 16.
    Пример отчёта обошибке (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-overflow READ of size 4 at 0x7f5620d981b4 thread T0 #0 0x4024e8 in main a.cc:4 Address 0x7f5620d981b4 is located at offset 436 in frame <main> of T0's stack: This frame has 1 object(s): [32, 432) 'stack_array'
  • 17.
    Пример отчёта обошибке (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-overflow READ of size 4 at 0x7fe4b0c76214 thread T0 #0 0x40246f in main a.cc:3 0x7fe4b0c76214 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
  • 18.
    Пример отчёта обошибке (4) int main(int argc, char **argv) { int *array = new int[100]; delete [] array; return array[argc]; } ==30226== ERROR: AddressSanitizer heap-use-after-free READ of size 4 at 0x7faa07fce084 thread T0 #0 0x40433c in main a.cc:4 0x7faa07fce084 is located 4 bytes inside of 400-byte region freed by thread T0 here: #0 0x4058fd in operator delete[](void*) #1 0x404303 in main a.cc:3 previously allocated by thread T0 here: #0 0x405579 in operator new[](unsigned long) #1 0x4042f3 in main a.cc:2
  • 20.
    Что такое AddressSanitizer? ●Компиляторная инструментация ○ Проверка всех обращений к памяти ○ Добавление редзон("redzones") вокруг глобальных и локальных переменных ● Runtime-библиотека ○ Аллокатор (выделение/освобождение памяти) ○ Отслеживание всех потоков ○ Печать отчётов об ошибках ○ Обёртки функций из стандартной библиотеки
  • 21.
    Теневой байт (shadowbyte) Если адреса всех переменных кратны 8, то у любых выровненных восьми байт есть 9 состояний: 0 7 6 5 4 3 2 1 -1
  • 22.
    Тень адресного пространства(*) Приложение 0xFFFFFFFF 0x20000000 Тень 0x1FFFFFFF 0x04000000 Mprotect 0x03FFFFF 0x0000000
  • 23.
    Инструментация доступа кпамяти int *a = ...; *a = ...; char *shadow = a >> 3; if (*shadow != 0 && *shadow < (a&7) + 4) ReportError(a); *a = ...;
  • 24.
    Инструментация стека void foo(){ char a[328]; <------------- CODE -------------> }
  • 25.
    Инструментация стека void foo(){ char redzone1[32]; // 32-byte aligned char a[328]; char redzone2[24]; char redzone3[32]; int *shadow = (&rz1 >> 3); shadow[0] = 0xffffffff; // poison rz1 shadow[11] = 0xffffff00; // poison rz2 shadow[12] = 0xffffffff; // poison rz3 <------------- CODE -------------> shadow[0] = shadow[11] = shadow[12] = 0; }
  • 26.
    Инструментация глобальных объектов int global; struct{ int original; char redzone[60]; } global; // 32-byte aligned
  • 27.
  • 28.
    Интеграция в LLVM ●Clang (или другой фронтенд) генерирует промежуточное представление программы; ● ASan добавляет инструментацию; ● Бэкенд оптимизирует код и генерирует объектный файл; ● Исполняемый файл линкуется с runtime- библиотекой.
  • 29.
    Runtime-библиотека ● Аллокатор ○ Добавлениередзон вокруг блоков динамически выделенной памяти ○ Карантин для освобождённой памяти ○ Хранение стека вызовов для каждой аллокации ● Печать отчётов об ошибках ● Перехват библиотечных функций char bad_string[2] = {'h', 'i'}; int len = strlen(bad_string);
  • 30.
    Производительность ● Замедление программы:2x ● Использование памяти: 1,5-3x ● "Just works" для большинства тестов и реальных программ ● Незначительное замедление для GUI- программ (Chromium+ASan работает без проблем)
  • 31.
    Трофеи 1000+ найденных ошибокповсюду: ● Chromium, Firefox, Safari ● Сервисы Google ● Perl, PHP, MySQL, ffmpeg, webrtc, vim ● LLVM, GCC Google заплатил $130000+ внешним исследователям за security-ошибки, найденные с помощью AddressSanitizer.
  • 32.
    Что будет дальше? ●Поиск других видов ошибок (в ближайшее время: поиск утечек памяти) ● Поиск ошибок в системных и прекомпилированных библиотеках ● AddressSanitizer для ядра Linux ● AddressSanitizer для Windows (нужен компилятор).
  • 33.
    Попробуйте AddressSanitizer! ● LLVM/Clang3.1+ clang++ -fsanitize=address a.cc ● GCC 4.8+ g++ -fsanitize=address a.cc ● Платформы: Linux, Mac OS X, Android code.google.com/p/address-sanitizer
  • 34.
  • 35.
    Что такое гонка? Гонкапроисходит, когда два потока одновременно обращаются к одной и той же переменной, и хотя бы одно из обращений является записью. Если в программе на C++ могут возникнуть гонки, её поведение не определено. Компилятор имеет право считать, что в программе нет гонок.
  • 36.
    Что напечатает этоткод? Поток 1 cout << x << "n"; x = 1; cout << y << "n"; Поток 2 cout << y << "n"; y = 1; cout << x << "n"; int x = 0, y = 0;
  • 37.
    "Безвредные" гонки ● Синхронизациязамедляет программу. ● Может возникнуть желание допустить гонки на "не очень важных" данных. ● Не нужно этого делать. В C++ не может быть "безвредных" (benign) гонок. H-J. Boehm "How to miscompile programs with "benign" data races" D. Vyukov "Benign data races: what could possibly go wrong?"
  • 39.
    Пример "безвредной гонки" longlong get_value() { return value; } void set_value( long long x) { value = x; } Является ли 64-битное чтение / запись атомарной операцией?
  • 40.
    Пример "безвредной гонки"(2) ... int my_counter = counter; // read global if (my_counter > my_old_counter) { my_action = print_stats; } ... if (my_counter > my_old_counter) { run_action(my_action); }
  • 41.
    Пример "безвредной гонки"(3) my_action = launch_nuclear_rocket; ... int my_counter = counter; // read global if (my_counter > my_old_counter) { my_action = print_stats; } ... my_counter = counter; if (my_counter > my_old_counter) { run_action(my_action); }
  • 42.
    А как женастоящие гонки?
  • 43.
    Метод-лжец // Returns theuser with a given ID User& getUser(int id) { MutexLock(&mutex); CHECK(0 <= id && id < users.size()); return users[id]; }
  • 44.
    Метод-лжец // Returns theuser with a given ID User& getUser(int id) { MutexLock(&mutex); CHECK(0 <= id && id < users.size()); return users[id]; }
  • 45.
    Что не такс этим кодом? class AbstractAction { public: AbstractAction(Executor *executor) { executor->add(this); ... } ... }
  • 46.
    Что не такс этим кодом? class AbstractAction { public: AbstractAction(Executor *executor) { executor->add(this); ... } ... virtual void Run() = 0; }
  • 47.
    Гонка на vptr classAbstractAction { public: AbstractAction(Executor *executor) { executor->add(this); // переключение контекста } ... virtual void Run() = 0; } Pure virtual call!
  • 48.
    ThreadSanitizer спешит напомощь WARNING: ThreadSanitizer: data race on vptr (ctor/dtor vs virtual call) Read of size 8 at 0x7fff103327f0 by thread T3: #0 ExecutorThread::RunAction() executor.cc:1112 #1 ExecutorThread::Start() executor.cc:1283 Previous write of size 8 at 0x7fff103327f0 by main thread: #0 MyAction::MyAction()vptr_race.cc:48 #1 main vptr_race.cc:59 Location is stack of main thread.
  • 49.
    Что такое ThreadSanitizer? ●Компиляторная инструментация ○ Проверка всех обращений к памяти ○ Отслеживание событий: вход/выход в функцию, обновление vptr, атомарные операции ● Runtime-библиотека ○ Аллокатор (выделение/освобождение памяти) ○ Отслеживание всех потоков ○ Печать отчётов об ошибках ○ Обёртки функций из стандартной библиотеки ○ Поддержка синхронизационных примитивов
  • 50.
    Инструментация (1) int *p= ...; *p = 42; int *p = ...; __tsan_write4(p); *p = 42;
  • 51.
    Инструментация (2) void foo(){ ... } void foo() { __tsan_func_entry( __builtin_return_address(0)); ... __tsan_func_exit(); }
  • 52.
    Инструментация (3) A::~A() { //this->vptr = A::vptr; } A::~A() { __tsan_vptr_update(&this->vptr, A::vptr); // this->vptr = A::vptr; }
  • 53.
    Runtime-библиотека ● Проверка всехдоступов в память ● Выделение/освобождение памяти ● Поддержка синхронизации (мьютексы, атомарные операции) ● Перехват функций работы с потоками (libpthread) ● Перехват функций из стандартной библиотеки ● Печать отчётов об ошибках
  • 54.
    Абстрактное время потока Увеличиваетсяпосле каждого события: void foo() { // time = 1 Lock(&mutex); // time = 2 x = 1; // time = 3 Unlock(&mutex); // time = 4 } // time = 5
  • 55.
    Векторные часы (vectorclock) VC[1..N], где VC[i] - время i-го потока. Векторные часы есть у каждого потока: VC[1..N], где VC[i] - время i-го потока в момент последней синхронизации с ним. Векторные часы есть у каждого мьютекса: VC[1..N], VC[i] - время i-го потока, когда он в последний раз отпустил мьютекс.
  • 56.
    Обновление векторных часов Доступк памяти в потоке T: T->VC[T->id]++;
  • 57.
    Обновление векторных часов(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]);
  • 58.
    Как кодировать обращениек памяти? Обращение к выровненным 8 байтам адресного пространства кодируется через: ● ID потока (TID) ● Время этого потока (TS) ● позиция (0..7) и размер (1,2,4,8) обращения ● является ли доступ чтением или записью? Вся эта информация помещается в 8 байт.
  • 59.
    Пример обращений кпамяти Приложение Тень
  • 60.
    Запись из потокаT1 Приложение Тень T1 TS1 W 0:2
  • 61.
    Чтение из потокаT2 Приложение Тень T1 TS1 W 0:2 T2 TS2 4:4 R
  • 62.
    Чтение из потокаT3 Приложение Тень T1 TS1 W 0:2 T2 TS2 4:4 R T3 TS3 0:4 R
  • 63.
    Гонка? Тень T1 TS1 W 0:2 T2 TS2 4:4 R T3 TS3 0:4 R ● Доступы пересекаются? ● Разныепотоки? ● Хотя бы одно из обращений - запись? ● Есть ли синхронизация? Гонка, если T3->VC[T1] < TS1
  • 64.
    Как узнать стеквызовов для каждого обращения к памяти? ● Циклическая очередь всех событий в каждом потоке: ○ доступ к памяти ○ вход/выход из функции ○ взятие/освобождение мьютекса ● "Повторение" событий перед печатью отчёта: ○ Через некоторое время события "забываются" ○ Неограниченный размер стека вызовов в отчёте ○ Отчёт содержит множество мьютексов, взятых в каждом потоке.
  • 65.
    Результаты ● Замедление программы:4-10x (в 10 раз быстрее аналогов). ● Использование памяти: 5-8x ● 600+ найденных ошибок ● Работает для больших серверных приложений
  • 66.
    Что будет дальше? ●Портирование на другие системы (сейчас - только 64-битный Linux). ● Поиск ошибок в системных и прекомпилированных библиотеках ● Больше оптимизаций, больше пользователей ● Поиск гонок в коде на Java
  • 67.
    Попробуйте ThreadSanitizer! ● LLVM/Clang3.2+ clang++ -fsanitize=thread a.cc ● GCC 4.8+ g++ -fsanitize=thread a.cc ● Go go build -race code.google.com/p/thread-sanitizer