sanitizer_how_to_use

本文详细介绍了Google的Sanitizer工具系列,包括其不同类型(内存泄漏、数据竞争、未知行为等)、安装步骤、静态库和动态库的区别,以及如何在CFLAGS和LDLAGS中正确配置以使用这些工具进行内存安全检查。通过实例演示了如何在C代码中检测内存泄漏并展示了AddressSanitizer的报警输出。
摘要由CSDN通过智能技术生成

Sanitizer 的应用笔记


Sanitizer 的功能

Sanitizer 是 Google 研发的一个用于检查工具。有下面的4种类型:

类型应用于检查以下问题
Address Sanitizer内存泄漏,地址越界,野指针,悬停指针,等等
Leak Sanitizer只有内存泄漏
Thread Sanitizer数据竞争(Data Race)
Unknown Behavior Sanitizer未定义行为(内存对齐,运算异常,等等)

注意:

  • 由于有的组合是不可以同时使用的。所以我们每次只用一种类型就好了。

Sanitizer 的安装

Sanitizer 最好使用 gcc 4.8 以上的版本。这样可以提供好的调试信息。

所以,这里以 gcc 7 为例子 进行安装。

# 安装 gcc 7
yum install -y devtoolset-7

# 安装 Address Sanitizer
yum install -y devtoolset-7-libasan-devel
# 安装 Leak Sanitizer
yum install -y devtoolset-7-liblsan-devel
# 安装 Thread Sanitizer
yum install -y devtoolset-7-libtsan-devel
# 安装 Unknown Behavior Sanitizer
yum install -y devtoolset-7-libubsan-devel

Sanitizer 的库

# 切换到 gcc 7
scl enable devtoolset-7 bash
# 检查一下 gcc 版本
gcc -v
>	Using built-in specs.
>	COLLECT_GCC=gcc
>	COLLECT_LTO_WRAPPER=/opt/rh/devtoolset-7/root/usr/libexec/gcc/x86_64-redhat-linux/7/lto-wrapper
>	Target: x86_64-redhat-linux
>	Configured with: ...
>	Thread model: posix
>	gcc version 7.3.1 20180303 (Red Hat 7.3.1-5) (GCC)			<-- Version of gcc

```bash
# 切换到 Sanitizer 库所在的目录
cd /opt/rh/devtoolset-7/root/usr/lib/gcc/x86_64-redhat-linux/7
# 看看 Sanitizer 的静态库,后续我们会使用静态库。
ll | grep san.a
>	-rw-r--r-- 1 root root 2921486 Sep 13  2019 libasan.a
>	-rw-r--r-- 1 root root  860830 Sep 13  2019 liblsan.a
>	-rw-r--r-- 1 root root 2032876 Sep 13  2019 libtsan.a
>	-rw-r--r-- 1 root root  805202 Sep 13  2019 libubsan.a
# 看看 Sanitizer 的动态库,会发现文件的大小实在太小了。
ll | grep san.so
>	-rw-r--r-- 1 root root      82 Sep 13  2019 libasan.so
>	-rw-r--r-- 1 root root      82 Sep 13  2019 liblsan.so
>	-rw-r--r-- 1 root root      82 Sep 13  2019 libtsan.so
>	-rw-r--r-- 1 root root      83 Sep 13  2019 libubsan.so
# 打开一个看看,发现Sanitizer 的动态库,只是一个链接。所以还是不要用了。
cat libasan.so
>	/* GNU ld script */
>	OUTPUT_FORMAT(elf64-x86-64)
>	INPUT ( /usr/lib64/libasan.so.4 )

至此,我们已经安装好 Sanitizer 的静态库。
将它们列表如下:

类型静态库
Address Sanitizerlibasan.so
Leak Sanitizerliblsan.so
Thread Sanitizerlibtsan.so
Unknown Behavior Sanitizerlibubsan.so

Sanitizer 的 flag

使用 man gcc 打开 帮助,可以看到 Sanitizer 的使用方法,是这样的。

-static-libasan

When the -fsanitize=address option is used to link a program,
the GCC driver automatically links against libasan.

If libasan is available as a shared library, and the -static option is not used,
then this links against the shared version of libasan.

The -static-libasan option directs the GCC driver to link libasan statically,
without necessarily linking other libraries statically.

-static-libtsan

When the -fsanitize=thread option is used to link a program,
the GCC driver automatically links against libtsan.

If libtsan is available as a shared library, and the -static option is not used,
then this links against the shared version of libtsan.

The -static-libtsan option directs the GCC driver to link libtsan statically,
without necessarily linking other libraries statically.

-static-liblsan

When the -fsanitize=leak option is used to link a program,
the GCC driver automatically links against liblsan.

If liblsan is available as a shared library, and the -static option is not used,
then this links against the shared version of liblsan.

The -static-liblsan option directs the GCC driver to link liblsan statically,
without necessarily linking other libraries statically.

-static-libubsan

When the -fsanitize=undefined option is used to link a program,
the GCC driver automatically links against libubsan.

If libubsan is available as a shared library, and the -static option is not used,
then this links against the shared version of libubsan.

The -static-libubsan option directs the GCC driver to link libubsan statically,
without necessarily linking other libraries statically.

参透了帮助信息,我们就可以编写 makefile 了。

Sanitizer 的 CFLAGS

假设Sanitizer 的 CFLAGS 为 SAN_CFLAGS
那么为了使用静态库,我们需要再追加 -static-lib*san
另外,为了可以便于提供更多的调试信息。我们还需要追加 -fno-omit-frame-pointer

类型SAN_CFLAGS
Address Sanitizer-fsanitize=address -static-libasan -fno-omit-frame-pointer
Leak Sanitizer-fsanitize=leak -static-liblsan -fno-omit-frame-pointer
Thread Sanitizer-fsanitize=thread -static-libtsan -fno-omit-frame-pointer
Unknown Behavior Sanitizer-fsanitize=undefined -static-libubsan -fno-omit-frame-pointer

以下以 Address Sanitizer 为例,写出 CFLAGS 的设置。

#-------------------------------------------------------------------------------
# compiler flag
## base linker flag
CFLAGS :=

#...............................................................................
## version compiler flag
VERSION_CFLAGS = -O0 -g3

## sanitizer compiler flags
ASAN_CFLAGS = -fsanitize=address -static-libasan -fno-omit-frame-pointer

SAN_CFLAGS = $(ASAN_CFLAGS)

#...............................................................................
# assemble compiler flag
CFLAGS += $(VERSION_CFLAGS)
CFLAGS += $(SAN_CFLAGS)

Sanitizer 的 LDLAGS

由于我们使用的是 Address Sanitizer 的静态库,所以链接的时候,需要用 -l:libasan.a来指定链接到静态库。
并且还需要使用 -Wl,--whole-archive 链接入一整个的静态库。

所以LDFLAGS需要设置如下:

## address sanitizer linker flags
LDFLAGS += -Wl,--whole-archive
LDFLAGS += -l:libasan.a
LDFLAGS += -Wl,--no-whole-archive

再者,Address Sanitizer 的静态库,是需要依赖于 librt.so, libdl.so, libpthread.so, libm.so 动态库的。
我们定义 SAN_LDFLAGS_DEPENDED_ON 表示 Sanitizer 所依赖的动态库。

SAN_LDFLAGS_DEPENDED_ON 的定义如下:

SAN_LDFLAGS_DEPENDED_ON = -lrt -ldl -lrt -lpthread -lm

注意:

  • -lrt -ldl -lrt-lrt 出现了两次,是为了避免链接时的次序问题。

LDFLAGS 在 -l:libasan.a 的前后,插入 SAN_LDFLAGS_DEPENDED_ON

#...............................................................................
## sanitizer linker flags depended on
SAN_LDFLAGS_DEPENDED_ON = -lrt -ldl -lrt -lpthread -lm

## address sanitizer linker flags
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
LDFLAGS += -Wl,--whole-archive
LDFLAGS += -l:libasan.a
LDFLAGS += -Wl,--no-whole-archive
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
endif

注意:

  • SAN_LDFLAGS_DEPENDED_ON 插入了两次,也是为了避免链接时的次序问题。

最后为了可以直接从 CFLAGS 就推断出 Sanitizer 的类型,从而设置好 LDFLAGS
我们使用 findstring 来对 CFLAGS 进行查找,来判断 Sanitizer 的类型。

#...............................................................................
## sanitizer linker flags depended on
SAN_LDFLAGS_DEPENDED_ON = -lrt -ldl -lrt -lpthread -lm

## address sanitizer linker flags
SAN_LDFLAGS=$(findstring sanitize=address, $(CFLAGS))
ifeq ($(SAN_LDFLAGS), sanitize=address)
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
LDFLAGS += -Wl,--whole-archive
LDFLAGS += -l:libasan.a
LDFLAGS += -Wl,--no-whole-archive
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
endif

完整的例子如下:

这是 makefile。
其中 定义了 4种类型的 Sanitizer CFLAGS。
在使用的时候,只需要设置 SAN_CFLAGS,让其指定使用其中一种就可以了。
LDFLAGS 会在后面自动识别。

#-------------------------------------------------------------------------------
TARGET = a.out
OBJS = main.o
CC = gcc

#-------------------------------------------------------------------------------
# compiler flag
## base linker flag
CFLAGS :=

#...............................................................................
## version compiler flag
VERSION_CFLAGS = -O0 -g3

## sanitizer compiler flags
ASAN_CFLAGS = -fsanitize=address -static-libasan -fno-omit-frame-pointer
LSAN_CFLAGS = -fsanitize=leak -static-liblsan -fno-omit-frame-pointer
TSAN_CFLAGS = -fsanitize=thread -static-libtsan -fno-omit-frame-pointer
UBSAN_CFLAGS = -fsanitize=undefined -static-libubsan -fno-omit-frame-pointer

SAN_CFLAGS = $(ASAN_CFLAGS)

#...............................................................................
# assemble compiler flag
CFLAGS += $(VERSION_CFLAGS)
CFLAGS += $(SAN_CFLAGS)

#-------------------------------------------------------------------------------
# linker flag
## base linker flag
LDFLAGS :=

#...............................................................................
## sanitizer linker flags depended on
SAN_LDFLAGS_DEPENDED_ON = -lrt -ldl -lrt -lpthread -lm

## address sanitizer linker flags
SAN_LDFLAGS=$(findstring sanitize=address, $(CFLAGS))
ifeq ($(SAN_LDFLAGS), sanitize=address)
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
LDFLAGS += -Wl,--whole-archive
LDFLAGS += -l:libasan.a
LDFLAGS += -Wl,--no-whole-archive
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
endif

## link sanitizer linker flags
SAN_LDFLAGS=$(findstring sanitize=leak, $(CFLAGS))
ifeq ($(SAN_LDFLAGS), sanitize=leak)
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
LDFLAGS += -Wl,--whole-archive
LDFLAGS += -l:liblsan.a
LDFLAGS += -Wl,--no-whole-archive
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
endif

## thread sanitizer linker flags
SAN_LDFLAGS=$(findstring sanitize=thread, $(CFLAGS))
ifeq ($(SAN_LDFLAGS), sanitize=thread)
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
LDFLAGS += -Wl,--whole-archive
LDFLAGS += -l:libtsan.a
LDFLAGS += -Wl,--no-whole-archive
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
endif

## undefined sanitizer linker flags
SAN_LDFLAGS=$(findstring sanitize=undefined, $(CFLAGS))
ifeq ($(SAN_LDFLAGS), sanitize=undefined)
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
LDFLAGS += -Wl,--whole-archive
LDFLAGS += -l:libubsan.a
LDFLAGS += -Wl,--no-whole-archive
LDFLAGS += $(SAN_LDFLAGS_DEPENDED_ON)
endif

#-------------------------------------------------------------------------------
all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^

%.o: %.c
	$(CC) $(CFLAGS) -c $<

#-------------------------------------------------------------------------------
clean:
	rm -f $(TARGET)
	rm -f $(OBJS)

用于测试的C代码如下。
其中是带有内存泄漏的。用于触发 Address Sanitizer 报警。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

int
main(int argc, char **argv)
{
	int *a;
	a = malloc(sizeof(int));

	return 0;
}

例子的输出:

./a.out
>	=================================================================
>	==1433==ERROR: LeakSanitizer: detected memory leaks
>
>	Direct leak of 4 byte(s) in 1 object(s) allocated from:
>		#0 0x4c35f0 in __interceptor_malloc (/home/andrew/Desktop/AddressSanitizer/a.out+0x4c35f0)
>		#1 0x501b25 in main /home/andrew/Desktop/AddressSanitizer/main.c:11
>		#2 0x7f5fabc22554 in __libc_start_main (/lib64/libc.so.6+0x22554)
>
>	SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).

可见,Address Sanitizer 检查出了内存泄漏的问题。与我们设想的一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值