使用 Clang 进行动态分析¶
本文档介绍如何使用 Clang 对 Python 及其库执行分析。除了执行分析之外,本文档还将介绍下载、构建和安装最新的 Clang/LLVM 组合(当前为 3.4)。
本文档不涵盖结果解读。有关结果解读的讨论,请参阅 Marshall Clow 的 使用 -fsanitize=undefined 测试 libc++。该博客文章详细探讨了 Clang 在 libc++
中发现的问题。
什么是 Clang?¶
Clang 是 LLVM 编译器的 C、C++ 和 Objective C 前端。该前端提供对 LLVM 的优化器和代码生成器的访问权限。清理器或检查器是代码生成阶段的挂钩,用于对编译代码进行检测,以便标记可疑行为。
什么是清理器?¶
Clang 清理器是用于识别可疑和未定义行为的运行时检查器。检查在运行时使用实际运行时参数进行,因此将误报降至最低。
有许多可用的清理器,但应定期使用的两个清理器是地址清理器 (ASan) 和未定义行为清理器 (UBSan)。使用编译器选项 -fsanitize=address
调用 ASan,使用 -fsanitize=undefined
调用 UBSan。这些标志通过 CFLAGS
和 CXXFLAGS
传递,有时通过 CC
和 CXX
传递(除了编译器之外)。
可以在 控制代码生成 中找到清理器的完整列表。
注意
由于清理器在实际程序参数上在运行时操作,因此提供一组完整的正负自测非常重要。
Clang 及其清理器有优点(也有缺点)。它只是发现错误和提高代码质量的武器库中的一个工具。Clang 应与其他方法(包括代码审查、Valgrind、Coverity 等)配合使用。
Clang/LLVM 设置¶
本文档的这一部分介绍如何下载、构建和安装 Clang 和 LLVM。有三个组件需要下载和构建。它们是 LLVM 编译器、编译器前端和编译器运行时库。
在准备阶段,您应该创建一个临时目录。还要确保您使用的是 Python 2,而不是 Python 3。Python 3 将导致构建失败。
下载、构建和安装¶
执行以下操作以下载、构建和安装 Clang/LLVM 3.4。
# Download
wget https://llvm.net.cn/releases/3.4/llvm-3.4.src.tar.gz
wget https://llvm.net.cn/releases/3.4/clang-3.4.src.tar.gz
wget https://llvm.net.cn/releases/3.4/compiler-rt-3.4.src.tar.gz
# LLVM
tar xvf llvm-3.4.src.tar.gz
cd llvm-3.4/tools
# Clang Front End
tar xvf ../../clang-3.4.src.tar.gz
mv clang-3.4 clang
# Compiler RT
cd ../projects
tar xvf ../../compiler-rt-3.4.src.tar.gz
mv compiler-rt-3.4/ compiler-rt
# Build
cd ..
./configure --enable-optimized --prefix=/usr/local
make -j4
sudo make install
注意
如果您收到错误 'LibraryDependencies.inc' file not found
,则确保您使用的是 Python 2,而不是 Python 3。如果您在切换到 Python 2 后遇到错误,请删除所有内容并重新开始。
在执行 make install
之后,编译器将安装在 /usr/local/bin
中,各种库将安装在 /usr/local/lib/clang/3.4/lib/linux/
中
$ ls /usr/local/lib/clang/3.4/lib/linux/
libclang_rt.asan-x86_64.a libclang_rt.profile-x86_64.a
libclang_rt.dfsan-x86_64.a libclang_rt.san-x86_64.a
libclang_rt.full-x86_64.a libclang_rt.tsan-x86_64.a
libclang_rt.lsan-x86_64.a libclang_rt.ubsan_cxx-x86_64.a
libclang_rt.msan-x86_64.a libclang_rt.ubsan-x86_64.a
在 macOS 上,库安装在 /usr/local/lib/clang/3.3/lib/darwin/
中
$ ls /usr/local/lib/clang/3.3/lib/darwin/
libclang_rt.10.4.a libclang_rt.ios.a
libclang_rt.asan_osx.a libclang_rt.osx.a
libclang_rt.asan_osx_dynamic.dylib libclang_rt.profile_ios.a
libclang_rt.cc_kext.a libclang_rt.profile_osx.a
libclang_rt.cc_kext_ios5.a libclang_rt.ubsan_osx.a
libclang_rt.eprintf.a
注意
您永远不必将库添加到项目中。Clang 会为您处理。如果您发现无法在 configure
期间通过 make
的隐式变量(CFLAGS
、CXXFLAGS
、CC
、CXXFLAGS
、LDFLAGS
)传递 -fsanitize=XXX
标志,那么您应该在配置后修改 Makefile 以确保标志通过编译器传递。
安装程序有时不会安装所有需要的组件。例如,您可能想要运行 scan-build
或使用 scan-view
检查结果。您可以手动复制组件,方法是
sudo mkdir /usr/local/bin/scan-build
sudo cp -r llvm-3.4/tools/clang/tools/scan-build /usr/local/bin
sudo mkdir /usr/local/bin/scan-view
sudo cp -r llvm-3.4/tools/clang/tools/scan-view /usr/local/bin
注意
由于安装程序有时不会安装所有需要的组件,因此在您确信一切按预期工作之前,不要删除临时目录。如果缺少某个库,那么您应该在 Clang/LLVM 构建目录中搜索它。
Python 构建设置¶
本文档的这一部分涵盖了使用选项调用 Clang 和 LLVM,以便扫描程序使用其测试套件分析 Python。使用了两个检查器 - ASan 和 UBSan。
由于扫描程序是运行时检查器,因此最好尽可能多地进行正向和负向自测。自测永远不会嫌多。
总体思路是使用扫描程序标志进行编译和链接。在链接时,Clang 将包含所需的运行时库。但是,您不能使用 CFLAGS
和 CXXFLAGS
通过编译器将选项传递给链接器,因为 BUILDPYTHON
、_testembed
和 _freeze_importlib
的 makefile 规则不使用隐式变量。
作为没有链接器标志的解决方法,您可以通过编译器 - CC
和 CXX
传递扫描程序选项。下面使用了通过编译器传递标志,但据报道通过 LDFLAGS
传递标志也可以正常工作。
构建 Python¶
首先,使用所需的扫描程序导出感兴趣的变量。可以同时指定两个扫描程序
# ASan
export CC="/usr/local/bin/clang -fsanitize=address"
export CXX="/usr/local/bin/clang++ -fsanitize=address -fno-sanitize=vptr"
或
# UBSan
export CC="/usr/local/bin/clang -fsanitize=undefined"
export CXX="/usr/local/bin/clang++ -fsanitize=undefined -fno-sanitize=vptr"
-fno-sanitize=vptr
删除了由于噪音而从 C++ 项目中移除的 UBSan 中的 vtable 检查。Python 不需要它,但您可能需要它用于其他 C++ 项目。
导出 CC
和 CXX
后,正常地 configure
$ ./configure
checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
checking for --enable-universalsdk... no
checking for --with-universal-archs... 32-bit
checking MACHDEP... linux
checking for --without-gcc... no
checking for gcc... /usr/local/bin/clang -fsanitize=undefined
checking whether the C compiler works... yes
...
接下来是标准 make
(添加了格式以提高清晰度)
$ make
/usr/local/bin/clang -fsanitize=undefined -c -Wno-unused-result
-DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I.
-IInclude -I./Include -DPy_BUILD_CORE -o Modules/python.o
./Modules/python.c
/usr/local/bin/clang -fsanitize=undefined -c -Wno-unused-result
-DNDEBUG -g -fwrapv -O3 -Wall -Wstrict-prototypes -I.
-IInclude -I./Include -DPy_BUILD_CORE -o Parser/acceler.o
Parser/acceler.c
...
最后是 make test
(添加了格式以提高清晰度)
Objects/longobject.c:39:42: runtime error: index -1 out of bounds
for type 'PyLongObject [262]'
Objects/tupleobject.c:188:13: runtime error: member access within
misaligned address 0x2b76be018078 for type 'PyGC_Head' (aka
'union _gc_head'), which requires 16 byte alignment
0x2b76be018078: note: pointer points here
00 00 00 00 40 53 5a b6 76 2b 00 00 60 52 5a b6 ...
^
...
如果您使用的是地址扫描程序,则通过 asan_symbolize.py
传递输出以获得良好跟踪非常重要。例如,从编译期间的问题 20953(添加了格式以提高清晰度)
$ make test 2>&1 | asan_symbolize.py
...
/usr/local/bin/clang -fsanitize=address -Xlinker -export-dynamic
-o python Modules/python.o libpython3.3m.a -ldl -lutil
/usr/local/ssl/lib/libssl.a /usr/local/ssl/lib/libcrypto.a -lm
./python -E -S -m sysconfig --generate-posix-vars
=================================================================
==24064==ERROR: AddressSanitizer: heap-buffer-overflow on address
0x619000004020 at pc 0x4ed4b2 bp 0x7fff80fff010 sp 0x7fff80fff008
READ of size 4 at 0x619000004020 thread T0
#0 0x4ed4b1 in PyObject_Free Python-3.3.5/./Objects/obmalloc.c:987
#1 0x7a2141 in code_dealloc Python-3.3.5/./Objects/codeobject.c:359
#2 0x620c00 in PyImport_ImportFrozenModuleObject
Python-3.3.5/./Python/import.c:1098
#3 0x620d5c in PyImport_ImportFrozenModule
Python-3.3.5/./Python/import.c:1114
#4 0x63fd07 in import_init Python-3.3.5/./Python/pythonrun.c:206
#5 0x63f636 in _Py_InitializeEx_Private
Python-3.3.5/./Python/pythonrun.c:369
#6 0x681d77 in Py_Main Python-3.3.5/./Modules/main.c:648
#7 0x4e6894 in main Python-3.3.5/././Modules/python.c:62
#8 0x2abf9a525eac in __libc_start_main
/home/aurel32/eglibc/eglibc-2.13/csu/libc-start.c:244
#9 0x4e664c in _start (Python-3.3.5/./python+0x4e664c)
AddressSanitizer can not describe address in more detail (wild
memory access suspected).
SUMMARY: AddressSanitizer: heap-buffer-overflow
Python-3.3.5/./Objects/obmalloc.c:987 PyObject_Free
Shadow bytes around the buggy address:
0x0c327fff87b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff87c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff87d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff87e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff87f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c327fff8800: fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8810: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8820: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c327fff8850: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
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
Heap right redzone: fb
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
ASan internal: fe
==24064==ABORTING
make: *** [pybuilddir.txt] Error 1
注意
asan_symbolize.py
应该在 make install
期间安装。如果未安装,请在 Clang/LLVM 构建目录中查找它并将其复制到 /usr/local/bin
。
黑名单(忽略)发现结果¶
Clang 允许您在编译时提供一个特殊的黑名单文件,从而更改扫描程序工具在特定源代码级别的行为。需要黑名单是因为它会报告问题的每个实例,即使该问题在非托管库代码中被报告了数万次。
您可以使用 -fsanitize-blacklist=XXX
指定黑名单。例如
-fsanitize-blacklist=my_blacklist.txt
my_blacklist.txt
然后将包含以下条目。该条目将忽略 libc++
的 ios
格式化函数中的一个 bug
fun:_Ios_Fmtflags
以 Python 3.4.0 为例,audioop.c
会产生一些发现
./Modules/audioop.c:422:11: runtime error: left shift of negative value -1
./Modules/audioop.c:446:19: runtime error: left shift of negative value -1
./Modules/audioop.c:476:19: runtime error: left shift of negative value -1
./Modules/audioop.c:504:16: runtime error: left shift of negative value -1
./Modules/audioop.c:533:22: runtime error: left shift of negative value -128
./Modules/audioop.c:775:19: runtime error: left shift of negative value -70
./Modules/audioop.c:831:19: runtime error: left shift of negative value -70
./Modules/audioop.c:881:19: runtime error: left shift of negative value -1
./Modules/audioop.c:920:22: runtime error: left shift of negative value -70
./Modules/audioop.c:967:23: runtime error: left shift of negative value -70
./Modules/audioop.c:968:23: runtime error: left shift of negative value -70
...
其中一个感兴趣的函数是 audioop_getsample_impl
(在第 422 行标记),黑名单条目将包括
fun:audioop_getsample_imp
或者,你可以使用以下命令忽略整个文件
src:Modules/audioop.c
遗憾的是,在运行 sanitizer 之前,你不知道要将什么列入黑名单。
文档可在 Sanitizer 特殊情况列表 中找到。