目录


ASAN 基础原理

ASAN 全称 Address Sanitizer, 其包含了 Address Sanitizer、Memory Sanitizer 和 Leak Sanitizer 都是最初由 Google 开发的,用于运行时检测 C/C++ 程序中的内存错误和多线程 data race的。ASAN 主要用于运行时检测程序中的内存错误,另外 ASAN 除了可以发现堆上的内存越界 (Out-Of-Range), 还可以检查栈和全局变量的越界,最后 ASAN 还可以检查内存泄露. ASAN 弥补了现有一些工具的很多不足,代表了先进生产力的发展方向, 比如它们都采用了 CTI(CompileTime Instrumentation) 技术,即在编译时进行代码插入,运行速度快,比传统的 Valgrind 等工具速度上要快一个数量级, 另外它们的输出信息都非常详细,方便快速地定位问题. ASAN 由编译器的插桩模块和替换 malloc/free 函数的运行库组成,ASAN 可以探测如下类型的错误:

  • Use after free (为悬浮指针赋值)
  • Heap buffer overflow (堆溢出)
  • Stack buffer overflow (栈溢出)
  • Global buffer overflow (全局缓冲区溢出)
  • Use after return (通过返回值访问局部变量的内存)
  • Use after scope (访问已经释放的局部变量的内存)
  • Initialization order bugs (使用未初始化的内存)
  • Memory leaks (内存泄露)

上图是一个简单的堆溢出案例,可以看到 array[] 数组一共包含了 10 个程序,但函数对 array[10] 进行了赋值,此时已经超出了堆栈的范围,如果程序运行可能会触发错误。那么接下来讲解一下如果使用 GUN GCC 的 ASAN 工具在程序运行时找出并分析堆栈泄露的问题。GCC-4.8 之后就默认支持 ASAN 工具, 可以在编译 C/C++ 源码时带上指定的参数即可,开发者也可以通过源码编译 GCC 时打开 ASAN 相关的配置:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O1 -g
#CFLAGS += -fsanitize=memory

GUN GCC 源码编译的时候添加上诉的编译参数,ASAN 工具的插桩模块会向源码中添加指定的代码,这些代码就是 ASAN 运行的基础代码,另外 ASAN 工具的运行需要动态运行库 libasan, 因此开发者需要根据不同的发行版安装 libasan 库,libasan 库会在程序运行的时候动态替换 malloc/free 函数,以此对程序的内存进行监听。最后可以查看 ASAN 工具运行时候的效果:

程序 BiscuitOS-ASAN-default 运行的时候,ASAN 工具检测到有栈溢出,那么 ASAN 在进程堆移除的时候终止了运行,然后抛出调试信息,首先红色字符串描述内存错误的类型,然后基于错误类型给出了发生错误的虚拟内存地址。ASAN 同样也会打印处当前进程的堆栈调用信息,另外 ASAN 的 SUMMAY 字段会给出具体发生内存错误的地址。接下来是 ASAN 本身的一部分调试信息。以上便是 ASAN 工具分析内存问题的流程,那么接下来开发者可以基于本文了解更多关于 ASAN 的信息和实践内容.


ASAN 基础使用手册

GUN GCC-4.8 之后的版本就默认支持 ASAN 工具,可以在编译 C/C++ 程序时使用指定的 CFLAGS 参数就可以在运行时使用 ASAN 工具,本节重点介绍在不同的环境下如何使用 ASAN 工具。本节在 BiscuitOS 上实践进行讲解,首先准备所需的环境,如果没有安装 GCC,请使用如下命令:

# Running on Ubuntu
sudo apt-get install -y gcc
gcc --version

如果开发者的 GUN GCC 版本小于 4.8,那么需要额外源码编译 ASAN 版的 GUN GCC, 请参考如下章节:

源码编译 ASAN 版 GUN GCC

由于 ASAN 运行时需要使用 libasan 库动态替换 malloc/free 函数,那么需要确保开发环境中已经安装 libasan 库,如果没有可以参考如下命令:

# Running on Ubuntu
sudo apt-get install -y libasan

GUN GCC 和 libasan 准备好之后,开发者可以部署实践所需的源码,在 BiscuitOS 中,源码的部署可以参考:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] GCC Address Sanitizer Tools (ASAN)  --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-default

BiscuitOS 独立应用程序实践攻略

BiscuitOS-ASAN-default 实践源码 Gitee 链接

案例代码很简单,在 main() 函数的局部堆栈创建了一个 int 数组 array,其包含 10 个成员。程序运行之后会在 main 函数的堆栈中分配 10 个 int 长度的内存用于作为 array[] 数组,这个数组也就是堆栈缓冲区。程序接着将 array[10] 的值赋值为 0x20, 由于 array[] 数组只有 10 个成员,分别是 array[0] 到 array[9], 那么显然 array[10] 已经超出了堆栈缓冲区的范围,那么称这种内存错误为堆栈缓存区溢出,或者是堆栈溢出 stack overflow. 这里虽然只是使用了一个简单的例子进行说明,可以一眼看出问题的所在,但是开发者在开发复杂程序的过程中,想通过眼里查找隐藏很深的堆栈溢出,那么是一件很棘手的事,这个时候 ASAN 工具就派上了用场。由于 ASAN 工具的插桩模块是在程序编译的时候进行插入,那么需要在编译程序时添加如下编译选项:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O1 -g
#CFLAGS += -fsanitize=memory

‘-fsanitize=leak’ 选项用于跟踪程序内存泄露,’-fsanitize=address’ 选项可以跟踪堆栈缓冲区溢出,另外加上几个调试使用的选项,以便使用 GDB 或者 OBJDUMP 工具获得更多的调试信息。接下来基于 BiscuitOS 编译架构对源码进行编译,编译成功之后会生成如下文件:

编译完成之后会生成两个版本的文件,其中一个是带 ASAN 工具的版本,另外一个是不带 ASAN 的原始版本, BiscuitOS-ASAN-default 目录下各文件的含义如下:

  • main.c 源码文件
  • Makefile 编译文件
  • BiscuitOS-ASAN-default-Prep.i 带 ASAN 工具的预处理文件
  • BiscuitOS-ASAN-default-ASM.s 带 ASAN 工具的汇编文件
  • BiscuitOS-ASAN-default-obj.o 带 ASAN 工具的中间文件
  • BiscuitOS-ASAN-default-Object.obs 带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-default 带 ASAN 工具的可执行二进制文件
  • BiscuitOS-ASAN-default-target.obs BiscuitOS-ASAN-default 反汇编文件
  • BiscuitOS-ASAN-default-Prep-NOASAN.i 不带 ASAN 预处理文件
  • BiscuitOS-ASAN-default-ASM-NOASAN.s 不带 ASAN 汇编文件
  • BiscuitOS-ASAN-default-obj-NOASAN.o 不带 ASAN 中间文件
  • BiscuitOS-ASAN-default-Object-NOASAN.obs 不带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-default-NOASAN 不带 ASAN 的可执行二进制文件
  • BiscuitOS-ASAN-default-target-NOASAN.obs 不带 ASAN 可执行文件的反汇编文件

由于 BiscuitOS-ASAN-default-NOASAN 可执行文件不带 ASAN 工具,那么其运行时直接导致进程 Abort 退出,而 BiscuitOS-ASAN-default 可执行文件带 ASAN 工具,并在程序运行发生内存问题时直接停止运行,并打印相应的信息,其信息相应的含义如下:

ASAN 工具打印的第一部分信息如上图,红色字符串打印了第一次出现内存错误的位置,其中 “AddressSanitizer” 字段指明了是 ASAN 工具集的 AddressSanitizer 工具发现了内存问题。接着 “stack-buffer-overflow” 字段指明了发生内存问题的类型,这里是堆栈缓冲区溢出导致的内存问题。”address 0x7ffe8272ff78” 字段指明了发生内存问题的虚拟内存地址,”pc 0x5616d02daa0e bp 0x7ffe8272ff20 sp 0x7ffw8272ff10” 字段则指明了发生内存问题是进程的 PC 寄存器、BP 寄存器以及 SP 寄存器的值. 接下来蓝色字段 “WRITE of size 4 at 0x7ffe8272ff78 thread T0” 指明了发生内存问题是进程的动作,这里可以看到发生内存问题时线程 T0 正在向虚拟地址 0x7ffe8272ff78 写入一个 4 字节的值,这个正好对应了源码 “array[10] = 0x20” 的字段。最后一部分内容就是发生内存问题是进程的调用栈信息,可以看到进程 BiscuitOS-ASAN-default 从其 0x849 处的 _start 开始运行,然后调用 libc 的 __libc_start_main 接口,最后就是运行到 main.c 的第 19 行处发生了内存问题。通过上面的信息基本可以定位了发生内存问题的位置,那么接下来继续分析其他信息:

ASAN 工具打印的第二部分信息如上图,首先绿色字符串打印发生内存问题的虚拟地址的位置,可以从图中看出该虚拟内存位于 T0 线程的堆栈偏移 72 字节处,接下来的 ‘#0’ 表示堆栈的位置,可以看出位于 main.c 文件的 16 行,此处正好是 array[] 数组定义的位置,接下来就是具体堆栈的信息,可以看出 array[] 数组在堆栈的范围是 “[32,72)”, 接着绿色字符串指出了方式内存问题的操作是内存访问了堆栈 72 字节处,但这里已经导致了 array[] 数组的越界。接下来 ASAN 工具打印了 HINT 信息,该信息是 ASAN 的一些建议。最后一部分信息 “SUMMARY” 字符串是 ASAN 对这次内存问题的总结, “AddressSanitizer” 指明 ASAN 的 Address Sanitizer 工具发现的内存问题,内存问题类型是 stack buffer overflow 即栈缓存溢出,最后将发生栈溢出的位置标记出来,此时为 main.c 文件的 19 行,即 “array[10] = 0x20” 代码行。通过上面的信息可以准确定位内存问题的位置,那么接下来就是 ASAN 内部调试使用的信息字段:

根据 ASAN 的实现原理,ASAN 会从进程的地址空间申请一部分内存作为 Shadow Memory, 这部分 Shadow Memory 用于表示进程跟踪内存的使用情况,这里不对 ASAN 实现原理进行简介,如果想了解其实现原理,开发者可以参考《ASAN 实现原理分析》. ASAN 此时打印第三部分内容就是关于 Shadow Memory 的,ASAN 将出现内存问题的虚拟内存在 Shadow Memory 中打印出来,例如 0x7ffe8272ff78 的标记信息位于 Shadow Memory 区域的 0x1000504ddfef 处,此时标志的值为 f2,那么此时 ASAN 打印了 “Shadow bytes legend”, 可以看出 f2 对应的内存错误信息是 “Stack mid redzone”. 至此一次 ASAN 的使用到此为止,接下来开发者可以在本文多个章节的提示下可以对 ASAN 工具进一步研究.


ASAN 解析悬浮指针/野指针案例

悬浮指针指的是声明了但没有赋值的指针,它指向内存中的随机位置,悬浮指针的危害很大,它会将进程的内存进行破坏,给进程带来未知的问题。避免悬浮指针的一个方式就是在声明之后将悬浮指针设置为 NULL,这样可以使用 if 语句判断出来。另外与悬浮指针相类似的就是野指针,野指针不是 NULL 指针,而是指向 “垃圾” 内存的指针,所谓 “垃圾” 内存就是已经释放的内存。野指针的形成有两种途径: 第一种途径是指针没有初始化. 任何指针变量刚创建时不会自动设置为 NULL 指针,它的缺省值是随机的,指向未知的位置,所以指针变量在创建的同时应当被初始化,要么将指针设置为 NULL,要么将指针指向合法的内存. 第二种途径是指针被 free 或者 delete 之后,没有设置为 NULL, 让程序误认为指针是合法指针。free 和 delete 只是把指针指向的内存释放掉,但没有把指针本身释放掉,因此指针如果没有设置为 NULL 之后会形成野指针。

ASAN 工具可以检测 C/C++ 程序中使用悬浮指针和野指针的情况,ASAN 将这类型的内存问题归类为 Heap use after free, 也就是使用了已经释放的堆内存。接下来使用一个案例进行讲解, BiscuitOS 部署逻辑如下:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Heap use after free (C Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-heap-use-after-free-default

BiscuitOS 独立应用程序实践攻略

BiscuitOS-ASAN-heap-use-after-free-default 实践源码 Gitee 链接

案例代码很简单,定义了 func() 函数,函数内部定义了一个指针 p,并初始化为 NULL,然后申请一块内存,并将指针 p 指向新内存,接着有调用 free() 函数回收了这段内存,最后函数 func() 返回了指针 p 的值。main() 函数定义了一个变量 p,并调用 func() 函数, 并将 func() 函数的返回值作为变量 p 的初始值。从代码逻辑可以知道,在 25 行处,p 指向的内存已经变成 “垃圾” 内存,这个时候再从 “垃圾” 内存上读取内容,那么这些内存都是无效的。如果这种内存错误发生在比较复杂的场景中,那么无法通过判断 func() 的返回值来确认内容是否有效,这个棘手问题就需要 ASAN 工具进行排查。由于 ASAN 工具的插桩模块是在程序编译的时候进行插入,那么需要在编译程序时添加如下编译选项:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O1 -g
#CFLAGS += -fsanitize=memory

‘-fsanitize=leak’ 选项用于跟踪程序内存泄露,’-fsanitize=address’ 选项可以跟踪堆栈缓冲区溢出,另外加上几个调试使用的选项,以便使用 GDB 或者 OBJDUMP 工具获得更多的调试信息。接下来基于 BiscuitOS 编译架构对源码进行编译,编译成功之后会生成如下文件:

编译完成之后会生成两个版本的文件,其中一个是带 ASAN 工具的版本,另外一个是不带 ASAN 的原始版本, BiscuitOS-ASAN-heap-use-after-free-default 目录下各文件的含义如下:

  • main.c 源码文件
  • Makefile 编译文件
  • BiscuitOS-ASAN-XX-Prep.i 带 ASAN 工具的预处理文件
  • BiscuitOS-ASAN-XX-ASM.s 带 ASAN 工具的汇编文件
  • BiscuitOS-ASAN-XX-obj.o 带 ASAN 工具的中间文件
  • BiscuitOS-ASAN-XX-Object.obs 带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX 带 ASAN 工具的可执行二进制文件
  • BiscuitOS-ASAN-XX-target.obs BiscuitOS-ASAN-XX 反汇编文件
  • BiscuitOS-ASAN-XX-Prep-NOASAN.i 不带 ASAN 预处理文件
  • BiscuitOS-ASAN-XX-ASM-NOASAN.s 不带 ASAN 汇编文件
  • BiscuitOS-ASAN-XX-obj-NOASAN.o 不带 ASAN 中间文件
  • BiscuitOS-ASAN-XX-Object-NOASAN.obs 不带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX-NOASAN 不带 ASAN 的可执行二进制文件
  • BiscuitOS-ASAN-XX-target-NOASAN.obs 不带 ASAN 可执行文件的反汇编文件

由于 BiscuitOS-ASAN-heap-use-after-free-NOASAN 可执行文件不带 ASAN 工具,那么其运行时系统并未发现有野指针的存在,那么程序并没有报错。相反 由于 BiscuitOS-ASAN-heap-use-after-free 可执行文件带了 ASAN 工具,那么程序运行的时候,ASAN 工具发现了野指针,因此 ASAN 工具中断的程序的执行,并打印相应的信息。其信息具体含义如下图:

ASAN 工具打印的第一部分信息如上图,红色字符串打印了第一次出现内存错误的位置,其中 “AddressSanitizer” 字段指明了是 ASAN 工具集的 AddressSanitizer 工具发现了内存问题。接着 “heap-use-after-free” 字段指明了发生内存问题的类型,这里是悬浮制作或者野指针导致的内存问题。”address 0x602000000010” 字段指明了发生内存问题的虚拟内存地址,”pc 0x563d840238cf bp 0x7ffc7e7b34e0 sp 0x7ffc7e7b34d0” 字段则指明了发生内存问题是进程的 PC 寄存器、BP 寄存器以及 SP 寄存器的值. 接下来蓝色字段 “READ of size 4 at 0x602000000010 thread T0” 指明了发生内存问题时进程的动作,这里可以看到发生内存问题时线程 T0 正在向读取虚拟地址 0x602000000010 处一个 4 字节的值,这个正好对应了源码 25 行处。最后一部分内容就是发生内存问题是进程的调用栈信息,可以看到进程 BiscuitOS-ASAN-heap-use-after-free-default 从其 0x7a9 处的 _start 开始运行,然后调用 libc 的 __libc_start_main 接口,接着 main.c 文件 30 行处调用 func() 函数,最后就是运行到 main.c 的第 25 行处发生了内存问题。通过上面的信息基本可以定位了发生内存问题的位置,那么接下来继续分析其他信息:

ASAN 工具打印的第二部分信息如上图,首先绿色字符串打印了发生内存问题的堆信息,其在堆中的范围是 0x602000000010 到 0x602000000014。接着第一个调用栈表明了发生内存问题堆的释放过程,可以看出 main() 函数调用 func() 函数,并且在 main.c 文件的 22 行将堆内存进行回收,这个时候这部分堆内存是 “垃圾” 内存, 最后 libasan 的 __interceptor_free() 函数接管了 free() 函数,并监听 “垃圾” 内存. 第二个函数调用栈是堆内存的分配过程,可以看出 main() 函数调用 func() 函数,并在 main.c 文件的 20 行处分配堆内存,并使用 __interceptor_malloc() 函数接管了 malloc() 函数,对堆内存进行监听. ASAN 工具第三部分信息是对这个内存问题的总结,其表明 AddressSanitizer 工具在 main.c 文件的 25 行处发现了 heap-use-after-free 类型的内存错误。

根据 ASAN 的实现原理,ASAN 会从进程的地址空间申请一部分内存作为 Shadow Memory, 这部分 Shadow Memory 用于表示进程跟踪内存的使用情况,这里不对 ASAN 实现原理进行简介,如果想了解其实现原理,开发者可以参考《ASAN 实现原理分析》. ASAN 此时打印第三部分内容就是关于 Shadow Memory 的,ASAN 将出现内存问题的虚拟内存在 Shadow Memory 中打印出来,例如 0x602000000010 的标记信息位于 Shadow Memory 区域的 0x0c047fff8002 处,此时标志的值为 fd,那么此时 ASAN 打印了 “Shadow bytes legend”, 可以看出 fd 对应的内存错误信息是 “Free heap region”. 至此一次 ASAN 的使用到此为止, 案例只给出一个简单的例子,更多的例子可以参考:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Heap use after free (C++ Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-heap-use-after-free-CPP-default

BiscuitOS-ASAN-heap-use-after-free-default-CPP 实践源码 Gitee 链接

上图案例是一个简单的 C++ 程序,其 new 新建了一个数组对象,接着又 delete 这个数组对象,最后在 22 行还使用这个数组对象,因此造成了 heap-use-after-free 的内存问题.


ASAN 解析堆溢出案例

进程的地址空间被分作堆、栈、MMAP 区域、代码段以及数据段等多个区域,进程可以从不同的区域获得虚拟内存,其中从堆上获得的内存称为堆内存。进程的堆一般位于进程的数据段或者 BSS 段之后,其向高地址生长。进程通过 malloc() 等函数分配的小粒度内存块基本来自堆内存。堆内存溢出表示进程访问内存时超出了堆的访问范围,那么称这类型的内存问题为堆缓冲区溢出。一般在简单的程序开发中容易发现该类型的问题,但在大型项目中要发现这类型的问题特别困难,首先函数调用时返回的堆内容不是 NULL 或者关键值,那么就不能通过 if 语句进行判断。为了更好的发现问题,那么可以使用 GUN GCC 的 ASAN 工具进行检测。ASAN 工具可以检测 C/C++ 程序中堆缓冲溢出的情况,ASAN 将这类型的内存问题归类为 Heap buffer overflow。接下来使用一个案例进行讲解, BiscuitOS 部署逻辑如下:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Heap buffer overflow (C Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-heap-buffer-overflow-default

BiscuitOS 独立应用程序实践攻略

BiscuitOS-ASAN-heap-buffer-overflow-default 实践源码 Gitee 链接

案例代码很简单,在 main() 函数内部定义了一个 int 类型的指针 p,然后使用 malloc() 函数从进程的堆上分配了 8 个 int 长度的内存,接着函数在 23 行通过越界的方式给 p[8] 赋值,正常的程序无法察觉这个错误,因为 malloc() 函数在分配堆内存的时候都会按 8 字节或者 16、32 字节方式对齐,这样就导致 malloc() 函数实际分配的内存会大于原始内存,那么进程对这部分多余内存的使用并不会被系统察觉有什么异样,但就是这样的操作存在隐患。函数接着在 26 行调用 free() 函数释放了这段堆内存,并在 28 行返回了 p[8] 的值。由于 ASAN 工具的插桩模块是在程序编译的时候进行插入,那么需要在编译程序时添加如下编译选项:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O1 -g
#CFLAGS += -fsanitize=memory

‘-fsanitize=leak’ 选项用于跟踪程序内存泄露,’-fsanitize=address’ 选项可以跟踪堆栈缓冲区溢出,另外加上几个调试使用的选项,以便使用 GDB 或者 OBJDUMP 工具获得更多的调试信息。接下来基于 BiscuitOS 编译架构对源码进行编译,编译成功之后会生成如下文件:

编译完成之后会生成两个版本的文件,其中一个是带 ASAN 工具的版本,另外一个是不带 ASAN 的原始版本, BiscuitOS-ASAN-heap-buffer-overflow-default 目录下各文件的含义如下:

  • main.c 源码文件
  • Makefile 编译文件
  • BiscuitOS-ASAN-XX-Prep.i 带 ASAN 工具的预处理文件
  • BiscuitOS-ASAN-XX-ASM.s 带 ASAN 工具的汇编文件
  • BiscuitOS-ASAN-XX-obj.o 带 ASAN 工具的中间文件
  • BiscuitOS-ASAN-XX-Object.obs 带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX 带 ASAN 工具的可执行二进制文件
  • BiscuitOS-ASAN-XX-target.obs BiscuitOS-ASAN-XX 反汇编文件
  • BiscuitOS-ASAN-XX-Prep-NOASAN.i 不带 ASAN 预处理文件
  • BiscuitOS-ASAN-XX-ASM-NOASAN.s 不带 ASAN 汇编文件
  • BiscuitOS-ASAN-XX-obj-NOASAN.o 不带 ASAN 中间文件
  • BiscuitOS-ASAN-XX-Object-NOASAN.obs 不带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX-NOASAN 不带 ASAN 的可执行二进制文件
  • BiscuitOS-ASAN-XX-target-NOASAN.obs 不带 ASAN 可执行文件的反汇编文件

由于 BiscuitOS-ASAN-heap-buffer-overflow-NOASAN 可执行文件不带 ASAN 工具,那么其运行时系统并未发现堆栈缓冲区溢出问题,那么程序并没有报错。相反由于 BiscuitOS-ASAN-heap-buffer-overflow-default 可执行文件带了 ASAN 工具,那么程序运行的时候,ASAN 工具发现了堆缓冲区溢出问题,因此 ASAN 工具中断的程序的执行,并打印相应的信息。其信息具体含义如下图:

ASAN 工具打印的第一部分信息如上图,红色字符串打印了第一次出现内存错误的位置,其中 “AddressSanitizer” 字段指明了是 ASAN 工具集的 AddressSanitizer 工具发现了内存问题。接着 “heap-buffer-overflow” 字段指明了发生内存问题的类型,这里是堆缓冲区溢出导致的内存问题。”address 0x603000000030” 字段指明了发生内存问题的虚拟内存地址,”pc 0x55ba895538d1 bp 0x7ffc6e0f9640 sp 0x7ffc6e0f9630” 字段则指明了发生内存问题是进程的 PC 寄存器、BP 寄存器以及 SP 寄存器的值. 接下来蓝色字段 “READ of size 4 at 0x602000000030 thread T0” 指明了发生内存问题时进程的动作,这里可以看到发生内存问题时线程 T0 正在向读取虚拟地址 0x602000000030 处一个 4 字节的值,这个正好对应了源码 28 行处。最后一部分内容就是发生内存问题是进程的调用栈信息,可以看到进程 BiscuitOS-ASAN-heap-buffer-overflow-default 从其 0x7a9 处的 _start 开始运行,然后调用 libc 的 __libc_start_main 接口,最后运行到 main.c 的第 28 行处发生了内存问题。通过上面的信息基本可以定位了发生内存问题的位置,那么接下来继续分析其他信息:

ASAN 工具打印的第二部分信息如上图,首先绿色字符串打印了发生内存问题的堆信息,其在堆中的范围是 0x602000000010 到 0x602000000030。接着第一个调用栈表明了发生内存问题堆的释放过程,可以看出在 main.c 文件的 26 行将堆内存进行回收,这个时候这部分堆内存是 “垃圾” 内存, 最后 libasan 的 __interceptor_free() 函数接管了 free() 函数,并监听 “垃圾” 内存. 第二个函数调用栈是堆内存的分配过程,可以看出 main.c 文件的 20 行处分配堆内存,并使用 __interceptor_malloc() 函数接管了 malloc() 函数,对堆内存进行监听. ASAN 工具第三部分信息是对这个内存问题的总结,其表明 AddressSanitizer 工具在 main.c 文件的 28 行处发现了 heap-buffer-overflow 类型的内存错误.

根据 ASAN 的实现原理,ASAN 会从进程的地址空间申请一部分内存作为 Shadow Memory, 这部分 Shadow Memory 用于表示进程跟踪内存的使用情况,这里不对 ASAN 实现原理进行简介,如果想了解其实现原理,开发者可以参考《ASAN 实现原理分析》. ASAN 此时打印第三部分内容就是关于 Shadow Memory 的,ASAN 将出现内存问题的虚拟内存在 Shadow Memory 中打印出来,例如 0x602000000030 的标记信息位于 Shadow Memory 区域的 0x0c067fff8000 处,此时标志的值为 fa,那么此时 ASAN 打印了 “Shadow bytes legend”, 可以看出 fd 对应的内存错误信息是 “Heap left redzone”. 至此一次 ASAN 的使用到此为止, 案例只给出一个简单的例子,更多的例子可以参考:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Heap buffer overflow (C++ Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-heap-buffer-overflow-CPP-default

BiscuitOS-ASAN-heap-buffer-overflow-default-CPP 实践源码 Gitee 链接

上图案例是一个简单的 C++ 程序,其 new 新建了一个数组对象,接着在 20 行处越界方式向 array[8] 写入值,又 delete 这个数组对象,最后在 25 行还使用这个数组对象,因此造成了 heap-buffer-overflow 的内存问题.


ASAN 解析栈溢出案例

进程的地址空间被分作堆、栈、MMAP 区域、代码段以及数据段等多个区域,进程可以从不同的区域获得虚拟内存,其中从栈上获得的内存称为栈内存。进程的栈位于地址空间的顶部,一般情况下向下生长。进程定义的局部变量均来自堆栈内存。栈内存溢出表示进程访问内存时超出了栈的访问范围,那么称这类型的内存问题为栈缓冲区溢出。一般在简单的程序开发中容易发现该类型的问题,但在大型项目中要发现这类型的问题特别困难,首先函数的堆栈大于变量的大小,进程可以在非法的情况下使用越界的堆栈,但程序认为其位于堆栈内部,因此不会判定为堆栈溢出。为了更好的发现问题,那么可以使用 GUN GCC 的 ASAN 工具进行检测。ASAN 工具可以检测 C/C++ 程序中栈缓冲区溢出的情况,ASAN 将这类型的内存问题归类为 Stack buffer overflow。接下来使用一个案例进行讲解, BiscuitOS 部署逻辑如下:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Stack buffer overflow (C Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-stack-buffer-overflow-default

BiscuitOS 独立应用程序实践攻略

BiscuitOS-ASAN-stack-buffer-overflow-default 实践源码 Gitee 链接

案例代码很简单,在 main() 函数内定义了一个局部数组 array[], 其包含了 10 个成员,接着在 19 行处对 array[10] 成员进行写操作,这样将会导致 array[] 数组溢出,函数最后返回 0. 在简单的程序中这种错误很容易发现,但在大型复杂程序中,这种问题很难被发现,因此可以使用 ASAN 工具对堆栈缓冲区溢出问题进行检查。由于 ASAN 工具的插桩模块是在程序编译的时候进行插入,那么需要在编译程序时添加如下编译选项:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O1 -g
#CFLAGS += -fsanitize=memory

‘-fsanitize=leak’ 选项用于跟踪程序内存泄露,’-fsanitize=address’ 选项可以跟踪堆栈缓冲区溢出,另外加上几个调试使用的选项,以便使用 GDB 或者 OBJDUMP 工具获得更多的调试信息。接下来基于 BiscuitOS 编译架构对源码进行编译,编译成功之后会生成如下文件:

编译完成之后会生成两个版本的文件,其中一个是带 ASAN 工具的版本,另外一个是不带 ASAN 的原始版本, BiscuitOS-ASAN-stack-buffer-overflow-default 目录下各文件的含义如下:

  • main.c 源码文件
  • Makefile 编译文件
  • BiscuitOS-ASAN-XX-Prep.i 带 ASAN 工具的预处理文件
  • BiscuitOS-ASAN-XX-ASM.s 带 ASAN 工具的汇编文件
  • BiscuitOS-ASAN-XX-obj.o 带 ASAN 工具的中间文件
  • BiscuitOS-ASAN-XX-Object.obs 带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX 带 ASAN 工具的可执行二进制文件
  • BiscuitOS-ASAN-XX-target.obs BiscuitOS-ASAN-XX 反汇编文件
  • BiscuitOS-ASAN-XX-Prep-NOASAN.i 不带 ASAN 预处理文件
  • BiscuitOS-ASAN-XX-ASM-NOASAN.s 不带 ASAN 汇编文件
  • BiscuitOS-ASAN-XX-obj-NOASAN.o 不带 ASAN 中间文件
  • BiscuitOS-ASAN-XX-Object-NOASAN.obs 不带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX-NOASAN 不带 ASAN 的可执行二进制文件
  • BiscuitOS-ASAN-XX-target-NOASAN.obs 不带 ASAN 可执行文件的反汇编文件

由于 BiscuitOS-ASAN-stack-buffer-overflow-NOASAN 可执行文件不带 ASAN 工具,那么其运行时系统只是发现堆栈出问题了,但不知道堆栈怎么出问题了。相反由于 BiscuitOS-ASAN-stack-buffer-overflow-default 可执行文件带了 ASAN 工具,那么程序运行的时候,ASAN 工具发现了栈缓冲区溢出问题,因此 ASAN 工具中断的程序的执行,并打印相应的信息。其信息具体含义如下图:

ASAN 工具打印的第一部分信息如上图,红色字符串打印了第一次出现内存错误的位置,其中 “AddressSanitizer” 字段指明了是 ASAN 工具集的 AddressSanitizer 工具发现了内存问题。接着 “stack-buffer-overflow” 字段指明了发生内存问题的类型,这里是栈缓冲区溢出导致的内存问题。”address 0x7ffc5810cc58” 字段指明了发生内存问题的虚拟内存地址,”pc 0x56443c39ea0e bp 0x7ffc5810cc00 sp 0x7ffc5810cbf0” 字段则指明了发生内存问题是进程的 PC 寄存器、BP 寄存器以及 SP 寄存器的值. 接下来蓝色字段 “WRITE of size 4 at 0x7ffc5810cc58 thread T0” 指明了发生内存问题时进程的动作,这里可以看到发生内存问题时线程 T0 正在向虚拟地址 0x7ffc5810cc58 处写入一个 4 字节的值,这个正好对应了源码 20 行处。最后一部分内容就是发生内存问题是进程的调用栈信息,可以看到进程 BiscuitOS-ASAN-stack-buffer-overflow-default 从其 0x7a9 处的 _start 开始运行,然后调用 libc 的 __libc_start_main 接口,最后运行到 main.c 的第 20 行处发生了内存问题。通过上面的信息基本可以定位了发生内存问题的位置,那么接下来继续分析其他信息:

ASAN 工具打印的第二部分信息如上图,首先绿色字符串打印了发生内存问题的栈信息,信息指明错误虚拟地址位于 T0 线程的堆栈 72 字节处,其堆缓冲区位于 main.c 的 16 行处,此处正好都是 array[] 数组定义的位置. ASAN 工具第三部分信息是对这个内存问题的总结,其表明 AddressSanitizer 工具在 main.c 文件的 20 行处发现了 stack-buffer-overflow 类型的内存错误.

根据 ASAN 的实现原理,ASAN 会从进程的地址空间申请一部分内存作为 Shadow Memory, 这部分 Shadow Memory 用于表示进程跟踪内存的使用情况,这里不对 ASAN 实现原理进行简介,如果想了解其实现原理,开发者可以参考《ASAN 实现原理分析》. ASAN 此时打印第三部分内容就是关于 Shadow Memory 的,ASAN 将出现内存问题的虚拟内存在 Shadow Memory 中打印出来,例如 0x7ffc5810cc58 的标记信息位于 Shadow Memory 区域的 0x10000b01998b 处,此时标志的值为 f2,那么此时 ASAN 打印了 “Shadow bytes legend”, 可以看出 f2 对应的内存错误信息是 “Stack mid redzone”. 至此一次 ASAN 的使用到此为止, 案例只给出一个简单的例子,更多的例子可以参考:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Stack buffer overflow (C++ Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-stack-buffer-overflow-CPP-default

BiscuitOS-ASAN-stack-buffer-overflow-default-CPP 实践源码 Gitee 链接

上图案例是一个简单的 C++ 程序,其在 main() 函数内部创建了局部变量 array[] 数组,数组的长度只有 10,然而 19 行位置将 0x88 写入了 array[10], 此时已经越界了,因此 ASAN 工具可以抓到 stack-buffer-overflow 内存错误.


ASAN 解析全局缓冲区溢出案例

进程的地址空间被分作堆、栈、MMAP 区域、代码段以及数据段等多个区域,进程可以从不同的区域获得虚拟内存,其中全局缓冲区位于数据段或者 BSS 段上的内存。进程的数据段/BSS 段用于存储进程的全局数据,其中全局缓冲区常见的类型就是全局数组。全局缓冲区内存溢出表示进程访问全局数组时超出了数组的访问范围,那么称这类型的内存问题为全局缓冲区溢出。一般在简单的程序开发中容易发现该类型的问题,但在大型项目中要发现这类型的问题特别困难。为了更好的发现问题,那么可以使用 GUN GCC 的 ASAN 工具进行检测。ASAN 工具可以检测 C/C++ 程序中全局缓冲区溢出的情况,ASAN 将这类型的内存问题归类为 Global buffer overflow。接下来使用一个案例进行讲解, BiscuitOS 部署逻辑如下:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Global buffer overflow (C Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-global-buffer-overflow-default

BiscuitOS 独立应用程序实践攻略

BiscuitOS-ASAN-global-buffer-overflow-default 实践源码 Gitee 链接

案例代码很简单,在 main.c 文件中定义了一个全局数组 array[], 其包含了 10 个成员,接着在 20 行处对 array[10] 成员进行写操作,这样将会导致 array[] 数组溢出,函数最后返回 0. 在简单的程序中这种错误很容易发现,但在大型复杂程序中,这种问题很难被发现,因此可以使用 ASAN 工具对堆栈缓冲区溢出问题进行检查。由于 ASAN 工具的插桩模块是在程序编译的时候进行插入,那么需要在编译程序时添加如下编译选项:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O1 -g
#CFLAGS += -fsanitize=memory

‘-fsanitize=leak’ 选项用于跟踪程序内存泄露,’-fsanitize=address’ 选项可以跟踪堆栈缓冲区溢出,另外加上几个调试使用的选项,以便使用 GDB 或者 OBJDUMP 工具获得更多的调试信息。接下来基于 BiscuitOS 编译架构对源码进行编译,编译成功之后会生成如下文件:

编译完成之后会生成两个版本的文件,其中一个是带 ASAN 工具的版本,另外一个是不带 ASAN 的原始版本, BiscuitOS-ASAN-global-buffer-overflow-default 目录下各文件的含义如下:

  • main.c 源码文件
  • Makefile 编译文件
  • BiscuitOS-ASAN-XX-Prep.i 带 ASAN 工具的预处理文件
  • BiscuitOS-ASAN-XX-ASM.s 带 ASAN 工具的汇编文件
  • BiscuitOS-ASAN-XX-obj.o 带 ASAN 工具的中间文件
  • BiscuitOS-ASAN-XX-Object.obs 带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX 带 ASAN 工具的可执行二进制文件
  • BiscuitOS-ASAN-XX-target.obs BiscuitOS-ASAN-XX 反汇编文件
  • BiscuitOS-ASAN-XX-Prep-NOASAN.i 不带 ASAN 预处理文件
  • BiscuitOS-ASAN-XX-ASM-NOASAN.s 不带 ASAN 汇编文件
  • BiscuitOS-ASAN-XX-obj-NOASAN.o 不带 ASAN 中间文件
  • BiscuitOS-ASAN-XX-Object-NOASAN.obs 不带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX-NOASAN 不带 ASAN 的可执行二进制文件
  • BiscuitOS-ASAN-XX-target-NOASAN.obs 不带 ASAN 可执行文件的反汇编文件

由于 BiscuitOS-ASAN-global-buffer-overflow-NOASAN 可执行文件不带 ASAN 工具,那么程序运行的时候,系统并未发现任何异样。相反由于 BiscuitOS-ASAN-global-buffer-overflow-default 可执行文件带了 ASAN 工具,那么程序运行的时候,ASAN 工具发现了全局缓冲区 array[] 数组溢出问题,因此 ASAN 工具中断的程序的执行,并打印相应的信息。其信息具体含义如下图:

ASAN 工具打印的第一部分信息如上图,红色字符串打印了第一次出现内存错误的位置,其中 “AddressSanitizer” 字段指明了是 ASAN 工具集的 AddressSanitizer 工具发现了内存问题。接着 “global-buffer-overflow” 字段指明了发生内存问题的类型,这里是全局缓冲区溢出导致的内存问题。”address 0x55735f46c0c8” 字段指明了发生内存问题的虚拟内存地址,”pc 0x55735f26b990 bp 0x7fffea8f5380 sp 0x7fffea8f5370” 字段则指明了发生内存问题是进程的 PC 寄存器、BP 寄存器以及 SP 寄存器的值. 接下来蓝色字段 “WRITE of size 4 at 0x55735f46c0c8 thread T0” 指明了发生内存问题时进程的动作,这里可以看到发生内存问题时线程 T0 正在向虚拟地址 0x55735f46c0c8 处写入一个 4 字节的值,这个正好对应了源码 20 行处。最后一部分内容就是发生内存问题是进程的调用栈信息,可以看到进程 BiscuitOS-ASAN-global-buffer-overflow-default 从其 0x879 处的 _start 开始运行,然后调用 libc 的 __libc_start_main 接口,最后运行到 main.c 的第 20 行处发生了内存问题。通过上面的信息基本可以定位了发生内存问题的位置,那么接下来继续分析其他信息:

ASAN 工具打印的第二部分信息如上图,首先绿色字符串打印了发生内存问题的栈信息,信息指明错误虚拟地址位于全局变量 array 偏移 40 字节处,其全局缓冲区位于 main.c 的 15 行处,此处正好都是 array[] 数组定义的位置. ASAN 工具第三部分信息是对这个内存问题的总结,其表明 AddressSanitizer 工具在 main.c 文件的 20 行处发现了 global-buffer-overflow 类型的内存错误.

根据 ASAN 的实现原理,ASAN 会从进程的地址空间申请一部分内存作为 Shadow Memory, 这部分 Shadow Memory 用于表示进程跟踪内存的使用情况,这里不对 ASAN 实现原理进行简介,如果想了解其实现原理,开发者可以参考《ASAN 实现原理分析》. ASAN 此时打印第三部分内容就是关于 Shadow Memory 的,ASAN 将出现内存问题的虚拟内存在 Shadow Memory 中打印出来,例如 0x55735f46c0c8 的标记信息位于 Shadow Memory 区域的 0x0aaeebe85810 处,此时标志的值为 f9,那么此时 ASAN 打印了 “Shadow bytes legend”, 可以看出 f2 对应的内存错误信息是 “Global redzone”. 至此一次 ASAN 的使用到此为止, 案例只给出一个简单的例子,更多的例子可以参考:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Global buffer overflow (C++ Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-global-buffer-overflow-CPP-default

BiscuitOS-ASAN-global-buffer-overflow-default-CPP 实践源码 Gitee 链接

上图案例是一个简单的 C++ 程序,其在 main.c 文件内创建了全局变量 array[] 数组,数组的长度只有 10,然而 19 行位置将 0x88 写入了 array[10], 此时已经越界了,因此 ASAN 工具可以抓到 global-buffer-overflow 内存错误.


ASAN 解析超范围引用局部内存案例

进程的地址空间被分作堆、栈、MMAP 区域、代码段以及数据段等多个区域,进程可以从不同的区域获得虚拟内存,其中局部变量的作用域只在函数内部有效。如果在函数外部访问局部变量的内存,那么这将会引起为止的内存错误,但这种问题进程运行的时候并不认为这是错误的,因此此时堆栈在进程的地址空间上,并且堆栈一直保持可读写的状态,那么对局部变量的非法访问也不会被发现。一般在简单的程序开发中容易发现该类型的问题,但在大型项目中要发现这类型的问题特别困难。为了更好的发现问题,那么可以使用 GUN GCC 的 ASAN 工具进行检测。ASAN 工具可以检测 C/C++ 程序中对局部内存超作用域访问的情况,ASAN 将这类型的内存问题归类为 Stack use after scope。接下来使用一个案例进行讲解, BiscuitOS 部署逻辑如下:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Stack use after scope (C Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-stack-use-after-scope-default

BiscuitOS 独立应用程序实践攻略

BiscuitOS-ASAN-stack-use-after-scope 实践源码 Gitee 链接

案例代码很简单,在 main.c 文件中定义了一个全局 int 类型指针 ptr, 接着定义了一个函数 func(), 在 func() 函数内部定义了一个局部数组 array[], 其包含 10 个成员,函数在 21 行处将 ptr 指向了 array[] 数组的第 6 个成员,接着 main.c 文件的 main() 函数内部,函数在 27 行调用了 func() 函数,并在 29 行处返回 ptr 指向的内存。在简单的程序中这种错误很容易发现,但在大型复杂程序中,这种问题很难被发现,因此可以使用 ASAN 工具对超范围访问局部内存问题进行检查。由于 ASAN 工具的插桩模块是在程序编译的时候进行插入,那么需要在编译程序时添加如下编译选项:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O1 -g
#CFLAGS += -fsanitize=memory

‘-fsanitize=leak’ 选项用于跟踪程序内存泄露,’-fsanitize=address’ 选项可以跟踪堆栈缓冲区溢出,另外加上几个调试使用的选项,以便使用 GDB 或者 OBJDUMP 工具获得更多的调试信息。接下来基于 BiscuitOS 编译架构对源码进行编译,编译成功之后会生成如下文件:

编译完成之后会生成两个版本的文件,其中一个是带 ASAN 工具的版本,另外一个是不带 ASAN 的原始版本, BiscuitOS-ASAN-stack-use-after-scope-default 目录下各文件的含义如下:

  • main.c 源码文件
  • Makefile 编译文件
  • BiscuitOS-ASAN-XX-Prep.i 带 ASAN 工具的预处理文件
  • BiscuitOS-ASAN-XX-ASM.s 带 ASAN 工具的汇编文件
  • BiscuitOS-ASAN-XX-obj.o 带 ASAN 工具的中间文件
  • BiscuitOS-ASAN-XX-Object.obs 带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX 带 ASAN 工具的可执行二进制文件
  • BiscuitOS-ASAN-XX-target.obs BiscuitOS-ASAN-XX 反汇编文件
  • BiscuitOS-ASAN-XX-Prep-NOASAN.i 不带 ASAN 预处理文件
  • BiscuitOS-ASAN-XX-ASM-NOASAN.s 不带 ASAN 汇编文件
  • BiscuitOS-ASAN-XX-obj-NOASAN.o 不带 ASAN 中间文件
  • BiscuitOS-ASAN-XX-Object-NOASAN.obs 不带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX-NOASAN 不带 ASAN 的可执行二进制文件
  • BiscuitOS-ASAN-XX-target-NOASAN.obs 不带 ASAN 可执行文件的反汇编文件

由于 BiscuitOS-ASAN-stack-use-after-scope-default-NOASAN 可执行文件不带 ASAN 工具,那么程序运行的时候,系统并未发现任何异样。相反由于 BiscuitOS-ASAN-stack-use-after-scope-default 可执行文件带了 ASAN 工具,那么程序运行的时候,ASAN 工具发现了局部变量 array[] 数组超范围访问问题,因此 ASAN 工具中断的程序的执行,并打印相应的信息。其信息具体含义如下图:

ASAN 工具打印的第一部分信息如上图,红色字符串打印了第一次出现内存错误的位置,其中 “AddressSanitizer” 字段指明了是 ASAN 工具集的 AddressSanitizer 工具发现了内存问题。接着 “stack-use-after-scope” 字段指明了发生内存问题的类型,这里是对局部变量的超范围访问导致的内存问题。”0x7ffeab030028” 字段指明了发生内存问题的虚拟内存地址,”pc 0x55e8131fbb01 bp 0x7ffeab02ffe0 sp 0x7ffeab02ffd0” 字段则指明了发生内存问题是进程的 PC 寄存器、BP 寄存器以及 SP 寄存器的值. 接下来蓝色字段 “READ of size 4 at 0x7ffeab030028 thread T0” 指明了发生内存问题时进程的动作,这里可以看到发生内存问题时线程 T0 正在从虚拟地址 0x7ffeab030028 处读取一个 4 字节的值,这个正好对应了源码 29 行处。最后一部分内容就是发生内存问题是进程的调用栈信息,可以看到进程 BiscuitOS-ASAN-stack-use-after-scope-default 从其 0x839 处的 _start 开始运行,然后调用 libc 的 __libc_start_main 接口,最后运行到 main.c 的第 29 行处发生了内存问题。通过上面的信息基本可以定位了发生内存问题的位置,那么接下来继续分析其他信息:

ASAN 工具打印的第二部分信息如上图,首先绿色字符串打印了发生内存问题的栈信息,信息指明错误虚拟地址位于局部变量 array[] 上,其位于堆栈偏移 56 字节处, 并指出引起内存错误的是 array[] 数组的第 6 个成员,即其在堆栈的偏移是 56 个字节处. ASAN 工具第三部分信息是对这个内存问题的总结,其表明 AddressSanitizer 工具在 main.c 文件的 29 行处发现了 stack-use-after-scope 类型的内存错误.

根据 ASAN 的实现原理,ASAN 会从进程的地址空间申请一部分内存作为 Shadow Memory, 这部分 Shadow Memory 用于表示进程跟踪内存的使用情况,这里不对 ASAN 实现原理进行简介,如果想了解其实现原理,开发者可以参考《ASAN 实现原理分析》. ASAN 此时打印第三部分内容就是关于 Shadow Memory 的,ASAN 将出现内存问题的虚拟内存在 Shadow Memory 中打印出来,例如 0x7ffeab030028 的标记信息位于 Shadow Memory 区域的 0x1000555fe000 处,此时标志的值为 f8,那么此时 ASAN 打印了 “Shadow bytes legend”, 可以看出 f8 对应的内存错误信息是 “Stack yuse after scope”. 至此一次 ASAN 的使用到此为止, 案例只给出一个简单的例子,更多的例子可以参考:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Stack use after scope (C++ Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-stack-use-after-scope-CPP-default

BiscuitOS-ASAN-stack-use-after-scope-CPP 实践源码 Gitee 链接

上图案例是一个简单的 C++ 程序,其在 main.c 文件内创建了全局指针 ptr, 然后在 func() 函数内部创建了局部变量 array[] 数组,其包含了 10 个成员,并且将 ptr 指向了 array[6]. 接着在 main() 函数内部调用了 func() 函数,最后返回了 ptr 的值,此时 27 行处 array[] 数组的生命周期已经结束,ptr 的引起已经超出了 array[] 数组的范围,因此 ASAN 工具可以抓到 stack-use-after-scope 内存错误.


ASAN 解析内存泄露案例

进程的地址空间被分作堆、栈、MMAP 区域、代码段以及数据段等多个区域,进程可以通过 malloc 函数从堆或者 MMAP 区域分配虚拟内存,当使用完毕之后通过 free 函数将虚拟内存进行释放,该流程是正常的动态分配内存过程。如果进程只是分配和使用了动态虚拟内存,而没有释放动态分配内存,那么随着程序长时间的运行,系统内存将会被慢慢消耗殆尽,那么称这种行为为内存泄露。一般在简单的程序开发中容易发现该类型的问题,但在大型项目中要发现这类型的问题特别困难。为了更好的发现问题,那么可以使用 GUN GCC 的 ASAN 工具进行检测。ASAN 工具可以检测 C/C++ 程序中内存泄露的情况,ASAN 将这类型的内存问题归类为 。接下来使用一个案例进行讲解, BiscuitOS 部署逻辑如下:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Memory leak (C Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-memory-leak-default

BiscuitOS 独立应用程序实践攻略

BiscuitOS-ASAN-memory-leak 实践源码 Gitee 链接

案例代码很简单,在 main() 函数中定义一个指针,然后为指针分配 100 字节的内存,接着在 20 行处又将指针指向了 0,那么之前分配的内存就泄露了,没有得到回收。在简单的程序中这种错误很容易发现,但在大型复杂程序中,这种问题很难被发现,因此可以使用 ASAN 工具对内存泄露内存问题进行检查。由于 ASAN 工具的插桩模块是在程序编译的时候进行插入,那么需要在编译程序时添加如下编译选项:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O0 -g
#CFLAGS += -fsanitize=memory

‘-fsanitize=leak’ 选项用于跟踪程序内存泄露,’-fsanitize=address’ 选项可以跟踪堆栈缓冲区溢出,另外加上几个调试使用的选项,以便使用 GDB 或者 OBJDUMP 工具获得更多的调试信息。接下来基于 BiscuitOS 编译架构对源码进行编译,编译成功之后会生成如下文件:

编译完成之后会生成两个版本的文件,其中一个是带 ASAN 工具的版本,另外一个是不带 ASAN 的原始版本, BiscuitOS-ASAN-memory-leak-default 目录下各文件的含义如下:

  • main.c 源码文件
  • Makefile 编译文件
  • BiscuitOS-ASAN-XX-Prep.i 带 ASAN 工具的预处理文件
  • BiscuitOS-ASAN-XX-ASM.s 带 ASAN 工具的汇编文件
  • BiscuitOS-ASAN-XX-obj.o 带 ASAN 工具的中间文件
  • BiscuitOS-ASAN-XX-Object.obs 带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX 带 ASAN 工具的可执行二进制文件
  • BiscuitOS-ASAN-XX-target.obs BiscuitOS-ASAN-XX 反汇编文件
  • BiscuitOS-ASAN-XX-Prep-NOASAN.i 不带 ASAN 预处理文件
  • BiscuitOS-ASAN-XX-ASM-NOASAN.s 不带 ASAN 汇编文件
  • BiscuitOS-ASAN-XX-obj-NOASAN.o 不带 ASAN 中间文件
  • BiscuitOS-ASAN-XX-Object-NOASAN.obs 不带 ASAN 中间文件的反汇编文件
  • BiscuitOS-ASAN-XX-NOASAN 不带 ASAN 的可执行二进制文件
  • BiscuitOS-ASAN-XX-target-NOASAN.obs 不带 ASAN 可执行文件的反汇编文件

由于 BiscuitOS-ASAN-memory-leak-default-NOASAN 可执行文件不带 ASAN 工具,那么程序运行的时候,系统并未发现任何异样。相反由于 BiscuitOS-ASAN-memory-leak-default 可执行文件带了 ASAN 工具,那么程序运行的时候,ASAN 工具发现了内存泄露问题,因此 ASAN 工具中断的程序的执行,并打印相应的信息。其信息具体含义如下图:

ASAN 工具打印的第一部分信息如上图,红色字符串打印了问题类型,其中 “LeakSanitizer” 字段指明了是 ASAN 工具集的 LeakSanitizer 工具发现了内存问题。接着 “detected memory leaks” 字段指明了发生内存问题的类型,这里是内存泄露问题。接下来蓝色字段 “Direct leak of 100 byte(s) in 1 object(s) allocated from:” 指明了发生内存泄露信息,这里可以看到进程的一个内存对象泄露了 100 个字节。最后一部分内容就是发生内存问题是进程的调用栈信息,可以看到进程 BiscuitOS-ASAN-stack-use-after-scope-default 从调用 libc 的 __libc_start_main 接口,接着运行到 main.c 的第 18 行处发生了内存泄露。通过上面的信息基本可以定位了发生内存问题的位置。最后一部分信息就是 SUMMARY 总结信息,AddressSanitizer 发现了进程泄露了 100 个字节的内存。至此一次 ASAN 的使用到此为止, 案例只给出一个简单的例子,更多的例子可以参考:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  Memory Sanitizers  --->
        [*] ASAN: Memory leak (C++ Version) --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-ASAN-memory-leak-CPP-default

BiscuitOS-ASAN-memory-leak-CPP 实践源码 Gitee 链接

上图案例是一个简单的 C++ 程序,其在 main() 函数中定义了一个指针,然后通过 new 分配了 100 字节内存,然后在 19 行直接将指针指向了 0,那么原来的 100 字节内存就被泄露了。此时 ASAN 工具可以发现 19 行的内存泄露。


ASAN_OPTIONS: symbolize

“ASAN_OPTIONS=symbolize” 选项用于控制 AddressSanitiers 工具在显示信息时,控制是否显示虚拟地址对应的文件行号信息。”symbolize” 可以设置为 true 或者 false。如果 “ASAN_OPTIONS=symbolize=true” 那么虚拟地址对应的文件行号就会被显示出来; 如果 “ASAN_OPTIONS=symbolize=false” 那么不显示虚拟地址对应的文件行号信息. 开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析悬浮指针/野指针案例

通过对比可以看出,当 “ASAN_OPTIONS=symbolize=false” 时 ASAN 调用栈中的虚拟地址只是显示文件并为显示文件行号,反之 “ASAN_OPTIONS=symbolize=ture” 时 ASAN 则把函数调用栈中的虚拟地址对应的文件行号都显示出来了。


ASAN_OPTIONS: strip_path_prefix

“ASAN_OPTIONS=strip_path_prefix” 选项用于控制 AddressSanitizer 贡献在显示信息时,路径前缀的输出。”strip_path_prefix” 的值是一段路径字符串。当将该字段设置为某个路径之后,那么 AddressSanitizer 工具在输出路径信息时会将 “strip_path_prefix” 中的字符串给移除掉。开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析悬浮指针/野指针案例

通过对比可以看出,当不设置 “ASAN_OPTIONS=strip_path_prefix” 时 ASAN 会显示调用栈中的虚拟地址对应的文件完整路径信息,反之 “ASAN_OPTIONS=strip_path_prefix” 设置为 “/xspace/OpenSource/BiscuitOS/BiscuitOS/output/linux-5.0-x86_64/package/”, 那么 ASAN 在显示调用栈中虚拟地址对应文件路径时,将 “/xspace/OpenSource/BiscuitOS/BiscuitOS/output/linux-5.0-x86_64/package/” 字段移除。


ASAN_OPTIONS: log_path

“ASAN_OPTIONS=log_path” 选项用于控制 AddressSanitizer 将信息输出到指定文件中,文件最终命名会加上进程的 PID 字段,另外如果 log_path 的值为 stderr 或者 stdout 时,ASAN 工具会将日志信息直接写到设备的 stderr 和 stdout 输出通道上。开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析悬浮指针/野指针案例

通过对比可以看出,当设置 “ASAN_OPTIONS=log_path” 时 ASAN 可以将日志信息输出到执行目录,其中支持向 stdout 和 stderr 进行输出, 并且在输出 log 的时候还会在名字上加上进行 PID 字段,对多 log 的抓的场景有很多的帮助。另外 stdout 和 stderr 在 BiscuitOS 都是指向串口的,因此效果一样。


ASAN_OPTIONS: log_exe_name

“ASAN_OPTIONS=log_exe_name” 选项用于控制 AddressSanitizer 在输出日志时将可执行文件名字进行输出,ASAN 将可执行文件名字输出到最后部分. “log_exe_name” 支持 false 和 true 两个值,当 log_exe_name 设置为 true 时 ASAN 才会输出可执行文件的名字。开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析悬浮指针/野指针案例

通过对比可以看出,当不设置 “ASAN_OPTIONS=log_exe_name” 时 ASAN 输出的信息与设置为 false 一致,当 log_exe_name 设置为 true 时,可以看到输出 log 的最后一行多了可执行程序的名字。


ASAN_OPTIONS: verbosity

“ASAN_OPTIONS=verbosity” 选项用于控制 AddressSanitizer 是否输出更多冗余信息,当 verbosity 的值为 0 时,ASAN 输出精简日志信息,当 verbosity 为 1 时输出相对多一些日志信息,当 verbosity 为 2 时输出尽可能多的信息。开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析悬浮指针/野指针案例

通过对比可以看出,当设置 “ASAN_OPTIONS=verbosity” 为 0 时 ASAN 输出的信息与 ASAN 默认输出信息一致,当 verbosity 设置为 1 时,可以看到输出日志多了 ASAN 内部信息,当 verbosity 为 2 时,ASAN 尽可能将所有日志都输出。


ASAN_OPTIONS: detect_leaks

“ASAN_OPTIONS=detect_leaks” 选项用于控制 ASAN 是否启动 LeakSanitizer 工具进行内存泄露检查。该选项可以设置为 true 或 false,当选项设置为 false 是 ASAN 不进行内存泄露检测,当选项设置为 false 时,ASAN 在编译 GCC 不带 -fsanitize=leak 的情况下也可以做内存泄露检查。开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析内存泄露案例

通过对比可以看出,当设置 “ASAN_OPTIONS=detect_leaks=false” 时 ASAN 不对任何内存泄露进行检测。相反就算编译的 GCC 不带 “-fsanitize=leak” 选项时,ASAN 也会进行内存泄露检查.


ASAN_OPTIONS: leak_check_at_exit

“ASAN_OPTIONS=leak_check_at_exit” 选项用于控制 ASAN 是无条件在 ASAN 退出的时候调用 LeakSanitizer 工具进行内存泄露检查,就算 ASAN_OPTIONS 已经包含了 “detect_leaks=false” 的选项或者 “__lsan_do_leak_check()” 函数已经被调用运行,”leak_check_at_exit=true” 选项也会在 ASAN 退出时进行内存检查。该选项可以设置为 true 或 false,当选项设置为 false 是 ASAN 不进行内存泄露检测,当选项设置为 false 时,ASAN 就算 “detect_leaks=false” 的情况也做内存泄露检查。开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析内存泄露案例

通过对比可以看出,当设置 “ASAN_OPTIONS=leak_check_at_exit=false” 时 ASAN 不对任何内存泄露进行检测。另外当 “ASAN_OPTIONS=detect_leaks=false” 的场景下,只要 “leak_check_at_exit=true”, 那么 ASAN 也会做内存泄露检查.


ASAN_OPTIONS: print_summary

“ASAN_OPTIONS=print_summary” 选项用于控制 ASAN 在输出日志的时候是否输出 SUMMARY 字段,该字段用于对问题排查总结。该选项可以取 true 或者 false. 当该选项为 true 时,ASAN 不输出 SUMMARY 字段,反之当该选项为 false 时 ASAN 不输出 SUMMARY 字段. 开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析内存泄露案例

通过对比可以看出,当设置 “ASAN_OPTIONS=print_summary=false” 时 ASAN 不输出 SUMMARY 字段信息, 而 “ASAN_OPTIONS=print_summary=true” 时 ASAN 与默认情况下都输出 SUMMARY 字段信息。


ASAN_OPTIONS: color

“ASAN_OPTIONS=color” 选项用于控制 ASAN 在输出日志的时候是否使用不同的颜色高亮重要信息。该选项可以取 false/auto/always. ASAN 输出日志时默认带颜色高亮,当该选项为 false 时,ASAN 输出信息是不带颜色高亮,反之当该选项为 auto/always 时 ASAN 输出信息是带颜色高亮的. 开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析内存泄露案例

通过对比可以看出,当设置 “ASAN_OPTIONS=color=false” 时 ASAN 输出的日志不带颜色,反之 auto 和 always 以及默认情况下带颜色.


ASAN_OPTIONS: help

“ASAN_OPTIONS=help” 选项用于打印 ASAN_OPTIONS 支持的所有 flags 信息。该选项可以取 false 和 true,只有取 true 的时候才会打印 help 信息. 开发者可以基于下面链接中的实践代码进行验证:

ASAN 解析内存泄露案例

通过对比可以看出,当设置 “ASAN_OPTIONS=help=false” 时 ASAN 输出的 HELP 信息,反之 true 的时候输出所有 HELP 信息.


SanitizerCommonFlags 使用手册

ASAN 支持 Run-time flag 标志合集,ASAN 通过 ASAN_OPTIONS 环境变量进行设置,ASAN 支持的标志集合如上图,BiscuitOS 支持的标志实践入上目录索引. ASAN_OPTIONS 环境变量可以设置为不同的 options,以此控制 ASAN 的行为和功能。ASAN_OPTIONS 格式如下:

ASAN_OPTIONS=option=value ./Executable_file

ASAN_OPTIONS 为环境变量名字,option 为 flag 选线,value 为 option 的值,不同的 option 可设置的值不同,Executable_file 为被检查的可执行程序。ASAN 除了可以通过 ASAN_OPTIONS 环境变量在 Run-time 的时候控制 ASAN 的行为,ASAN 也支持将这些 flags 嵌入到源码中,例如:

正如上图所示,在源文件的 16 到 18 行,使用 __asan_default_options() 函数将内嵌的 option 进行返回即可. 另外,开发者可以使用 “ASAN_OPTIONS=help=true” 的 help 选项查看更多细节选项说明:


AddressSanitizeFlags 使用手册

AddressSanitizFlags on Github Link

ASAN 支持 Run-time flag 标志合集,ASAN 通过 ASAN_OPTIONS 环境变量进行设置,ASAN 支持的标志集合如上图. ASAN_OPTIONS 环境变量可以设置为不同的 options,以此控制 ASAN 的行为和功能。AddressSanitizeFlags 的使用与 SanitizerCommonFlags 一致,其格式如下:

ASAN_OPTIONS=option=value ./Executable_file

ASAN_OPTIONS 为环境变量名字,option 为 flag 选线,value 为 option 的值,不同的 option 可设置的值不同,Executable_file 为被检查的可执行程序。ASAN 除了可以通过 ASAN_OPTIONS 环境变量在 Run-time 的时候控制 ASAN 的行为,ASAN 也支持将这些 flags 嵌入到源码中,例如:

正如上图所示,在源文件的 16 到 18 行,使用 __asan_default_options() 函数将内嵌的 option 进行返回即可. 另外,开发者可以使用 “ASAN_OPTIONS=help=true” 的 help 选项查看更多细节选项说明:


LeakSanitizeFlags 使用手册

LeakSanitizeFlags on Github Link

ASAN 的 LeakSanitizer 工具用于检查内存泄露问题,其也支持 Run-time flag 标志集合,以此控制 ASAN 内存泄露检查过程。ASAN 通过 LSAN_OPTIONS 环境变量进行设置,ASAN 支持 LeakSanitizeFlags 标志集合如上图. LASAN_OPTIONS 环境变量可以设置为不同的 options,以此控制 ASAN 内存泄露检查行为和功能。其使用格式如下:

LSAN_OPTIONS=option=value ./Executable_file

LSAN_OPTIONS 为环境变量名字,option 为 flag 选线,value 为 option 的值,不同的 option 可设置的值不同,Executable_file 为被检查的可执行程序。


ASAN GUN GCC 编译选项研究

GCC 从 4.8 版本之后开始支持 AddressSanitizer, 并从 4.9 版本之后开始支持 LeakSanitizer. ASAN 包括插桩模块和 libasan 运行库,在源码编译阶段 ASAN 的插桩模块会对所有的 memory access 都会检查所对应 shadow memory 的状态,这里被称为静态插桩。另外插桩模块对所有栈上对象和全局对象创建前后的保护区 (Poisoned redzone), 为准备做准备。另外就是 libasan 运行库,其在运行的时候替换了默认路径的 malloc 和 free 等函数,为所有堆对象创建前后保护区,将 free 掉的堆区域隔离一段实践,避免它立即被分配给其他人使用。通过上面的分析,为了让程序使用上 ASAN,那么需要在编译时添加相应的编译选项,那么 GCC 才会调用 ASAN 的插桩模块对源程序进行处理。通常 ASAN 使用的编译选项如下:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O0 -g
#CFLAGS += -fsaniti

在编译的时候加上上面选项,ASAN 在运行时就可以对程序进行 AddressSanitize 和 LeakSanitize. 本节的重点用于介绍 GCC “-fsanitize=” 的多个编译选项对 ASAN 的影响,那么开发者参考下面目录进行查看:


-fsanitize=address

GUN GCC CFLAGS 中添加 “-fsabutize=address” 字段,GCC 将使用 AddressSanitizer 工具在程序运行时快速检查内存错误。AdddressSanitizer 能够检查 out-of-bounds 和 use-after-free 内存访问 Bug,out-of-bound Bug 即内存访问越界或者缓冲区溢出,比如常见的堆缓存溢出、栈溢出以及全局缓冲区溢出。use-after-free Bug 即非法访问已经释放的内存区域,比如常见的悬浮指针和超范围引用局部内存等。另外 “-fsanitize=address” 选项还可以与 ASAN_OPTIONS 的 AddressSanitizeFlags 在 Run-time 时结合使用,以便定制不同的 AddressSanitizer 工具, 更多技术细节可以参考下文。该选项不能与 “-fsanitize=thread” 和 “-fcheck-pointer-bounds” 联合使用.

AddressSanitizeFlags 使用手册


-fsanitize=leak

GUN GCC CFLAGS 中添加 “-fsanitize=leak” 字段,GCC 将使用 LeakSanitizer 工具在程序运行时检查内存泄露问题。该选项对可执行文件的链接其作用,LeakSanitizer 会将远程的 malloc 和 free 等函数替换成成相应的监控函数。LeakSanitizer 运行的时候可以结合 LSAN_OPTIONS 环境变量使用,以便定制不同的内存泄露检测功能,更多技术细节可以参考下文:

LeakSanitizeFlags 使用手册


-fsanitize=kernel-address

GUN GCC CFLAGS 中添加 “-fsantize=kernel-address” 字段,GCC 将使用 AddressSanitizer 工具对 Linux kernel 进行内存错误检测.


-fsanitize=undefined

GUN GCC CFLAGS 中添加 “-fsantize=undefined” 字段, GCC 将启用 UndefinedBehaviorSanitizer 工具,该工具用于快速检测未定义行为。需要对各种计算进行检测,以便在运行时找到未定义的行为。


源码编译 ASAN 版 GUN GCC

GCC 从 4.8 版本之后开始支持 AddressSanitizer, 并从 4.9 版本之后开始支持 LeakSanitizer. 如果开发者所安装的 GCC 还不支持 AddressSanitizer 和 LeakSanitizer, 那么需要源码编译 ASAN 版本的 GCC,开发者可以在 BiscuitOS 上进行 GCC 的源码编译,也可以独立的 GCC 源码编译。无论使用那种方法,其核心原理一致,这里以 BiscuitOS 上 ASAN 版 GCC 源码编译为例进行讲解, BiscuitOS 上的部署如下:

cd BiscuitOS
make menuconfig

[*] Package  --->
    [*]  GUN GCC  --->
        [*] GUN GCC V9.X ASAN on BiscuitOS --->

OUTPUT:
BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-GUN-GCC-V9-ASAN-9.2.0

BiscuitOS 独立应用程序实践攻略

cd BiscuitOS/output/linux-XXX-YYY/package/BiscuitOS-GUN-GCC-V9-ASAN-9.2.0
make download
make tar
make configure
make 
make install
make pack

使用上面的命令便可以在 BiscuitOS 上使用源码变异的 ASAN 版本 GUN GCC。反之如果是独立于 BiscuitOS 进行源码编译,那么首先需要下载 GUN GCC 源码,可以在 GUN GCC Download web 选择指定的版本进行下载,例如这里选在 9.2.0 版本进行源码编译. 待源码下载完毕并解压到指定目录之后,接下来就是配置和编译源码,最后就是安装,具体命令可以参考如下:

wget http://ftp.gnu.org/gnu/gcc/gcc-9.2.0/gcc-9.2.0.tar.xz
tar xf gcc-9.2.0.tar.xz
cd gcc-9.2.0
./contrib/download_prerequisites
cd ..
mkdir Build-GCC
cd Build-GCC
../gcc-9.2.0/configure --enable-checking=release --enable-languages=c,c++ --disable-multilib make -j8
make install

编译可能会花上一些时间,当编译好安装完毕之后,在使用生成的 GCC 编译源码时带上下面的 CFLAGS 参数就可以使用 ASAN 工具集合了:

# CFLAGS for GCC
CFLAGS += -fsanitize=leak
CFLAGS += -fsanitize=address
CFLAGS += -fno-omit-frame-pointer
CFLAGS += -fno-optimize-sibling-calls -O0 -g
#CFLAGS += -fsaniti


附录

BiscuitOS Home

BiscuitOS Blog 2.0

Linux Kernel

Bootlin: Elixir Cross Referencer

捐赠一下吧 🙂

MMU