Zapccを評価するために、図らずも自作RISC-VシミュレータをLLVMに移植したのだった。
LLVMに移植したので、どうせならLLVMのメモリリークチェック機能などについて試行してみたい。 TuringCompleteFM にも出ていたのだが、LLVMのSanitizerはかなり優秀らしいということで動作させてみた。 これまではValgrindしか使ってこなかったのでLLVMのSanitizerのほうが優秀なのであれば移行したい。
LLVMのSanitizerを調査した結果、大きく分けて3種類が存在するらしい。
- AddressSanitizer (detects addressability issues) and LeakSanitizer (detects memory leaks)
- ThreadSanitizer (detects data races and deadlocks) for C++ and Go
- MemorySanitizer (detects use of uninitialized memory)
一つ一つ試していこう。
Memory Sanitizer (未初期化メモリをチェックする)
Memory Sanitizerを有効化するためには、LLVMのコンパイルオプションに -fsanitize=memory を追加してビルドする。
ビルド後のRISC-Vシミュレータを動作させると、いきなりGFlagsの部分で落ちてしまった。あれえ?Googleのコードなのに。
==21479==WARNING: MemorySanitizer: use-of-uninitialized-value
#0 0x70ebf6 in std::_Rb_tree<char const*, std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*>, std::_Select1st<std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*> >, google::(anonymous namespace)::StringCmp, std::allocator<std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*> > >::_M_get_insert_unique_pos(char const* const&) /usr/lib/gcc/x86_64-linux-gnu/7.3.0/../../../../include/c++/7.3.0/bits/stl_tree.h:2035:7
#1 0x70e6f0 in std::pair<std::_Rb_tree_iterator<std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*> >, bool> std::_Rb_tree<char const*, std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*>, std::_Select1st<std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*> >, google::(anonymous namespace)::StringCmp, std::allocator<std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*> > >::_M_insert_unique<std::pair<char const*, google::(anonymous namespace)::CommandLineFlag*> >(std::pair<char const*, google::(anonymous namespace)::CommandLineFlag*>&&) /usr/lib/gcc/x86_64-linux-gnu/7.3.0/../../../../include/c++/7.3.0/bits/stl_tree.h:2091:4
#2 0x70d7ce in std::pair<std::_Rb_tree_iterator<std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*> >, bool> std::map<char const*, google::(anonymous namespace)::CommandLineFlag*, google::(anonymous namespace)::StringCmp, std::allocator<std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*> > >::insert<std::pair<char const*, google::(anonymous namespace)::CommandLineFlag*>, void>(std::pair<char const*, google::(anonymous namespace)::CommandLineFlag*>&&) /usr/lib/gcc/x86_64-linux-gnu/7.3.0/../../../../include/c++/7.3.0/bits/stl_map.h:810:16
#3 0x70d045 in google::(anonymous namespace)::FlagRegistry::RegisterFlag(google::(anonymous namespace)::CommandLineFlag*) /home/msyksphinz/work/forest/riscv_forest_zapcc/vendor/gflags/src/gflags.cc:750:12
#4 0x6fd174 in google::(anonymous namespace)::RegisterCommandLineFlag(char const*, char const*, char const*, google::(anonymous namespace)::FlagValue*, google::(anonymous namespace)::FlagValue*) /home/msyksphinz/work/forest/riscv_forest_zapcc/vendor/gflags/src/gflags.cc:1472:35
#5 0x730f93 in google::FlagRegisterer::FlagRegisterer<long>(char const*, char const*, char const*, long*, long*) /home/msyksphinz/work/forest/riscv_forest_zapcc/vendor/gflags/src/gflags.cc:1484:3
#6 0x47f5f0 in __cxx_global_var_init.4 /home/msyksphinz/work/forest/riscv_forest_zapcc/src/swimmer_main.cpp:62:1
#7 0x480e02 in _GLOBAL__sub_I_swimmer_main.cpp /home/msyksphinz/work/forest/riscv_forest_zapcc/src/swimmer_main.cpp
#8 0x77695c in __libc_csu_init (/home/msyksphinz/work/forest/riscv_forest_zapcc/build_riscvforest/riscvforest+0x77695c)
#9 0x7f085bce0b27 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:266
#10 0x490b39 in _start (/home/msyksphinz/work/forest/riscv_forest_zapcc/build_riscvforest/riscvforest+0x490b39)
SUMMARY: MemorySanitizer: use-of-uninitialized-value /usr/lib/gcc/x86_64-linux-gnu/7.3.0/../../../../include/c++/7.3.0/bits/stl_tree.h:2035:7 in std::_Rb_tree<char const*, std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*>, std::_Select1st<std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*> >, google::(anonymous namespace)::StringCmp, std::allocator<std::pair<char const* const, google::(anonymous namespace)::CommandLineFlag*> > >::_M_get_insert_unique_pos(char const* const&)
Exiting
というわけで Memory Sanitizerはいったんスキップする。
Address Sanitizer (メモリリークをチェックする)
Memory Sanitizerを有効化するためには、LLVMのコンパイルオプションに -fsanitize=address を追加してビルドする。
ビルドしてバイナリを実行すると、エラーで落ちた。確かに、アドレスオーバフローを起こしている部分が存在した。 修正して再ビルドすると、エラー無く実行できるようになった。
==4691==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffc68865b80 at pc 0x000000552502 bp 0x7ffc68865b30 sp 0x7ffc688652e0
READ of size 4 at 0x7ffc68865b80 thread T0
#0 0x552501 in __asan_memcpy /home/msyksphinz/software/llvm/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:23
#1 0x673654 in ModuleRom::ModuleRom(EnvBase*, _IO_FILE*) /home/msyksphinz/work/forest/riscv_forest_zapcc/src/module_rom.cpp:62:5
#2 0x5a4efd in RiscvPeThread::RiscvPeThread(_IO_FILE*, PrivMode, bool, bool, _IO_FILE*) /home/msyksphinz/work/forest/riscv_forest_zapcc/src/riscv_pe_thread.cpp:71:45
#3 0x59970f in main /home/msyksphinz/work/forest/riscv_forest_zapcc/src/swimmer_main.cpp:170:33
#4 0x7f0fc95ecb96 in __libc_start_main /build/glibc-OTsEL5/glibc-2.27/csu/../csu/libc-start.c:310
#5 0x489479 in _start (/home/msyksphinz/work/forest/riscv_forest_zapcc/build_riscvforest/riscvforest+0x489479)
Address 0x7ffc68865b80 is located in stack of thread T0 at offset 64 in frame
#0 0x6733cf in ModuleRom::ModuleRom(EnvBase*, _IO_FILE*) /home/msyksphinz/work/forest/riscv_forest_zapcc/src/module_rom.cpp:50
This frame has 3 object(s):
[32, 64) 'init' (line 53) <== Memory access at offset 64 overflows this variable
[96, 488) 's' (line 65)
[560, 592) 'config_string' (line 90)
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/msyksphinz/software/llvm/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:23 in __asan_memcpy
Shadow bytes around the buggy address:
0x10000d104b20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10000d104b30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10000d104b40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10000d104b50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10000d104b60: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 00 00 00 00
=>0x10000d104b70:[f2]f2 f2 f2 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
0x10000d104b80: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
0x10000d104b90: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
0x10000d104ba0: f8 f8 f8 f8 f8 f2 f2 f2 f2 f2 f2 f2 f2 f2 f8 f8
0x10000d104bb0: f8 f8 f3 f3 f3 f3 f3 f3 00 00 00 00 00 00 00 00
0x10000d104bc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==4691==ABORTING
Thread Sanitizer (スレッドのレースコンディションをチェックする)
Thread Sanitizerを有効化するためには、 LLVMのコンパイルオプションに -fsanitize=thread を追加してビルドする。
ビルドしてバイナリを実行しても、特にエラーは発生しなかった。まあ、確かにマルチスレッドは使用していないのでエラーは発生しないだろう。
実行速度
それぞれ実行速度を比較すると以下のようになった。まあValgrindは--leak-check=fullなのでもしかしたら正確な比較ではないかもしれないが、LLVMのSanitizer、結構速いね。
- Normal : 2m43.460s
- Thread Sanitizer : 16m37.715s
- Address Sanitizer : 7m53.666s
- Valgrind
--leak-check=full: 3時間程度...