C/C++应用程序编译规范
在C/C++应用程序开发过程中,编译器类型、语言标准及编译选项的差异,可能引入未定义行为、安全隐患和平台兼容性问题,进而影响系统稳定性和产品交付质量。本规范对C/C++应用程序的编译器选型、语言标准、编译参数、构建模式及相关管理要求作出统一规定,作为项目开发、集成、测试和交付过程中的基本遵循依据。
如果需要定位C/C++应用程序中出现的内存问题,可以开启ASan(AddressSanitizer)并解析ASan日志定位问题原因,详细请参考ASan以及ASan日志解析。
ASan
ASan(AddressSanitizer)是一款用于C/C++的内存错误检测器。以下内容介绍如何使用ASan日志查看内存问题。
ASan可以获取的内存错误信息,请参见表1。
| 关键字 | 含义 |
|---|---|
| heap-buffer-overflow | 堆越界 |
| heap-use-after-free | 释放后使用 |
| double-free | 重复释放 |
| invalid-free | 非法释放 |
| stack-buffer-overflow | 栈越界 |
| stack-use-after-return | 栈UAR(函数返回后仍然使用其栈内存) |
| global-buffer-overflow | 全局区越界 |
| alloc-dealloc-mismatch | 分配释放不匹配 |
| new-delete-type-mismatch | new/delete 错误 |
| use-after-poison | 访问红区 |
| memory-leak | 内存泄漏 |
该工具支持x86、ARMCPU架构,支持Linux操作系统。
开启并生成ASan日志
以下步骤详细介绍如何开启ASan,并生成ASan日志:
- 使用ASan,需要在编译C/C++程序时在编译命令中添加-fsanitize=address。 以最基础的编译命令举例:
gcc test.c -o test -fsanitize=address
- 检查ASan环境变量是否设置。
echo $ASAN_OPTIONS
如果输出为空,执行3。否则检查Asan环境变量是否含有目标参数,并且参数值设置正确。
- 设置Asan环境变量。 执行以下命令设置ASan环境变量:
export ASAN_OPTIONS="unmap_shadow_on_exit=true:abort_on_error=true:log_path=[log_path]/[log_file_prefix]:alloc_dealloc_mismatch=1:fast_unwind_on_fatal=true:detect_stack_use_after_return=1:log_exe_name=true"
ASAN_OPTIONS可设置参数请参考。
- unmap_shadow_on_exit:在退出时显式地取消映射(大页)影子内存。默认为false。
- abort_on_error:在打印错误报告后,工具将调用abort()函数,而不是_exit()函数。默认为false。
- log_path:将日志写入“log_path.pid”。特殊值有“stdout”和“stderr”,默认为“stderr”。
其中环境变量中建议设置log_path,该参数为ASan日志输出路径,如不设置,无法获取具体的报错栈。[log_path]为日志文件目录,[log_file_prefix]为日志文件前缀。[log_path]和[log_file_prefix]均可由用户设置。
- alloc_dealloc_mismatch:用来检测“分配 / 释放方式不匹配”的内存错误,默认为0。
- fast_unwind_on_fatal:在发生致命错误时,使用基于快速帧指针的异常处理程序。默认为false。
- detect_stack_use_after_return:用于检测“函数返回后仍然使用其栈内存”的错误(Stack Use-After-Return,简称 UAR),默认为0。
- log_exe_name:报告错误时提及可执行文件名称,并在日志中添加可执行文件名(如“log_path.exe_name.pid”)。
- 运行编译后的文件。
./test
- 查看ASan日志
执行结束后,ASan日志会生成在2ASan环境变量ASAN_OPTIONS中log_path设置的路径[log_path]中,日志格式为[log_file_prefix].[exe_name].[pid]。
以log_path设置为“/var/log”,log_file_prefix设置为“runlog”,exe_name为可执行文件名为test,运行test时的pid为1234为例,日志文件输出路径为:
/var/log/runlog.test.1234
ASan日志解析
下面通过一段ASan日志分析日志结构:
=================================================================
==602791==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000026904 at pc 0x000000400775 bp 0x7ffeba71f610 sp 0x7ffeba71f608
WRITE of size 4 at 0x603000026904 thread T0
#0 0x400774 in main (/home/test/workspace/mem+0x400774)
#1 0x7fb441858964 in __libc_start_main (/lib64/libc.so.6+0x22964)
#2 0x400658 (/home/test/workspace/mem+0x400658)
0x603000026904 is located 0 bytes to the right of 20-byte region [0x6030000268f0,0x603000026904)
allocated by thread T0 here:
#0 0x7fb4428d84d0 in __interceptor_malloc ../../.././libsanitizer/asan/asan_malloc_linux.cc:62
#1 0x400731 in main (/home/test/workspace/mem+0x400731)
#2 0x7fb441858964 in __libc_start_main (/lib64/libc.so.6+0x22964)
SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/test/workspace/mem+0x400774) in main
Shadow bytes around the buggy address:
0x0c067fffccd0: 00 00 00 00 fa fa 00 00 00 00 fa fa 00 00 00 fa
0x0c067fffcce0: fa fa 00 00 00 00 fa fa 00 00 00 00 fa fa fd fd
0x0c067fffccf0: fd fd fa fa fd fd fd fd fa fa 00 00 00 fa fa fa
0x0c067fffcd00: fd fd fd fd fa fa fd fd fd fd fa fa fd fd fd fd
0x0c067fffcd10: fa fa fd fd fd fd fa fa 00 00 00 fa fa fa 00 00
=>0x0c067fffcd20:[04]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fffcd30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fffcd40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fffcd50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fffcd60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c067fffcd70: 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
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
==602791==ABORTING 从上到下拆分日志依次解析:
日志头部信息:
==602791==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000026904 at pc 0x000000400775 bp 0x7ffeba71f610 sp 0x7ffeba71f608
字段含义:
- ==602791==
- ERROR:
- AddressSanitizer
- heap-buffer-overflow
错误类型关键字,详情请参考表1。
- on address 0x...
- at pc 0x...
- bp 0x... sp 0x...
访问动作摘要如下:
WRITE of size 4 at 0x603000026904 thread T0
字段含义:
报错触发调用栈如下:
#0 0x400774 in main (/home/test/workspace/mem+0x400774) #1 0x7fb441858964 in __libc_start_main (/lib64/libc.so.6+0x22964) #2 0x400658 (/home/test/workspace/mem+0x400658)
地址与对象边界描述如下:
0x603000026904 is located 0 bytes to the right of 20-byte region [0x6030000268f0,0x603000026904)
- is located ...
- xx-byte region
- [start, end)
分配/释放来源:
allocated by thread T0 here:
#0 0x... in __interceptor_malloc ...:62
#1 0x... in main (...) - allocated by thread T0 here
- #0 ... __interceptor_malloc
拦截器函数(interceptor):ASan会把 malloc/free/new/delete等替换/包装成自己的版本,用来记录元数据与检测越界。
asan_malloc_linux.cc:62是ASan运行库源码位置(用于表明这是ASan的拦截实现)。
若是use-after-free,通常还会出现 freed by thread ... here: 的释放栈。
SUMMARY行:简要归纳(Summary):
SUMMARY: AddressSanitizer: heap-buffer-overflow (/path/to/bin+0x400774) in main
通常包含:
- sanitizer名称。
- 错误类型。
- 一个“代表性位置”(模块+偏移、以及符号化后的函数名)。
简要归纳可以快速定位“主要出错点”。