内存走漏简介

不合法内存拜访过错很快就会露出出来,但不同的是,内存走漏过错有时即使长期运转也不会露出出来,这就导致内存走漏过错是不易被发现的。内存走漏可能并不严峻,乃至无法经过正常办法检测到。在现代操作体系中,应用程序运用的内存在应用程序终止时由操作体体系一开释,这意味着只运转很短时刻的程序中的内存走漏可能不会被注意到并且很少是严峻的。

可是内存走漏带来的损害是不可忽视的,内存走漏会导致体系可用内存逐渐减少从而直接下降计算机的功用,极端状况下,当体系的可用内存被耗尽,此刻体系全部或部分中止工作,体系无法启动新的应用程序,体系可能因为颤动而大大减慢,这种状况通常只可以经过重启体系才可以让体系恢复。

lsan 简介

lsan 是一个运转时内存走漏检测器,它可以与 asan 结合运用以一起取得检测内存拜访过错和内存走漏的能力,它也可以单独运用。lsan 是在进程结束时才开始走漏检测,因而它简直不会下降程序的功用。


敞开 lsan

lsan 支撑两种运转形式,这两种运转形式下,lsan 的敞开办法不同。

和 asan 一起运转

经过运转时标识detect_leaks来敞开,asan 支撑如下两种办法来传递运转时标志:

  1. 环境变量ASAN_OPTIONS:
ASAN_OPTIONS=detect_leaks=1
  1. 函数__asan_default_options
const char*__asan_default_options() { return "detect_leaks=1"; }

完好比方拜见:

github.com/dengking/sa…

lsan 在 x86_64 Linux 的 asan 版别中默认启用。

Stand-alone mode

对功用要求高的场景可能无法承受因为 asan 而引进的功用损耗,此刻可以只敞开 lsan。

关于 cmake,办法如下:

set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=leak")set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=leak")set(CMAKE_LINKER_FLAGS_DEBUG"${CMAKE_LINKER_FLAGS_DEBUG}-fsanitize=leak")

Run-time flags

和 asan 相同,lsan 也支撑经过 run-time flag 来对它的行为进行调整。

Sanitizers 系列之 leak sanitizer 介绍

设置 flag 的两种办法

lsan 支撑如下两种办法来传递 run-time flags,工程师可以依据实际状况选取合适的办法。

  • LSAN_OPTIONS

    LSAN_OPTIONS是环境变量,在不同的 OS 中,设置的办法不同。

  • __lsan_default_options函数

    运用该函数来将 run-time flags 嵌入代码中,比方:

    github.com/dengking/sa…

查看完好的 run-time flag

和 asan 相同,lsan 也支撑经过help=1来查看完好的 run-time flag,完好比方拜见:

github.com/dengking/sa…

Suppression file

可以经过传入 suppression file 来指示 lsan 疏忽某些走漏。suppression file 的每行有必要包含一个 suppression rule,suppression rule 的格式为:

leak:<pattern>

在发现走漏后,lsan 会将该走漏的 stack trace 进行符号化,然后与进行子字符串匹配,假如函数名、源文件名或二进制文件名匹配,那么这个走漏陈述将被制止。下面是一个比方:

$ cat suppr.txt # This is a known leak.leak:FooBar$ cat lsan-suppressed.cc #include <stdlib.h>
void FooBar() {  malloc(7);}
void Baz() {  malloc(5);}
int main() {  FooBar();  Baz();  return 0;}$ clang++ lsan-suppressed.cc -fsanitize=address$ ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=suppressions=suppr.txt ./a.out
===================================================================26475==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 5 byte(s) in 1 object(s) allocated from:    #0 0x44f2de in malloc /usr/home/hacker/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:74    #1 0x464e86 in Baz() (/usr/home/hacker/a.out+0x464e86)    #2 0x464fb4 in main (/usr/home/hacker/a.out+0x464fb4)    #3 0x7f7e760b476c in __libc_start_main /build/buildd/eglibc-2.15/csu/libc-start.c:226
-----------------------------------------------------Suppressions used:[design document](AddressSanitizerLeakSanitizerDesignDocument)  count      bytes template      1          7 FooBar-----------------------------------------------------
SUMMARY: AddressSanitizer: 5 byte(s) leaked in 1 allocation(s).

上述比方源自:

github.com/google/sani…

支撑正则表达式语法,比方特别符号^和$匹配字符串的最初和结束。


lsan 的原理

官方文档:

github.com/google/sani…

术语阐明

为便于论说遍历,界说如下术语:

Sanitizers 系列之 leak sanitizer 介绍

## **原理概述**

lsan 在进程 exit 的前一刻被触发,它首要暂停进程(”StopTheWorld”),然后扫描进程的memory,lsan将进程的内存分为两大类:

  • heap region
  • live memory

lsan 扫描进程的 live memory 以发现 live pointer,然后校验每个 heap block 是否有 live pointer 指向它,假如没有,那么 lsan 就判定它被走漏了。

具体论说

与 asan 不同,lsan 并不需求编译器对源代码进行转化以刺进检查代码,lsan 由如下两部分组成:

  • leak checking module

  • run-time environment,它包含:

    • memory allocator

    • thread registry

    • interceptors(拦截器)for memory allocation / thread management functions

其间,run-time environment 是大多数 sanitizer 工具同享的通用组件,因为 lsan 和 asan 的完成并不冲突,因而 lsan 可以在 asan 之上运转。

在方针进程结束之前,leak checking module 都处于非活动状况,它在进程 exit 的前一刻被触发,它会中止方针进程的履行,然后检查方针进程的内存走漏。在具体完成上,lsan 会以进程的办法运转,它运用 ptrace 附加到方针进程。

关于 ptrace,拜见:

stackoverflow.com/questions/2…

en.wikipedia.org/wiki/Ptrace


live memory

live memory 有必要至少包含以下内容:

  • global variables
  • stacks of running threads
  • general-purpose registers of running threads
  • ELF thread-local storage and POSIX thread-specific data

lsan 首要在 live memory 中查找 live pointer:它扫描上述内存以查找指向 heap block 指针的字节形式,lsan 将内部指针与指向 heap block 最初的指针视为相同。关于有 live pointer 指向的 heap block,lsan 认为是可拜访的,它们的内容也被视为“live memory”,因而 lsan 也需求扫描它们的内容以发现 live pointer,明显这个进程十分类似于求解闭包。经过这种办法,lsan 可以发现了从“live memory”中可拜访的一切 heap block。lsan 会在 heap block 的元数据中设置一个标志来符号这些可拜访的 heap block。然后 lsan 遍历一切现有的 heap block 并将无法拜访的 heap block 陈述为走漏,为了便于排查问题,在陈述走漏的一起 lsan 会将这些走漏的 heap block 的动态分配函数履行进程的 stack trace 一并输出。

lsan 的另一个有用的功用是可以区别直接走漏的 heap block(无法从任何地方拜访)和直接走漏的 heap block(可从其他走漏的 heap block 拜访)。这是经过对符号为走漏的 heap block 运用上述相同的算法来完成的。

怎么取得 live memory

live memory 的获取依赖于 OS 供给 system call,关于此在

(github.com/google/sani…

Sanitizers 系列之 leak sanitizer 介绍

## **False negative**

lsan 算法是存在 false negative 的,在笔者的 GitHub 库房

(github.com/dengking/sa… false-negative

(github.com/dengking/sa… lsan 无法检测出走漏、少检测出走漏的比方,下面结合一些典型的比方进行阐明。

有 live pointer 指向的 heap region

按照 lsan 的原理,它会将没有 live pointer 指向的 heap region 符号为走漏;关于有 live pointer 指向的 heap region,假如在 process 退出的时分,它依然没有被开释,lsan 并不会将它符号为走漏,这种状况是否算是走漏其实很难界定,因为 OS 会在进程退出的时分,一致收回进程占用的资源,所以即使工程师没有开释,大多数状况下是没有问题的,可是假如程序的正确性依赖于某个 heap object 的 destructor 的履行,那么这种状况下应用程序可能是过错的。别的从严厉的工程实践来说,工程师应该保证程序的完全正确,应该对资源管理完全担任,所以本文会将这种状况界说为走漏,将这种状况视为 lsan 的 False negative。实际上,这种状况,现在基本上是没有工具可以检测出来的。

下面的比方展示了这种状况:

Sanitizers 系列之 leak sanitizer 介绍

最最典型的是:

github.com/dengking/sa…

完好代码如下:

#include <stdlib.h>
void *global;
int main(){    global = malloc(7);    return 0;}

global变量是一个效果域为全局的指针,明显它所指向的 heap region 没有被开释,上述程序 lsan 并不会报走漏,可是实际上global所指向的 heap region 被走漏了,仅就这个程序而言,这个走漏是不会带来过错的。

std::vector的一些比方

下面的这个比方是源自:

github.com/google/sani…

#include <vector>std::vector<int *> *global;int main(int argc, char **argv){    global = new std::vector<int *>;    global->push_back(new int[10]);    global->push_back(new int[20]);    global->push_back(new int[30]);    global->push_back(new int[40]);    global->pop_back(); // The last element leaks now.    return 0;}

完好代码拜见:github.com/dengking/sa…

严厉来说,上述程序实际存在 5 处走漏(5处 new),可是按照 lsan 的算法,只是注释标示的句子引进了一处走漏。从github.com/google/sani…

中的内容可知,这种走漏的检测是有赖于对std::vector进行特别完成的,否则因为std::vector运用 lazy-deallocate 技能‍(github.com/dengking/sa… vector 的 heap 中删去,这样 lsan 会认为它依然是 live pointer。

除了上述比方,std::vector的 false negative 比方还包含:

Sanitizers 系列之 leak sanitizer 介绍

总结

从现在的验证来看,lsan 存在着一些 false negative。

编译器支撑状况

‍下面是 clang 文档(clang.llvm.org/docs/LeakSa… 中给出的 LLVM clang 的支撑状况,本文不在赘述。需求强调的是: Apple clang 不支撑 lsan,在 macOS 上 一般运用 instrument 来检测走漏。除此之外,MSVC 也不支撑 lsan,下面会进行具体阐明。

Windows 不支撑 lsan

Windows 尚不支撑 lsan,具体原因如下:

经过前面的介绍可知,lsan 需求可以在进程退出或其他时刻点中止进程以扫描 live pointer,posix 完成运用 ptrace, Windows 暂不支撑 ptrace,所以无法完成 lsan。

更多内容,拜见:github.com/google/sani…


lsan 的 example code

相较于 asan,lsan 的功用比较单一,lsan 的功用会集在对内存走漏的检测。在

github.com/dengking/sa…

github.com/dengking/sa…

github.com/dengking/sa…

中整理了多个比方,这些比方涵盖了 C、C++,涵盖了 local object、global object,涵盖了 OOP,涵盖了一些典型的 STL container 等领域,旨在帮助读者快速上手。