全面解析記憶體漏失檢測與修復技術

2023-10-18 12:02:06

本文分享自華為雲社群《從源頭解決記憶體漏失問題:全面解析記憶體漏失檢測與修復技術》,作者 Lion Long 。

一、背景:什麼是記憶體漏失檢測?

1.1、記憶體漏失產生原因

記憶體漏失是在沒有自動gc的程式語言裡面經常發生的問題;因為沒有gc,所以分配的記憶體需要程式自己呼叫釋放。其核心是呼叫分配與釋放沒有符合開閉原則,沒有配對,形成了有分配,沒有釋放的指標,從而產生了記憶體漏失。
例如:

void func(size_t s1)
{
    void p1=malloc(s1);
    void p2=malloc(s1);
    // ...
    free(p1);
}

以上程式碼段,分配了兩個s1大小的記憶體塊,由 p1 與 p2 指向。而程式碼塊執行完以後,釋放了 p1,而 p2 沒有釋放。形成了有分配沒有釋放的指標,產生了記憶體漏失。

1.2、 記憶體漏失導致的後果

隨著工程程式碼量越來越多,記憶體漏失的排查就變得極為頭疼,一個程式,其虛擬記憶體一直在增長,無法準確判斷是程式需要還是記憶體漏失。有分配沒釋放,自然使程序堆的記憶體越來越少,直到耗盡。會造成後面的執行程式碼不能成功分配記憶體。甚至使程式奔潰。

1.3、記憶體漏失如何解決?

記憶體漏失是沒有自動 gc 的程式語言所產生的,解決方案一,引入 gc。這是根治記憶體漏失的最好的方案。但是這樣的方案有失去了 c/c++語言的優勢。方案二,當發生記憶體漏失的時候,能夠精準的定位程式碼哪一行所引起的。這也是實現記憶體漏失檢測的核心實現需求。

(1)能夠檢測出來記憶體漏失。

(2)能夠判斷是由程式碼哪一行引起的記憶體漏失。

一個程式,其虛擬記憶體一直在增長,無法準確判斷是程式需要還是記憶體漏失;如果是記憶體漏失也不知道是發生在哪一行程式碼。

二、地址轉換為符號資訊

2.1、addr2line工具

將地址轉換為檔名和行號。

addr2line [-a|--addresses]
          [-b bfdname|--target=bfdname]
          [-C|--demangle[=style]]
          [-e filename|--exe=filename]
          [-f|--functions] [-s|--basename]
          [-i|--inlines]
          [-p|--pretty-print]
          [-j|--section=name]
          [-H|--help] [-V|--version]
          [addr addr ...]

描述:

addr2line將地址轉換為檔名和行號。給定可執行檔案中的地址或可重定位物件部分中的偏移量,它使用偵錯資訊確定與之關聯的檔名和行號。

要使用的可執行或可重定位物件是用-e選項指定的。預設為檔案a.out。要使用的可重定位物件中的節是用-j選項指定的。

addr2line有兩種操作模式。

  • 在第一個命令列中,十六進位制地址在命令列中指定,addr2line顯示每個地址的檔名和行號。
  • 在第二個命令中,addr2line從標準輸入中讀取十六進位制地址,並在標準輸出中列印每個地址的檔名和行號。在這種模式下,addr2line可以在管道中用於轉換動態選擇的地址。

注意:

addr2line是將地址轉換為檔案號,而檔案是儲存在磁碟中的,程式執行的地址是在虛擬記憶體裡面的(程式碼段),在高版本Linux 中可能無法解析出地址在檔案哪個位置。addr2line只能看虛擬區域的地址。

2.2、dladdr1()函數

將地址轉換為符號資訊。函數原型:

#define _GNU_SOURCE
#include <dlfcn.h>

int dladdr(void *addr, Dl_info *info);

int dladdr1(void *addr, Dl_info *info, void **extra_info, int flags);

// Link with -ldl.

描述:

函數dladdr()確定addr中指定的地址是否位於呼叫應用程式載入的一個共用物件中。如果是,則dladdr()返回與addr重疊的共用物件和符號的資訊。此資訊以Dl_info結構返回:

typedef struct {
    const char *dli_fname;  /* Pathname of shared object that contains address */
    void       *dli_fbase;  /* Base address at which shared object is loaded */
    const char *dli_sname;  /* Name of symbol whose definition overlaps addr */
    void       *dli_saddr;  /* Exact address of symbol named in dli_sname */
} Dl_info;

函數dladdr1()類似於dladdr(),但通過引數extra_info返回附加資訊。返回的資訊取決於標誌中指定的值,標誌可以具有以下值之一:

(1)RTLD_DL_LINKMAP。獲取指向匹配檔案的連結對映的指標。extra_info引數指向<link.h>中定義的link_map結構(即struct link_map**)的指標。

struct link_map {
    ElfW(Addr) l_addr;  /* Difference between the address in the ELF file and the address in memory */
    char      *l_name;  /* Absolute pathname where object was found */
    ElfW(Dyn) *l_ld;    /* Dynamic section of the shared object */
    struct link_map *l_next, *l_prev;
                        /* Chain of loaded objects */

    /* Plus additional fields private to the implementation */
};

(2)RTLD_DL_SYMENT。獲取指向匹配符號的ELF符號表條目的指標。extra_info引數是指向符號指標的指標:const ElfW(Sym)**ElfW()宏定義將其引數轉換為適合硬體體系結構的ELF資料型別的名稱。例如,在64位元平臺上,ElfW(Sym)生成資料型別名稱Elf64_Sym,該名稱在<elf.h>中定義:

typedef struct  {
    Elf64_Word    st_name;     /* Symbol name */
    unsigned char st_info;     /* Symbol type and binding */
    unsigned char st_other;    /* Symbol visibility */
    Elf64_Section st_shndx;    /* Section index */
    Elf64_Addr    st_value;    /* Symbol value */
    Elf64_Xword   st_size;     /* Symbol size */
} Elf64_Sym;

封裝:

void * ConvertToElf(void *addr)
{
    Dl_info info;
    struct link_map *link;

    dladdr1(addr, &info, (void **)&link, RTLD_DL_LINKMAP);

    // 偏差糾正
    return (void *)((size_t)addr - link->l_addr);
}

三、記憶體漏失檢測的實現

內 存 洩 漏 是由於 內 存 分 配 與 內 存 釋放, 不 匹 配 所引起 的 。 對 內 存 分 配 函 數malloc/calloc/realloc,以及記憶體釋放 free 進行「劫持」hook。能夠統計出記憶體分配的位置,記憶體釋放的位置,從而判斷是否匹配。

3.1、方式一:使用mtrace

mtrace()和muntrace()函數

mtrace跟蹤紀錄檔。函數原型:

#include <mcheck.h>

void mtrace(void);

void muntrace(void);

描述:

mtrace()函數為記憶體分配函數安裝hook(勾點)函數【malloc()、realloc()、memalign(),free()】。這些hook函數記錄有關記憶體分配和釋放的跟蹤資訊。跟蹤資訊可用於發現記憶體漏失,並嘗試釋放程式中未分配的記憶體。

muntrace()函數禁用mtrace()安裝的hook函數,這樣就不再為記憶體分配函數記錄跟蹤資訊。如果mtrace()沒有成功安裝任何勾點函數,則muntrace()不執行任何操作。

呼叫mtrace()時,它會檢查環境變數MALLOC_TRACE的值,該變數應包含要記錄跟蹤資訊的檔案的路徑名。如果路徑名成功開啟,則其長度將被截斷為零。

如果未設定MALLOC_TRACE,或者它指定的路徑名無效或不可寫,則不會安裝hook函數,並且mtrace()無效。在set-user-ID 和 set-group-ID程式中,MALLOC_TRACE被忽略,mtrace()無效。

setenv()和unsetenv()函數

改變或新增環境變數。函數原型:

#include <stdlib.h>

int setenv(const char *name, const char *value, int overwrite);

int unsetenv(const char *name);

/* 
Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

setenv(), unsetenv():
    _BSD_SOURCE || _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
*/

描述:

如果名稱不存在,setenv()函數會將變數name新增到值為value的環境中。如果環境中確實存在name,且overwrite非零,則其值將更改為value;如果overwrite為零,則name的值不變(setenv()返回成功狀態)。此函數複製name和value指向的字串(與putenv(3)相反)。

unsetenv()函數用於從環境中刪除變數名。如果環境中不存在名稱,則函數成功,環境不變。

返回值:

setenv()函數在成功時返回零,在錯誤時返回-1,並設定errno以指示錯誤的原因。

unsetenv()函數在成功時返回零,在錯誤時返回-1,並設定errno以指示錯誤的原因。

錯誤:

錯誤碼含義
EINVAL name為NULL,指向長度為0的字串,或包含「=」字元。
ENOMEM 記憶體不足,無法向環境中新增新變數。

使用步驟

(1)在呼叫記憶體分配函數之前呼叫mtrace();
(2)在程式結束或者不需要追蹤記憶體漏失的地方呼叫muntrace();
(3)設定環境變數MALLOC_TRACE的值(setenv函數或者export命令);
(4)編譯時要帶上-g引數。
(5)當出現記憶體漏失時,使用addr2line工具定位記憶體漏失的位置。

$ addr2line -f -e memleak -a 0x4006b8

範例中memleak是程式名,0x4006b8是記憶體漏失的地址。

例如:

$ cc -g t_mtrace.c -o t_mtrace
$ export MALLOC_TRACE=/tmp/t
$ ./t_mtrace
$ mtrace ./t_mtrace $MALLOC_TRACE

範例程式碼

#include <stdio.h>
#include <stdlib.h>

#include <mcheck.h>

int main(int argc,char **argv)
{

    setenv("MALLOC_TRACE", "./mem.txt", 1);

    mtrace();

    void *p1 = malloc(10);
    void *p2 = malloc(20);
    void *p3 = malloc(30);

    free(p3);
    free(p2);
    muntrace();

    unsetenv("MALLOC_TRACE");

    return 0;
}

記憶體漏失檢測檔案內容:

$ cat mem.txt 
= Start
@ ./memleak:[0x4006b8] + 0x1886580 0xa
@ ./memleak:[0x4006c6] + 0x18865a0 0x14
@ ./memleak:[0x4006d4] + 0x18865c0 0x1e
@ ./memleak:[0x4006e4] - 0x18865c0
@ ./memleak:[0x4006f0] - 0x18865a0
= End

定位記憶體漏失位置:

$ addr2line -f -e memleak -a 0x4006b8
0x00000000004006b8
main
memleak.c:13

3.2、方式二:採用宏定義

Linux中有兩個宏__FILE____func____LINE__,分別指示當前的檔名、函數名和行號,利用宏定義封裝記憶體分配函數和釋放函數。

#define malloc(size)    _malloc(size,__FILE__,__LINE__)
#define free(p)            _free(p,__FILE__,__LINE__)

自己在_malloc函數和_free函數中呼叫malloc函數和free函數,並且做一些操作。

前提:宏一定要放在記憶體分配之前定義,這樣預編譯階段才會替換malloc為我們自己實現的_malloc_free

範例程式碼:

#include <stdio.h>
#include <stdlib.h>

void *_malloc(size_t size,const char*filename,int line)
{
    void *p = malloc(size);
    printf("[+] %s : %d, %p\n", filename, line,p);
    return p;
}

void _free(void *p, const char*filename, int line)
{
    printf("[-] %s : %d, %p\n", filename, line,p);
    return free(p);
}

#define malloc(size)    _malloc(size,__FILE__,__LINE__)
#define free(p)            _free(p,__FILE__,__LINE__)

int main(int argc,char **argv)
{
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    void *p3 = malloc(30);

    free(p3);
    free(p2);

    return 0;
}

採用宏定義方法的優缺點:

(1)優點,實現簡單。

(2)缺點,只適合單檔案,宏定義要放在檔案的最前面。

使用檔案替換列印:

程式執行時總是列印不必要的資訊即影響效率也不美觀,可以在一個資料夾裡建立、刪除檔案來統計記憶體漏失。

將指標值作為檔名,分配記憶體建立檔案,釋放記憶體刪除檔案,在檔案裡面記錄分配記憶體的檔名和行號。

如果資料夾裡有檔案則存在記憶體漏失,沒有檔案就代表沒有記憶體漏失。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define LEAK_FILE_PATH    "./mem/%p.mem"

void *_malloc(size_t size,const char*filename,int line)
{
    void *p = malloc(size);
    //printf("[+] %s : %d, %p\n", filename, line,p);
    char buff[128] = { 0 };
    sprintf(buff, LEAK_FILE_PATH, p);
    FILE *fp = fopen(buff, "w");
    fprintf(fp, "[+] %s : %d, addr: %p, size: %ld\n", filename, line, p, size);
    fflush(fp);//重新整理資料到檔案中
    fclose(fp);

    return p;
}

void _free(void *p, const char*filename, int line)
{
    //printf("[-] %s : %d, %p\n", filename, line,p);
    char buff[128] = { 0 };
    sprintf(buff, LEAK_FILE_PATH, p);
    if (unlink(buff) < 0)
    {
        printf("double free %p\n", p);
        return;
    }
    return free(p);
}

#define malloc(size)    _malloc(size,__FILE__,__LINE__)
#define free(p)            _free(p,__FILE__,__LINE__)

int main(int argc,char **argv)
{

    void *p1 = malloc(10);
    void *p2 = malloc(20);
    void *p3 = malloc(30);

    free(p3);
    free(p2);

    return 0;
}

注意,工具只能加快分析,不能100%確定記憶體漏失,因為複雜的系統情況比較複雜。

檢測記憶體漏失不是一開始就加上,一般通過」熱更新「的方式在需要的時候開啟,即在組態檔中有一個開啟記憶體漏失檢測的標誌位。只有需要的時候才開啟,這樣不影響程式效率。

3.3、方式三:hook(勾點)

hook使用步驟:

(1)定義函數指標。

typedef void *(*malloc_t)(size_t size);
typedef void(*free_t)(void *p);

malloc_t malloc_f = NULL;
free_t free_f = NULL;

(2)函數實現,函數名與目標函數名一致。

void *malloc(size_t size)
{
// ...
}

void free(void *ptr)
{
// ...
}

(3)初始化hook,呼叫dlsym()。

static init_hook()
{
    if (malloc_f == NULL)
        malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc");

    if (free_f == NULL)
        free_f = (malloc_t)dlsym(RTLD_NEXT, "free");
}

注意:

hook的時候,要考慮其他函數也用到所hook住的函數,比如在printf()函數裡面也呼叫了malloc,那麼就需要防止內部遞迴進入死迴圈。

例如:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <dlfcn.h>

typedef void *(*malloc_t)(size_t size);
typedef void(*free_t)(void *p);

malloc_t malloc_f = NULL;
free_t free_f = NULL;
void *malloc(size_t size)
{
    printf("malloc size: %ld", size);
    return NULL;
}

void free(void *ptr)
{
    printf("free: %p\n",ptr);
}

static int init_hook()
{
    if (malloc_f == NULL)
        malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc");

    if (free_f == NULL)
        free_f = (free_t)dlsym(RTLD_NEXT, "free");

    return 0;
}

int main(int argc,char **argv)
{
    init_hook();
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    void *p3 = malloc(30);

    free(p3);
    free(p2);
    return 0;
}

以上程式碼會出現段錯誤,使用gdb偵錯會發現在malloc函數的printf()呼叫進入了無限遞迴;棧溢位。

解決方案就是新增標誌。

gcc內建功能:void * __builtin_return_address(無符號整數級別)

此函數返回當前函數或其呼叫方之一的返回地址。引數是向上掃描呼叫堆疊的幀數。值 產生當前函數的返回地址,值 生成當前函數呼叫方的返回地址,依此類推。內聯預期行為時,函數返回返回的函數的地址。若要變通解決此問題,請使用函數屬性。

level:

該引數必須是常數整數。

在某些計算機上,可能無法確定除當前功能之外的任何函數的返回地址;在這種情況下,或者當到達堆疊的頂部時,此函數返回未指定的值。此外,可用於確定是否已到達堆疊的頂部。

範例程式碼:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#define LEAK_FILE_PATH    "./mem/%p.mem"

#include <dlfcn.h>

static int enable_malloc_hook = 1;
static int enable_free_hook = 1;

typedef void *(*malloc_t)(size_t size);
typedef void(*free_t)(void *p);

malloc_t malloc_f = NULL;
free_t free_f = NULL;
void *malloc(size_t size)
{
    void *p;
    if (enable_malloc_hook)
    {
        enable_malloc_hook = 0;
        
        p = malloc_f(size);
        printf("malloc size: %ld,p=%p\n", size,p);
        // 獲取上一層呼叫malloc地方的地址,這個地址用於addr2line工具將其轉換為行號
        void *caller = __builtin_return_address(0);

        char buff[128] = { 0 };
        sprintf(buff, LEAK_FILE_PATH, p);
        FILE *fp = fopen(buff, "w");
        fprintf(fp, "[+] %p , addr: %p, size: %ld\n", caller, p, size);
        fflush(fp);//重新整理資料到檔案中
        fclose(fp);

        enable_malloc_hook = 1;
    }
    else
        p = malloc_f(size);
    
    return p;
}

void free(void *p)
{
    if (enable_free_hook)
    {
        enable_free_hook = 0;
        //printf("free: %p\n",p);
        char buff[128] = { 0 };
        sprintf(buff, LEAK_FILE_PATH, p);
        if (unlink(buff) < 0)
        {
            printf("double free %p\n", p);
            //enable_free_hook = 1;
            free_f(p);
            return;
        }
        free_f(p);
        enable_free_hook = 1;
    }
    else
        free_f(p);
}

static int init_hook()
{
    if (malloc_f == NULL)
        malloc_f = (malloc_t)dlsym(RTLD_NEXT, "malloc");

    if (free_f == NULL)
        free_f = (free_t)dlsym(RTLD_NEXT, "free");

    return 0;
}

int main(int argc,char **argv)
{

    init_hook();
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    void *p3 = malloc(30);

    free(p3);
    free(p2);
    
    return 0;
}

通過__builtin_return_address(0)得到的地址需要addr2line工具將其轉換為檔案行號即可定位記憶體漏失的位置。

3.4、方式四:使用__libc_malloc和__libc_free

思路和hook的一樣,因為malloc和free底層呼叫的也是__libc_malloc__libc_free

範例程式碼:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 要記得手動建立一個mem資料夾
#define LEAK_FILE_PATH    "./mem/%p.mem"

extern void *__libc_malloc(size_t size);
extern void __libc_free(void *p);

static int enable_malloc_hook = 1;

void *malloc(size_t size)
{
    void *p;
    if (enable_malloc_hook)
    {
        enable_malloc_hook = 0;

        p = __libc_malloc(size);
        printf("malloc size: %ld,p=%p\n", size, p);
        // 獲取上一層呼叫malloc地方的地址,這個地址用於addr2line工具將其轉換為行號
        void *caller = __builtin_return_address(0);

        char buff[128] = { 0 };
        sprintf(buff, LEAK_FILE_PATH, p);
        FILE *fp = fopen(buff, "w");
        fprintf(fp, "[+] %p , addr: %p, size: %ld\n", caller, p, size);
        fflush(fp);//重新整理資料到檔案中
        fclose(fp);

        enable_malloc_hook = 1;
    }
    else
        p = __libc_malloc(size);

    return p;
}

void free(void *p)
{
    char buff[128] = { 0 };
    sprintf(buff, LEAK_FILE_PATH, p);
    if (unlink(buff) < 0)
    {
        printf("double free %p\n", p);
    }
    __libc_free(p);
}

int main(int argc,char **argv)
{

    void *p1 = malloc(10);
    void *p2 = malloc(20);
    void *p3 = malloc(30);

    free(p3);
    free(p2);

    return 0;
}

3.5、方式五:__malloc_hook(不推薦)

這種方式適用於比較老的Linux 版本,屬於舊版本的API,__malloc_hook是指標的方式,是一個固定的值。本質上也是一種hook技術。

函數原型:

#include <malloc.h>

void *(*__malloc_hook)(size_t size, const void *caller);

void *(*__realloc_hook)(void *ptr, size_t size, const void *caller);

void *(*__memalign_hook)(size_t alignment, size_t size, const void *caller);

void (*__free_hook)(void *ptr, const void *caller);

void (*__malloc_initialize_hook)(void);

void (*__after_morecore_hook)(void);

描述:

GNUC庫允許您通過指定適當的勾點函數來修改malloc、realloc和free的行為。例如,您可以使用這些掛鉤來幫助偵錯使用動態記憶體分配的程式。

變數__malloc_initialize_hook指向在初始化malloc實現時呼叫一次的函數。這是一個弱變數,因此可以在應用程式中使用如下定義覆蓋它:

void(*__malloc_initialize_hook)(void)=my_init_hook();

現在函數my_init_hook()可以初始化所有勾點。

__malloc_hook__realloc_hooks__memalign_hooke__free_hooky指向的四個函數的原型分別與函數malloc、realloc和memalign。

方案:

交換法,自定義函數指標,實現具體函數,將自己實現的函數與系統提供的__malloc_hook交換。

範例程式碼:

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 要記得手動建立一個mem資料夾
#define LEAK_FILE_PATH    "./mem/%p.mem"

#include <malloc.h>

/*
typedef void *(*malloc_t)(size_t size);
typedef void(*free_t)(void *p);

malloc_t malloc_f = NULL;
free_t free_f = NULL;
*/

static int enable_malloc_hook = 1;

static void my_init_hook(void);
static void *my_malloc_hook(size_t, const void *);
static void my_free_hook(void *, const void *);

/* Variables to save original hooks. */
static void *(*old_malloc_hook)(size_t, const void *);
static void(*old_free_hook)(void *, const void *);
/* Override initializing hook from the C library. */
void(*__malloc_initialize_hook) (void) = my_init_hook;

static void
my_init_hook(void)
{
    old_malloc_hook = __malloc_hook;
    __malloc_hook = my_malloc_hook;

    old_free_hook = __free_hook;
    __free_hook = my_free_hook;
}

static void *
my_malloc_hook(size_t size, const void *caller)
{
    void *result;

    /* Restore all old hooks */
    __malloc_hook = old_malloc_hook;

    /* Call recursively */
    //result = malloc(size);

    if (enable_malloc_hook)
    {
        enable_malloc_hook = 0;

        result = malloc(size);
        /* printf() might call malloc(), so protect it too. */
        printf("malloc(%u) called from %p returns %p\n",
            (unsigned int)size, caller, result);

        char buff[128] = { 0 };
        sprintf(buff, LEAK_FILE_PATH, result);

        FILE *fp = fopen(buff, "w");
        fprintf(fp, "[+] %p , addr: %p, size: %ld\n", caller, result, size);
        fflush(fp);//重新整理資料到檔案中
        fclose(fp);

        enable_malloc_hook = 1;
    }
    else
        result = malloc(size);

    /* Save underlying hooks */
    old_malloc_hook = __malloc_hook;
    /* Restore our own hooks */
    __malloc_hook = my_malloc_hook;

    return result;
}

static void my_free_hook(void *ptr, const void *caller)
{
    __free_hook = old_free_hook;

    free(ptr);

    old_free_hook = __free_hook;

    /* printf() might call malloc(), so protect it too. */
    printf("free(%p) called from %p\n",
        ptr, caller);

    char buff[128] = { 0 };
    sprintf(buff, LEAK_FILE_PATH, ptr);
    if (unlink(buff) < 0)
    {
        printf("double free %p\n", ptr);
    }

    /* Restore our own hooks */
    __free_hook = my_free_hook;
}

int main(int argc,char **argv)
{

    my_init_hook();

    void *p1 = malloc(10);
    void *p2 = malloc(20);
    void *p3 = malloc(30);

    free(p3);
    free(p2);

    return 0;
}

編譯的時候會出現警告,系統不推薦使用這樣的方法。

四、完整範例程式碼

程式碼比較長,為了避免篇幅較長,不利於閱讀,這裡沒有貼上。如果需要,可以聯絡博主,或者關注微信公眾號 《Lion 萊恩呀》 獲取。

總結

  • 記憶體漏失檢測的核心是要知道有沒有記憶體漏失已經在哪裡出現的記憶體漏失。
  • 檢測記憶體漏失的方式有:mtrace、hook、宏定義、libc_malloc、__malloc_hook。其中mtrace需要設MALLOC_TRACE環境變數並且需要重啟;宏定義適用於單檔案;__malloc_hook已經淘汰。
  • 在編譯程式時加上-g可以使用addr2line工具定位記憶體漏失在檔案中的位置。
  • 為了提高程式效率,release程式採用「熱更新」的方式在需要的時候設定組態檔標誌符為進行記憶體漏失檢測。

點選關注,第一時間瞭解華為雲新鮮技術~