Linux下内存问题排查利器

2023-02-28 13:55:08 浏览数 (2)

大家好,我是木荣,今天给大家分享一下Linux下如何排除内存泄漏问题。工作中,作为一个程序员,内存问题是我们经常遇到也是容易引起程序崩溃的常见问题,严重的后果会直接导致你的程序宕机从而带来灾难性的后果。

1. 内存泄漏

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

特点

  • 隐蔽性 因为内存泄漏的产生原因是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷
  • 积累性 内存泄漏通常不会直接产生可观察的错误症状,而是逐渐积累,降低系统整体性能,极端的情况下可能使系统崩溃。最直观的问题就是为什么我们的程序开始运行好好的,过段时间就异常退出。

内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于使用错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存未释放而浪费掉。

产生的原因

我们在进行程序开发的过程使用动态存储变量时,不可避免地面对内存管理的问题。程序中动态分配的存储空间,在程序执行完毕后需要进行释放。没有释放动态分配的存储空间而造成内存泄漏,是使用动态存储变量的主要问题。一般情况下,作为开发人员会经常使用系统提供的内存管理基本函数,如malloc、realloc、calloc、free等,完成动态存储变量存储空间的分配和释放。但是,当开发程序中使用动态存储变量较多和频繁使用函数调用时,就会经常发生内存管理错误。

2. 如何排查内存泄漏

我们平时开发过程中不可避免的会遇到内存泄漏问题,这是常见的问题。既然发生了内存泄漏,我们就要排查内存泄漏的问题。想必大家也经常会用到以下排查内存问题的工具,如下:

  • memwatch
  • mtrace
  • dmalloc
  • ccmalloc
  • valgrind
  • debug_new

今天木荣不是介绍上面的排查工具,而是向大家介绍另一个内存泄漏排查工具:AddressSanitizer(ASan)。它支持 Linux、OS、Android等多种平台,不止可以检测内存泄漏,它是一个内存错误检测工具,可以检测很多常见的内存问题。

常见的内存问题检测:

  • 内存泄漏
  • 越界访问
  • 使用了释放的内存

3. AddressSanitizer(ASan)工具

Address Sanitizer(ASan) 是一个快速的内存错误检测工具。它非常快,只拖慢程序两倍左右(比起Valgrind快多了)。它包括一个编译器instrumentation模块和一个提供malloc()/free()替代项的运行时库。从gcc 4.8开始,AddressSanitizer成为gcc的一部分。当然,要获得更好的体验,最好使用4.9及以上版本,因为gcc 4.8的AddressSanitizer还不完善,最大的缺点是没有符号信息。

使用方法:

  • 用-fsanitize=address选项编译和链接你的程序。
  • 用-fno-omit-frame-pointer编译,以得到更容易理解stack trace。
  • 可选择-O1或者更高的优化级别编译

gcc -fsanitize=address -o main -g main.c

内存泄漏

代码语言:javascript复制
#include <stdlib.h>

void Fun()
{
    char *pM = malloc(10);
}

int main(int argc, char *argv[])
{
    Fun();
    return 0;
}

编译输出

内存越界

  • 堆栈内存越界
代码语言:javascript复制
#include <stdlib.h>
#include <string.h>

void Fun()
{
    char *pM = malloc(10);
}

int main(int argc, char *argv[])
{
    //Fun();
    int *array = malloc(10*sizeof(int));
    if(array)
    {
            memset(array, 0, 10*sizeof(int));
    }
    int res = array[10];

    free(array);
    return 0;
}

运行输出

代码语言:javascript复制
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ gcc -fsanitize=address -o main -g main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ls
main  main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ./main
=================================================================
==3234==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x000000400854 bp 0x7ffccc9253d0 sp 0x7ffccc9253c0
READ of size 4 at 0x60400000dff8 thread T0
    #0 0x400853 in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:16
    #1 0x7fafc87a883f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6 0x2083f)
    #2 0x4006f8 in _start (/home/ubuntu/workspace_ex/Linux/ASan/main 0x4006f8)

0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8)
allocated by thread T0 here:
    #0 0x7fafc8bea602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2 0x98602)
    #1 0x4007f7 in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:11
    #2 0x7fafc87a883f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6 0x2083f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ubuntu/workspace_ex/Linux/ASan/main.c:16 main
Shadow bytes around the buggy address:
  0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa]
  0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c40: 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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==3234==ABORTING

  • 全局内存越界
代码语言:javascript复制
#include <stdlib.h>
#include <string.h>

int g_nArray[10] = {0};

void Fun()
{
    char *pM = malloc(10);
}

int main(int argc, char *argv[])
{
    //Fun();

    /*
    int *array = malloc(10*sizeof(int));
    if(array)
    {
            memset(array, 0, 10*sizeof(int));
    }
    int res = array[10];

    free(array);
    */

    int nIndex = g_nArray[10];
    return 0;
}

运行输出

代码语言:javascript复制
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ gcc -fsanitize=address -o main -g main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ls
main  main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ./main 
=================================================================
==4196==ERROR: AddressSanitizer: global-buffer-overflow on address 0x000000601128 at pc 0x00000040083e bp 0x7ffc8a332540 sp 0x7ffc8a332530
READ of size 4 at 0x000000601128 thread T0
    #0 0x40083d in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:26
    #1 0x7fda216a583f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6 0x2083f)
    #2 0x400718 in _start (/home/ubuntu/workspace_ex/Linux/ASan/main 0x400718)

0x000000601128 is located 0 bytes to the right of global variable 'g_nArray' defined in 'main.c:4:5' (0x601100) of size 40
SUMMARY: AddressSanitizer: global-buffer-overflow /home/ubuntu/workspace_ex/Linux/ASan/main.c:26 main
Shadow bytes around the buggy address:
  0x0000800b81d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0000800b81e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0000800b81f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0000800b8200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0000800b8210: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0000800b8220: 00 00 00 00 00[f9]f9 f9 f9 f9 f9 f9 00 00 00 00
  0x0000800b8230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0000800b8240: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0000800b8250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0000800b8260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0000800b8270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==4196==ABORTING
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ 

使用释放的内存

代码语言:javascript复制
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

int g_nArray[10] = {0};
int *p;

void Fun()
{
    char *pM = malloc(10);
}

void Fun1()
{
    int nVar = 0;
    p = &nVar;
}

int main(int argc, char *argv[])
{
    //Fun();

    int *array = malloc(10*sizeof(int));
    if(array)
    {
            memset(array, 0, 10*sizeof(int));
    }
    int res = array[10];

    free(array);

    array[0] = 1;

    return 0;
}

运行输出

代码语言:javascript复制
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ gcc -fsanitize=address -o main -g main.c
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$ ./main
=================================================================
==4954==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000dff8 at pc 0x000000400b68 bp 0x7ffefbe3b170 sp 0x7ffefbe3b160
READ of size 4 at 0x60400000dff8 thread T0
    #0 0x400b67 in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:28
    #1 0x7f1bbb78983f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6 0x2083f)
    #2 0x400928 in _start (/home/ubuntu/workspace_ex/Linux/ASan/main 0x400928)

0x60400000dff8 is located 0 bytes to the right of 40-byte region [0x60400000dfd0,0x60400000dff8)
allocated by thread T0 here:
    #0 0x7f1bbbbcb602 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.2 0x98602)
    #1 0x400b0b in main /home/ubuntu/workspace_ex/Linux/ASan/main.c:23
    #2 0x7f1bbb78983f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6 0x2083f)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ubuntu/workspace_ex/Linux/ASan/main.c:28 main
Shadow bytes around the buggy address:
  0x0c087fff9ba0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bb0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9bd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9be0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c087fff9bf0: fa fa fa fa fa fa fa fa fa fa 00 00 00 00 00[fa]
  0x0c087fff9c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c087fff9c40: 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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==4954==ABORTING
ubuntu@ubuntu:~/workspace_ex/Linux/ASan$

AddressSanitizer能检测的错误类型

错误类型

错误描述

(heap) Use after free

访问堆上已被释放的内存

Heap buffer overflow

堆上缓冲区访问溢出

Stack buffer overflow

栈上缓冲区访问溢出

Global buffer overflow

全局缓冲区访问溢出

Use after return

访问栈上已被释放的内存

Use after scope

栈对象使用超过定义范围

Initialization order bugs

初始化命令错误

Memory leaks

内存泄漏

  • 更多细节请访问官网

具体可以看 google 的官方文档:https://github.com/google/sanitizers/wiki/AddressSanitizer

结束语

ASan是个很好的检测内存问题的工具,不需要配置环境,使用还方便,编译时只需要-fsanitize=address -g 就可以, 运行程序时候可以选择添加对应的 ASAN_OPTIONS 环境变量就可以检测出很多内存问题。如果哪里不清楚可以查看官方说明,欢迎交流学习。

0 人点赞