Запуск Java вместе с AddressSanitizer
OpenJDK и AddressSanitizer довольно известные open-source проекты. OpenJDK содержит код на C/C++, который в отличие от кода на Java, может напрямую работать с памятью, что в свою очередь может привести к известным проблемам связанными с доступом за пределы выделенной памяти. Это всем известные и всевозможные переполнения буферов, use-after-ftee и прочие проблемы. Подобные проблемы могут быть обнаружены с помощью инструментов, которые называются runtime memory checkers. Одним из таких инструментов является AddressSanitizer. И теперь стало гораздо легче использовать AddressSanitizer вместе с OpenJDK.
English version is also available.
Что такое AddressSanitizer?
Как уже было отмечено, AddressSanitizer (или сокращенно ASan) инструмент, которые позволяет выявлять ошибки связанные с работой с памятью в C/C++ коде. Примерами таких ошибок могут быть переполнения буферов в разных областях памяти, чтение данных за пределами выделенной памяти, use-after-free, а также утечки памяти. Если кратко, то AddressSanitizer инструментирует код на C/C++ в процессе компиляции и вставляет дополнительные проверки, которые позволят обнаружить ошибки доступа к памяти во время выполнения инструментированного кода. Этот инструмент доступен в GCC 4.8+ и Clang 3.1+ работает с несколькими платформами включая Linux x64 и MacOS x64. Пользователям Windows повезло меньше - в настоящее время официальная версия AddressSanitizer официально не работает на многими любимой уютненькой операционной системе от Microsoft. AddressSanitizer может быть задействован, если передать параметр -fsanitize=address
компилятору и linker’у. Подробности на официальной страничке AddressSanitizer:
https://github.com/google/sanitizers/wiki/AddressSanitizer
Как же использовать AddressSanitizer для OpenJDK?
Java (или точнее OpenJDK) содержит много кода на C/C++ во многих своих компонентах, например:
- Большая часть виртуальной машины Hotspot написана на C/C++
- Java Core Libraries используют JNI для доступа к системным функциям. Для примеров можно посмотреть на код Java I/O или код для работы с сетью.
- Java Security Libraries используют JNI для более быстрого выполнения разных криптографических операций, а также для доступа к системным функциям по стандарту PKCS11
Разработчики ОpenJDK это люди, которые могут совершать ошибки, которые могут привести к печальным последствиям, таким как переполнения буферов и прочим напастям. Для своевременного обнаружения и устранения таких проблем мы и можем использовать AddressSanitizer.
Как использовать AddressSanitizer вместе с Java
Теперь можно построить OpenJDK с включенным AddressSanitizer. Поддержка AddressSanitizer была недавно добавлена в confiture/make файлы для OpenJDK:
http://hg.openjdk.java.net/jdk10/master/rev/77a5f2ef1807
Чтобы включить ASan при построении OpenJDK, нужно передать --enable-asan
параметр для configure
скрипта. Следующие команды позволяют построить OpenJDK с включенным AddressSanitizer:
# clone OpenJDK workspace
hg clone http://hg.openjdk.java.net/jdk10/master jdk10
cd jdk10
# configure
# you can also use 'fastdebug' or just --enable-debug
bash configure --with-boot-jdk=/path/to/jdk9 \
--with-debug-level=slowdebug \
--enable-asan
# build
make images
Для подробностей можно запустить bash configure --help
или посмотреть здесь:
http://hg.openjdk.java.net/jdk10/master/file/5a1afee9b9e2/doc/building.md
После того, как make
завершит свою работу, запустите java -version
для проверки, что Java собралась успешно. Здесь нужно будет отключить обработчик SEGV. Для этого нужно handle_segv=0
параметр для AddressSanitizer (runtime параметры AddressSanitizer ищет в переменной окружения ASAN_OPTIONS):
bash-4.2$ ASAN_OPTIONS="handle_segv=0" ./build/linux-x64/images/jdk/bin/java -version
java version "10-internal"
Java(TM) SE Runtime Environment (slowdebug build 10-internal+0-2017-10-30-1135083.asmotrak.jdk10asan)
Java HotSpot(TM) 64-Bit Server VM (slowdebug build 10-internal+0-2017-10-30-1135083.asmotrak.jdk10asan, mixed mode)
Если не передать handle_segv=0
, то можно увидеть такую ошибку:
ASAN:SIGSEGV
=================================================================
==115049==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x7f2e1c5eb513 sp 0x7f2e381709d8 bp 0x7f2e3df5a5c0 T1)
#0 0x7f2e1c5eb512 (+0x512)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ??:0 ??
Thread T1 created by T0 here:
#0 0x7f2e418f18aa in __interceptor_pthread_create /opt/jprt/jprtadm/erik/jdk9-dev/build/devkit/src/gcc-4.9.2/libsanitizer/asan/asan_interceptors.cc:183
#1 0x7f2e41284416 in ContinueInNewThread0 (/scratch/artem/jdk10_asan/build/linux-x64/images/jdk/bin/../lib/jli/libjli.so+0x1c416)
#2 0x7f2e4128006d in ContinueInNewThread (/scratch/artem/jdk10_asan/build/linux-x64/images/jdk/bin/../lib/jli/libjli.so+0x1806d)
#3 0x7f2e4128467a in JVMInit (/scratch/artem/jdk10_asan/build/linux-x64/images/jdk/bin/../lib/jli/libjli.so+0x1c67a)
#4 0x7f2e41275161 in JLI_Launch (/scratch/artem/jdk10_asan/build/linux-x64/images/jdk/bin/../lib/jli/libjli.so+0xd161)
#5 0x401272 in main (/scratch/artem/jdk10_asan/build/linux-x64/images/jdk/bin/java+0x401272)
#6 0x7f2e40cc4b34 in __libc_start_main (/lib64/libc.so.6+0x21b34)
==115049==ABORTING
По умолчанию AddressSanitizer еще включает LeakSanitizer. Это инструмент для обнаружения утечек памяти, который встроен в AddressSanitizer. В настоящее время мне не совсем понятно, работает ли LeakSanitizer корректно с виртуальной машиной Hotspot. Поэтому я обычно отключаю LeakSanitizer. Для этого нужно просто передать detect_leaks=0
через переменную окружения ASAN_OPTIONS:
bash-4.2$ ASAN_OPTIONS="handle_segv=0 detect_leaks=0" ./build/linux-x64/images/jdk/bin/java -version
java version "10-internal"
Java(TM) SE Runtime Environment (slowdebug build 10-internal+0-2017-10-30-1135083.asmotrak.jdk10asan)
Java HotSpot(TM) 64-Bit Server VM (slowdebug build 10-internal+0-2017-10-30-1135083.asmotrak.jdk10asan, mixed mode)
Подробности о параметрах AddressSanitizer можно найти здесь:
https://github.com/google/sanitizers/wiki/SanitizerCommonFlags
Запуск Java тестов вместе с AddressSanitizer
Если вы участвуете в разработке OpenJDK, и ваши изменения касаются кода на С/C++, то вам может быть полезно запускать существующие тесты для OpenJDK с включенным AddressSanitizer. Для этого нужно просто собрать OpenJDK с вашими изменениями и запустить make test
. Эта команда запустит все существующие автоматические тесты. Если вам необходимо запустить только какое-то подмножество тестов, то можно использовать переменную TEST, например, make run-test TEST=jdk_lang
. Еще можно просто запустить необходимые тесты с помощью Jtreg (это test harness, который используется для тестирования OpenJDK). Подробности можно найти на Testing OpenJDK
Известные проблемы
Код на Java, который запускает новые процессы с помощью ProcessBuilder может вызвать следующую ошибку если включен AddressSanitizer:
Address 0x7fceacab7280 is located in stack of thread T24 (MainThread)==10175==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_thread.cc:231 "((ptr[0] == kCurrentStackFrameMagic)) != (0)" (0x0, 0x0)
Происходит это. потому что Process API использует вызовы fork() and vfork(), в тоже время виртуальная машина Hotspot использует Threads API. Похоже, что AddressSanitizer сходит от этого с ума, подробности можно почитать здесь:
https://github.com/google/sanitizers/issues/255
Ограничения
Несмотря на то, что AddressSanitizer является хорошо себя зарекомендовавшим инструментом по отлову переполнения буферов и прочих безобразий, это вовсе не серебряная пуля. Все равно надо использовать мозг при написании C/C++ кода и стараться избегать переполнения буферов, утечек памяти и прочих ошибок, которые могут возникнуть с работой с памятью. Уже в дополнение к этому было бы неплохо вооружиться хорошими инструментами, такими как runtime memory checkers и всевозможными статическими анализаторами кода.
Что дальше?
Гриша Штойк заметил, что было бы здорово, если бы проверки AddressSanitizer’а вставлялись и в код, который генерирует Java runtime компилятор. Я не эксперт в runtime компиляции, поэтому спросил об это Игоря Игнатьева из Hotspot Group. Игорь сказал, что это будет сложно реализовать в C1/C2 компиляторах, но должно быть проще с компилятором Graal.