精华内容
下载资源
问答
  • c/c++的内存管理一直都是程序猿最头痛的事情,内存越界、数组越界、内存泄漏、内存溢出、野指针、空指针..., 随便一个问题都可能让程序崩溃。而且往往问题的源头都比较隐蔽,让人很难排查出问题的根源所在。想要解决...

    c/c++的内存管理一直都是程序猿最头痛的事情,内存越界、数组越界、内存泄漏、内存溢出、野指针、空指针..., 随便一个问题都可能让程序崩溃。而且往往问题的源头都比较隐蔽,让人很难排查出问题的根源所在。

    想要解决这个问题,还得从问题的根源入手。valgrind是一个强大的内存管理工具,常用来检测内存泄漏和内存的非法使用,用好了可以很好的从根源上解决c/c++内存管理的问题。

    1.valgrind的主要功能

    virgrind可以用来检测程序开发中的绝大多数内存,函数、缓存使用、多线程竞争访问内存、堆栈问题,是一个linux下功能非常强大内存检测工具。

    1 valgrind工具

    valgrind-tool= 最常用的选项。运行valgrind中名为toolname的工具。默认memcheck。

    memcheck:这是valgrind应用最广泛的工具,一个重量级的内存检查器,能够发现开发中绝大多数内存错误问题,比如:使用未初始化的内存,使用已经释放了的内存,内存访问越界等。下面将重点介绍此功能。

    callgrind: 主要用来检查程序中函数调用过程中出现的问题。

    cachegrind: 主要用来检查程序中缓存使用出现的问题。

    helgrind: 主要用来检查多线程程序中出现的竞争问题。

    massif: 主要用来检查程序中堆栈使用中出现的问题。

    extension: 可以利用core提供的功能,自己编写特定的内存调试工具。

    2 valgrind memcheck

    2.1memcheck内存检测原理

    valid-value表:

    对于进程的整个地址空间中的每一个字节(byte),都有与之对应的8个bits,对于CPU的每个寄存器,也有一个与之对应的bit向量。这些bits负责记录该字节或者寄存器值是否具有有效的、已经初始化的值。

    valid-Address表:

    对于进程整个地址空间中的一个字节(byte),还有与之对应的1bit,负责记录该地址是否能够被读写。

    内存检测原理:

    当要读写内存中的某个字节时,首先检查这个字节对应的address bit。如果该address bit显示该位置是无效位置,memcheck则报告内存读写错误。valgrind内核相当于一个虚拟的CPU环境,当内存中的某个字节被加载到真实的CPU中时,该字节对应的value bit也被加载到虚拟的CPU环境中,一旦寄存器中的值,被用来产生内存地址,或者该值能够影响程序的输出,则mencheck会检查对应的value bits,如果该值尚未初始化,则会报告使用未初始化内存错误。

    2.2 memcheck内存检测

    2.2.1 准备源码

    创建gdbmem.cpp源码文件,准备待检测的代码如下:

    #include

    #include

    #include

    #include

    #include

    #include

    //memory access overflow

    char* stringcopy(char* psrc)

    {

    int len = strlen(psrc) + 1;

    char* pdst = (char*)malloc(len);//12

    memset(pdst, 0, len * 2);//13

    memcpy(pdst, psrc, len*2);//14

    return pdst;

    }

    //array assess overflow

    void printarray(int arry[], int arrysize)

    {

    int i = 0;

    for(; i < arrysize; i++)

    //for(i = arrysize-1; i >= 0; i--)

    {

    printf("arry[%d]:%d\n",i, arry[i]);

    }

    printf("arry[%d]:%d\n",i+1, arry[i+1]);//27

    }

    //main body

    int main(int narg, const char** args)

    {

    char* pwildptr;

    char* pstr = "this is a memory debug program!\n";

    int array[10] = {1,2,3,4,5,6,7,8,9,10};

    char* ptmp = stringcopy(pstr);//36

    //memory leak

    char* ptmp2 = (char*)malloc(100);//38

    memset(ptmp2, 0, 100);

    // memory write overflow

    printf(ptmp);//41

    // array tip assess overflow

    printarray(array, 10);//43

    free(ptmp);//44

    printf("%p", pwildptr);//45

    //wild ptr copy

    memcpy(ptmp, ptmp2, 20);//47

    printf(ptmp);//48

    return 0;

    }

    2.2.2 编译源码:

    $g++ -g -O0 -c gdbmem.cpp#-g:编译带调试信息的obj文件,-O0:不优化

    $g++ -o gdbmem gdbmem.o

    编译后将在当前目录下生成gdbmem可执行文件。

    2.2.3 valgrind内存检测

    valgring 对gdbmem进行内存检测

    $ valgrind --tool=memcheck --leak-check=full --track-fds=yes ./gdbmem

    ==10668== Memcheck, a memory error detector

    ==10668== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.

    ==10668== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info

    ==10668== Command: ./gdbmem

    ==10668==

    ==10668== Invalid write of size 8

    ==10668== at 0x4C3453F: memset (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x40075D: stringcopy(char*) (gdbmem.cpp:13)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668== Address 0x5204060 is 32 bytes inside a block of size 33 alloc'd

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    ==10668== Invalid write of size 1

    ==10668== at 0x4C34558: memset (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x40075D: stringcopy(char*) (gdbmem.cpp:13)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668== Address 0x5204080 is 16 bytes after a block of size 48 in arena "client"

    ==10668==

    ==10668== Invalid write of size 8

    ==10668== at 0x4C326CB: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400778: stringcopy(char*) (gdbmem.cpp:14)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668== Address 0x5204060 is 32 bytes inside a block of size 33 alloc'd

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    ==10668== Invalid write of size 2

    ==10668== at 0x4C32723: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400778: stringcopy(char*) (gdbmem.cpp:14)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668== Address 0x5204080 is 16 bytes after a block of size 48 in arena "client"

    ==10668==

    this is a memory debug program!

    arry[0]:1

    arry[1]:2

    arry[2]:3

    arry[3]:4

    arry[4]:5

    arry[5]:6

    arry[6]:7

    arry[7]:8

    arry[8]:9

    arry[9]:10

    arry[11]:-623874025

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E8890E: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Use of uninitialised value of size 8

    ==10668== at 0x4E84711: _itoa_word (_itoa.c:180)

    ==10668== by 0x4E8812C: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E84718: _itoa_word (_itoa.c:180)

    ==10668== by 0x4E8812C: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E881AF: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E87C59: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E87CE2: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    main函数45行,memcpy(ptmp, ptmp2, 20);读取已经被释放的内存,导致memcheck报错。

    ==10668== Invalid write of size 8

    ==10668== at 0x4C326CB: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008FE: main (gdbmem.cpp:47)

    ==10668== Address 0x5204040 is 0 bytes inside a block of size 33 free'd

    ==10668== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008D0: main (gdbmem.cpp:44)

    ==10668== Block was alloc'd at

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    ==10668== Invalid write of size 2

    ==10668== at 0x4C32723: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008FE: main (gdbmem.cpp:47)

    ==10668== Address 0x5204050 is 16 bytes inside a block of size 33 free'd

    ==10668== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008D0: main (gdbmem.cpp:44)

    ==10668== Block was alloc'd at

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    ==10668== Invalid read of size 1

    ==10668== at 0x4ED0760: strchrnul (strchr.S:24)

    ==10668== by 0x4E87207: __find_specmb (printf-parse.h:108)

    ==10668== by 0x4E87207: vfprintf (vfprintf.c:1312)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x40090F: main (gdbmem.cpp:48)

    ==10668== Address 0x5204040 is 0 bytes inside a block of size 33 free'd

    ==10668== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008D0: main (gdbmem.cpp:44)

    ==10668== Block was alloc'd at

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    ==10668== Invalid read of size 1

    ==10668== at 0x4E8741A: vfprintf (vfprintf.c:1324)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x40090F: main (gdbmem.cpp:48)

    ==10668== Address 0x5204040 is 0 bytes inside a block of size 33 free'd

    ==10668== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008D0: main (gdbmem.cpp:44)

    ==10668== Block was alloc'd at

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    0x40097d==10668==

    ==10668== FILE DESCRIPTORS: 3 open at exit.

    ==10668== Open file descriptor 2: /dev/pts/4

    ==10668==

    ==10668==

    ==10668== Open file descriptor 1: /dev/pts/4

    ==10668==

    ==10668==

    ==10668== Open file descriptor 0: /dev/pts/4

    ==10668==

    ==10668==

    ==10668==

    ==10668== HEAP SUMMARY:

    ==10668== in use at exit: 100 bytes in 1 blocks

    ==10668== total heap usage: 3 allocs, 2 frees, 1,157 bytes allocated

    ==10668==

    ==10668== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400888: main (gdbmem.cpp:38)

    ==10668==

    ==10668== LEAK SUMMARY:

    ==10668== definitely lost: 100 bytes in 1 blocks

    ==10668== indirectly lost: 0 bytes in 0 blocks

    ==10668== possibly lost: 0 bytes in 0 blocks

    ==10668== still reachable: 0 bytes in 0 blocks

    ==10668== suppressed: 0 bytes in 0 blocks

    ==10668==

    ==10668== For counts of detected and suppressed errors, rerun with: -v

    ==10668== Use --track-origins=yes to see where uninitialised values come from

    ==10668== ERROR SUMMARY: 37 errors from 15 contexts (suppressed: 0 from 0)

    2.2.3 memcheck检测结果分析

    memcheck的LEAK SUMMARY输出结果将内存泄漏分为以下几种情况:

    definitely lost:明确地已经泄漏了,因为在程序运行完的时候,没有指针指向它, 指向它的指针在程序中丢失了

    indirectly lost:间接丢失。当使用了含有指针成员的类或结构时可能会报这个错误。这类错误无需直接修复,他们总是与”definitely lost”一起出现,只要修复”definitely lost”即可。

    possibly lost:发现了一个指向某块内存中部的指针,而不是指向内存块头部。这种指针一般是原先指向内存块头部,后来移动到了内存块的中部,还有可能该指针和该内存根本就没有关系,检测工具只是怀疑有内存泄漏。

    still reachable:可以访问,未丢失但也未释放

    suppressed:已被解决。出现了内存泄露但系统自动处理了。可以无视这类错误。

    内存泄漏概述:

    ==10668== LEAK SUMMARY:

    ==10668== definitely lost: 100 bytes in 1 blocks

    ==10668== indirectly lost: 0 bytes in 0 blocks

    ==10668== possibly lost: 0 bytes in 0 blocks

    ==10668== still reachable: 0 bytes in 0 blocks

    ==10668== suppressed: 0 bytes in 0 blocks

    此处只有100个字节的内存泄漏。

    ==10668== Invalid write of size 8

    ==10668== at 0x4C3453F: memset (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x40075D: stringcopy(char*) (gdbmem.cpp:13)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668== Address 0x5204060 is 32 bytes inside a block of size 33 alloc'd

    根据错误提示,stringcopy 函数13行,即memset(pdst, 0, len 2);申请了len的数据长度,memset的时候却使用了2len的数据长度,内存写溢出了。

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    ==10668== Invalid write of size 1

    ==10668== at 0x4C34558: memset (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x40075D: stringcopy(char*) (gdbmem.cpp:13)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668== Address 0x5204080 is 16 bytes after a block of size 48 in arena "client"

    stringcopy 函数13行,即memset(pdst, 0, len2);申请了len的数据长度,memset的时候却使用了2len的数据长度,内存写溢出。相同语句的内存写溢出,却报了两个错误,原因笔者目前也还没有弄明白,如果有大虾指点,不胜感激。

    ==10668==

    ==10668== Invalid write of size 8

    ==10668== at 0x4C326CB: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400778: stringcopy(char*) (gdbmem.cpp:14)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668== Address 0x5204060 is 32 bytes inside a block of size 33 alloc'd

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    ==10668== Invalid write of size 2

    ==10668== at 0x4C32723: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400778: stringcopy(char*) (gdbmem.cpp:14)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668== Address 0x5204080 is 16 bytes after a block of size 48 in arena "client"

    stringcopy 函数13行,即memcpy(pdst, psrc, len2);申请了len的数据长度,memset的时候却使用了2len的数据长度,内存写溢出。

    ==10668==

    this is a memory debug program!

    arry[0]:1

    arry[1]:2

    arry[2]:3

    arry[3]:4

    arry[4]:5

    arry[5]:6

    arry[6]:7

    arry[7]:8

    arry[8]:9

    arry[9]:10

    arry[11]:-623874025

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E8890E: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Use of uninitialised value of size 8

    ==10668== at 0x4E84711: _itoa_word (_itoa.c:180)

    ==10668== by 0x4E8812C: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E84718: _itoa_word (_itoa.c:180)

    ==10668== by 0x4E8812C: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E881AF: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E87C59: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    ==10668==

    ==10668== Conditional jump or move depends on uninitialised value(s)

    ==10668== at 0x4E87CE2: vfprintf (vfprintf.c:1631)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x4008E6: main (gdbmem.cpp:45)

    main函数45行,printf("%p", pwildptr);读取未初始化的野指针

    ==10668==

    ==10668== Invalid write of size 8

    ==10668== at 0x4C326CB: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008FE: main (gdbmem.cpp:47)

    ==10668== Address 0x5204040 is 0 bytes inside a block of size 33 free'd

    ==10668== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008D0: main (gdbmem.cpp:44)

    ==10668== Block was alloc'd at

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    ==10668== Invalid write of size 2

    ==10668== at 0x4C32723: memcpy@@GLIBC_2.14 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008FE: main (gdbmem.cpp:47)

    ==10668== Address 0x5204050 is 16 bytes inside a block of size 33 free'd

    ==10668== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008D0: main (gdbmem.cpp:44)

    ==10668== Block was alloc'd at

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    main函数47行,memcpy(ptmp, ptmp2, 20);写入已经释放的内存

    ==10668==

    ==10668== Invalid read of size 1

    ==10668== at 0x4ED0760: strchrnul (strchr.S:24)

    ==10668== by 0x4E87207: __find_specmb (printf-parse.h:108)

    ==10668== by 0x4E87207: vfprintf (vfprintf.c:1312)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x40090F: main (gdbmem.cpp:48)

    ==10668== Address 0x5204040 is 0 bytes inside a block of size 33 free'd

    ==10668== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008D0: main (gdbmem.cpp:44)

    ==10668== Block was alloc'd at

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    ==10668==

    ==10668== Invalid read of size 1

    ==10668== at 0x4E8741A: vfprintf (vfprintf.c:1324)

    ==10668== by 0x4E8F898: printf (printf.c:33)

    ==10668== by 0x40090F: main (gdbmem.cpp:48)

    ==10668== Address 0x5204040 is 0 bytes inside a block of size 33 free'd

    ==10668== at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x4008D0: main (gdbmem.cpp:44)

    ==10668== Block was alloc'd at

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400740: stringcopy(char*) (gdbmem.cpp:12)

    ==10668== by 0x40087A: main (gdbmem.cpp:36)

    main函数47行,printf(ptmp);写入已经释放的内存

    0x40097d==10668==

    ==10668== FILE DESCRIPTORS: 3 open at exit.

    ==10668== Open file descriptor 2: /dev/pts/4

    ==10668==

    ==10668==

    ==10668== Open file descriptor 1: /dev/pts/4

    ==10668==

    ==10668==

    ==10668== Open file descriptor 0: /dev/pts/4

    ==10668==

    linux为了实现一切皆文件的设计哲学,不仅将数据抽象成了文件,也将一切操作和资源抽象成了文件,比如说硬件设备,socket,磁盘,进程,线程等。

    这样的设计将系统的所有动作都统一起来,实现了对系统的原子化操作,大大降低了维护和操作的难度。

    设备描述符,就是描述该文件数据设备的唯一标示,不同类型的文件,文件描述符也不一样,如下所示:

    bVbmtiv?w=1430&h=474

    ==10668==

    ==10668==

    ==10668== HEAP SUMMARY:

    ==10668== in use at exit: 100 bytes in 1 blocks

    ==10668== total heap usage: 3 allocs, 2 frees, 1,157 bytes allocated

    ==10668==

    ==10668== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1

    ==10668== at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)

    ==10668== by 0x400888: main (gdbmem.cpp:38)

    ==10668==

    内存泄漏概述,3次内存分配,两次释放。已经有100个字节的内存已经确定泄漏。泄漏的内存分配于38行,charptmp2 = (char)malloc(100);至此,内存泄漏检测完毕。

    valgrind内存检测解析

    valgrind对内存读写溢出,读取未初始化的变量、指针、内存能够很好的检测到,但是对于数组越界却无法检测,这是因为memcheck并不对stack和全局数组进行越界检查,所以无法检测出printarray中的内存访问越界。

    展开全文
  • 1.内存泄漏确定现象:linux 内存泄漏,可以查看slabinfo 和另外一个proc下(貌似meminfo),关于内存的信息,可以看到内存是否在不断减少,以及减少的速度。vxworks系统,内存是否有相关信息???如果快速泄漏内存,...

    1.内存泄漏

    确定现象:

    linux 内存泄漏,可以查看slabinfo 和另外一个proc下(貌似meminfo),关于内存的信息,可以看到内存是否在不断减少,以及减少的速度。

    vxworks系统,内存是否有相关信息???

    如果快速泄漏内存,则较容易判断。如果是非常慢的,则要经过一定时间场景复现后,应该也能看出来。

    linux在内存耗光后,会有log打印。容易判断。vxworks待试验。

    在确认存在内存泄漏后,如何确认泄漏源头?

    malloc做修改,使其记录相关信息 ---- 当前是否此功能。没有就当没说。

    复现场景。在什么场景下会泄漏,具体点,哪个任务在跑是会泄漏,由此确认。

    【 具体方法补充】

    Linux在内存使用上的原则是:如果内存充足,不用白不用,尽量使用内存来缓存一些文件,从而加快进程的运行速度,而当内存不足时,会通过相应的内存回收策略收回cache内存,供进程使用。

    可通过对proc下进程相关的文件进行分析,精确评估系统消耗内存的大小,还可以对内存泄露类问题的解决提供一种定位手段。

    一、系统总内存的分析

    可以从proc目录下的meminfo文件了解到当前系统内存的使用情况汇总,其中可用的物理内存=memfree+buffers+cached,当memfree不够时,内核会通过回写机制(pdflush线程)把cached和buffered内存回写到后备存储器,从而释放相关内存供进程使用,或者通过手动方式显式释放cache内存:

    echo 3 > /proc/sys/vm/drop_caches

    通过cat /proc/meminfo 查看内存使用情况。

    二、进程使用内存的统计

    在32位操作系统中,每个进程拥有4G的虚拟内存空间,其中0~3GB是每个进程的私有用户空间,这个空间对系统中其他进程是不可见的。3~4GB是linux内核空间,由系统所有的进程以及内核所共享的。通过访问/proc/{pid}/下相关文件,可以了解每个线程虚拟内存空间的使用情况,从而了解每个线程所消耗内存的多少。

    由于我们的产品都是使用多线程方式实现的,多个线程共享一个进程的用户态虚拟地址空间,虚拟地址空间包含若干区域,主要有如下几个区域:

    1、当前执行文件的代码段,该代码段称为text段。

    2、执行文件的数据段,主要存储执行文件用到的全局变量,静态变量。

    3、存储全局变量和动态产生的数据的堆。

    4、用于保存局部变量和实现函数调用的栈。

    5、采用mmap方式映射到虚拟地址空间中的内存段

    所以只需要查看任意一个线程的用户态虚拟地址空间分配即可知道属于同一进程的所有线程占用总内存的大小。可以通过查看/proc/{pid}/maps文件来获取相关的虚拟地址空间内容

    如果在实际的调试过程中,怀疑某处发生了内存泄露,可以查看该进程的maps表,看进程的堆段或者mmap段的虚拟地址空间是否持续增加,如果是,说明很可能发生了内存泄露,如果mmap段虚拟地址空间持续增加,还可以看到各个段的虚拟地址空间的大小,从而可以确定是申请了多大的内存,对调试内存泄露类问题可以起到很好的定位作用。

    2.踩内存。

    1)预先分配的内存被踩。比如共享内存,axi_mem。有可能是其他核或当前核的不同启动阶段。

    先确认分配是否有问题,分配空间是否重叠。如果在边界,则重点怀疑相邻区域。

    也可能公共IP资源的寄存器,被胡乱修改,如果在当前核未做写操作时,寄存器被改“写”,则应该是其他核干的。

    2)系统内部,一个核的系统运行,普通的内存。

    不好弄。可以尝试打条件断点。被踩后错误的值是多少,合理的值是多少。

    是不是被自己模块内的数组越界等原因所踩。

    如果是因为为初始化指针访问,导致向随机地址上写,不用太担心,因为更大可能是data abort。

    用处不大。。

    6380113d162075d54abedbd4b5184422.png

    0fd941a2b537727fdc758644d4d54c91.png

    展开全文
  • Linux产品开发过程中,通常需要注意系统内存使用量,和评估单一进程的内存使用情况,便于我们选取合适的机器配置,来部署我们的产品。Linux本身提供了一些工具方便我们达成这些需求,查看进程实时资源top工具,更...

    在Linux产品开发过程中,通常需要注意系统内存使用量,和评估单一进程的内存使用情况,便于我们选取合适的机器配置,来部署我们的产品。

    Linux本身提供了一些工具方便我们达成这些需求,查看进程实时资源top工具,更详细的进程内存堆栈情况,pmap工具,Linux进程运行时状态信息也会保存在proc目录下,相应进程ID目录下,这里有很丰富的信息,先讨论进程内存。

    借助网上大部分人的说法,Linux系统在内存分配上:内存充足时,尽量使用内存来缓存一些文件,从而加快进程的运行速度,而当内存不足时,会通过相应的内存回收策略收回cache内存,供进程使用。

    虽然在Linux平台下做开发,但是对Linux内存管理并不熟悉,不过上述说法,可以通过下面的方法来验证:

    一、系统内存。

    在proc目录下的meminfo文件描述系统内存的使用情况,可用的物理内存=memfree+buffers+cached,下图是suse10 的情况:

    MemTotal 是全部物理内存,我的虚拟器配置的是1G内存,memfree+buffers+cached = 438752,大概还有430M可用,因为我的机器上只跑着apache和redis进程。

    当memfree不够时,内核会通过回写机制(pdflush线程)把cached和buffered内存回写到后备存储器,也可以通过手动方式显式释放cache内存

    echo 3 > /proc/sys/vm/drop_caches

    释放后,Buffers和Cached 表小了好多,MemFree变大了许多,memfree+buffers+cached三者和大约仍然是430M。

    二、进程内存

    在32位操作系统中,每个进程拥有4G的虚拟内存空间,其中0~3GB是每个进程的私有用户空间,这个空间对系统中其他进程是不可见的。3~4GB是linux内核空间,由系统所有的进程以及内核所共享的。通过访问/proc/{pid}/下相关文件,可以查看进程内存情况。

    如果进程内含有多个线程,多个线程共享一个进程的用户态虚拟地址空间,虚拟地址空间包含若干区域,主要有如下几个区域:

    1、当前执行文件的代码段,该代码段称为text段。

    2、执行文件的数据段,主要存储执行文件用到的全局变量,静态变量。(全局和static)

    3、存储全局变量和动态产生的数据的堆。(堆)

    4、用于保存局部变量和实现函数调用的栈。(栈)

    5、采用mmap方式映射到虚拟地址空间中的内存段

    这是我的机器上,redis 进程的情况,

    第一行:从r-xp可知其权限为只读、可执行,该段内存地址对应于执行文件的

    代码段,程序的代码段需加载到内存中才可以执行。由于其只读,不会

    被修改,所以在整个系统内共享。

    第二行:从rw-p可知其权限为可读写,不可执行,该段内存地址对应于执行文件的数据段,存放执行文件所用到的全局变量、静态变量。

    第三行:从rwxp可知其权限是可读写,可执行,地址空间向上增长,而且不对应文件,是堆段,进程使用malloc申请的内存放在堆段。每个进程只有一个堆段,不论是主进程,还是不同的线程申请的内存,都反映到到进程的堆段。堆段向上增长,最大可以增长到1GB的位置,即0x40000000,如果大于1GB,glibc将采用mmap的方式,为堆申请一块内存。

    第四行:是程序连接的共享库的内存地址。

    第五行:是以mmap方式映射的虚拟地址空间。

    第六、七行:是线程的栈区地址段,每个线程的栈大小都是16K。

    第八行:是进程的栈区。关于栈段,每个线程都有一个,如果进程中有多个线程,则包含多个栈段。

    三、当前系统总内存的统计

    1、进程占用的总内存可以通过上述maps表计算出来。

    2、当系统运行起来以后,会把应用层相关的文件挂载到tmpfs文件系统下,海思系统下这部分大概有13M左右,这部分内存是以cache方式统计出来的,但是这部分内存cache无法通过回收策略或者显式的调用释放掉。

    3、根文件系统ramdisk占用的内存。

    4、当前系统保留内存的大小,可以通过查看/proc/sys/vm/min_free_kbytes来获取或者修改此内存的大小。

    5、当然,当系统运行起来后,还应该留有一定的内存用于在硬盘读写时做cache或者网络负荷比较高时分配skb等,一般需要30M以上。

    四、对调试内存泄露类问题的一些启示

    当进程申请内存时,实际上是glibc中内置的内存管理器接收了该请求,随着进程申请内存的增加,内存管理器会通过系统调用陷入内核,从而为进程分配更多的内存。

    针对堆段的管理,内核提供了两个系统调用brk和mmap,brk用于更改堆顶地址,而mmap则为进程分配一块虚拟地址空间。

    当进程向glibc申请内存时,如果申请内存的数量大于一个阀值的时候,glibc会采用mmap为进程分配一块虚拟地址空间,而不是采用brk来扩展堆顶的指针。缺省情况下,此阀值是128K,可以通过函数来修改此值。

    #include

    Intmallopt(int param, int value)

    Param的取值分别为M_MMAP_THRESHOLD、M_MMAP_MAX。

    Value的取值是以字节为单位的。

    M_MMAP_THRESHOLD是glibc中申请大块内存阀值,大于该阀值的内存申请,内存管理器将使用mmap系统调用申请内存,如果小于该阀值的内存申请,内存管理器使用brk系统调用扩展堆顶指针。

    M_MMAP_MAX是该进程中最多使用mmap分配地址段的数量。

    如果在实际的调试过程中,怀疑某处发生了内存泄露,可以查看该进程的maps表,看进程的堆段或者mmap段的虚拟地址空间是否持续增加,如果是,说明很可能发生了内存泄露,如果mmap段虚拟地址空间持续增加,还可以看到各个段的虚拟地址空间的大小,从而可以确定是申请了多大的内存,对调试内存泄露类问题可以起到很好的定位作用。

    转自:http://blog.csdn.net/babykakaluo/article/details/9763605

    展开全文
  • 4、踩内存问题的定位手段 1)gdb watch 排查代码,找到了存储link_map的全局变量_rtld_global,在C模块的main函数中,守护进程拉起后,加入了延时(这样我们有时间可以根据全局变量找到总是被踩的内存的地址),重新...

    这几天在做总结,把三年前写的一个定位案例,翻了出来。回想起定位这个问题时的场景,领导催得紧,自己对很多东西又不熟悉,所以当时面临的压力还是很大的。现在回想起来感慨还是很多的,我们在遇到任何一个问题,一定不要放弃。还记得在产品线做开发时,学到的一些项目知识,任何一个bug,他总有一天会爆发出来。任何一个问题,总有一天找到好的解决方案。当我们尝尽了所有可以尝试的方案,定位办法,解决思路后,往往这个问题也就迎刃而解了。

    把工程上的事情放大看,其实生活中很多事情都是一样的,任何一个问题都有解决办法,但是这个办法不是摆在那里给我们用,他是藏在某个地方,等着我们去挖掘的。所以勤奋,努力,不气馁,找对方向都是很重要的。

    好了,我们言归正传,说下我这个踩内存的问题。首先我们来看一下,公司常讲的编程规范:有一条是说,

    结构体指针在使用前需要赋初值。

    这是很简单的一条规则,很多时候,我们会觉着麻烦,或者我们在后面具体用到这个结构体时,我们再对结构体的成员赋值也可以,或者我们在使用时,仅对我们感兴趣的成员赋值就好了,其他的我们就不关心了。但是下面我来告诉大家,这是不行的,下面这个问题会告诉大家,编程规范都是用血的教训写出来的。遵守它,我们就可以避免很多不必要的debuging。

    我们下面看下这种写法是否正确:

    structmsghdr msg;

    msg.msg_iov= &iov;

    msg.msg_iovlen=iovlen;

    ret= recvmsg(sockfd, &msg, int flags);

    在函数中,我们定义了一个msg结构体,但是没有给这个结构体赋初值,仅对其中的两个我们关心的变量做了赋值。那么这样是否会带来问题呢?

    在项目交付前,A同学需要完成对S系统的压力测试,上面会反复的重启虚拟机,反复的杀死大量服务进程,做cpu,内存加压。这样的压力测试大概执行一天到两天,就会出现异常,C服务出现了大量的coredump。

    这个问题是怎么产生的,如何定位这个问题,下面请看具体的定位步骤:

    1、coredump

    应用程序由于各种异常或者bug导致在运行过程中异常退出或者中止,并且在满足一定条件下会产生一个叫做core的文件。通常情况下,core文件会包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息还有各种函数调用堆栈信息等,我们可以理解为是程序工作当前状态存储生成第一个文件,许多的程序出错的时候都会产生一个core文件,通过分析这个文件,我们可以定位到程序异常退出的时候对应的堆栈调用等信息,找出问题所在并进行及时解决。

    应用程序崩溃时能产生core文件,还需要做一些设置:

    1) 修改ulimit –c

    如下,ulimit  -c设置core文件大小,一般设置为unlimited,让core文件不受大小限制。如果在当前会话设置,那么仅对当前会话的应用程序有效,即只有当前会话的程序崩溃才会有core文件生成。如果要想系统均有效,需要设置/etc/profile文件,在文件中加入ulimit –c unlimited,然后source /etc/profile,让配置生效。如果还是生成不了core文件,可以在拉起进程的脚本里加入ulimit –c unlimited。

    2) 配置core文件生成路径

    core文件默认的存储位置与对应的可执行程序在同一目录下,文件名为core,很多时候程序崩溃了,我们找不到core文件存放在什么位置,按照如下方法可以指定core文件的存放位置。

    echo "/home/xx/core-%e-%p-%u-%g-%t" > /proc/sys/kernel/core_pattern

    参数含义:%%:相当于%

    %p:相当于

    %u:相当于

    %g:相当于

    %s:相当于导致dump的信号的数字%t:相当于dump的时间%e:相当于执行文件的名称%h:相当于hostname

    coredump产生的几种可能情况:

    1)内存访问越界

    a) 由于使用错误的下标,导致数组访问越界。

    b) 搜索字符串时,依靠字符串结束符来判断字符串是否结束,但是字符串没有正常的使用结束符。

    c) 使用strcpy, strcat, sprintf, strcmp,strcasecmp等字符串操作函数,将目标字符串读/写爆。应该使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函数防止读写越界。2)多线程程序使用了线程不安全的函数。3)多线程读写的数据未加锁保护。

    对于会被多个线程同时访问的全局数据,应该注意加锁保护,否则很容易造成coredump4)非法指针

    a) 使用空指针

    b) 随意使用指针转换。一个指向一段内存的指针,除非确定这段内存原先就分配为某种结构或类型,或者这种结构或类型的数组,否则不要将它转换为这种结构或类型的指针,而应该将这段内存拷贝到一个这种结构或类型中,再访问这个结构或类型。这是因为如果这段内存的开始地址不是按照这种结构或类型对齐的,那么访问它时就很容易因为bus error而core dump。5)堆栈溢出

    不要使用大的局部变量(因为局部变量都分配在栈上),这样容易造成堆栈溢出,破坏系统的栈和堆结构,导致出现莫名其妙的错误

    2、Gdb进程coredump定位

    再来具体看看core文件,一般使用gdb的方式来查看,为了看到更多的信息,需要先编译当时的C服务的debug版本,可以取对应代码,加-g选项进行编译,或者从obs上取到对应的debuginfo的安装包。同时也需要安装了glibc的debuginfo包。

    然后通过命令 gdb C(C为c服务的二进制可执行文件调试版本) core.C-pid打印出core信息。bt来查看调用栈。

    program terminated with signal 11, Segmentation fault.

    #0 strcmp () at ../sysdeps/x86_64/strcmp.s:135

    135 cmpb [%rsi], %al

    (gdb) bt

    #0 strcmp () at ../sysdeps/x86_64/strcmp.s:135#1 0x00007fc63177a345 in _dl_name_watch_p (name=0x400d56 "libc.so.6", map=0x7fc631909000) ad dl-misc.c:305#2……

    gdb常用命令:经常使用有l(list),b(break), p(print), r(run), c(continue), q(quit),i r(查看当时的寄存器信息),info local(查看当前变量信息),frame 4(切换),disassemble+地址(反汇编解析)

    通过命令 p *(struct link_map *)+地址来查看出现问题时map的内容,如下:

    (gdb) p *(struct link_map*)0x7fae515fe000$1 = {l_addr = 16, l_name = 0x7fab00000001

    },……}

    可以看到结构体中l_name的地址是明显错误的,查看glibc的代码,代码如下,strcmp在比较name的时候出现了段错误。L_name的地址为0x7fab00000001,怀疑是低四字节的位置被踩。

    intinternal_function

    _dl_name_match_p (const char *name, const struct link_map *map)

    {if (strcmp (name, map->l_name) == 0)return 1;struct libname_list *runp = map->l_libname;while (runp !=NULL)if (strcmp (name, runp->name) == 0)return 1;elserunp= runp->next;return 0;

    }

    另外通过map中的next指针,依次可以找到链表所有节点的l_name的值,如下所示:

    link_map->l_name ""link_map->l_name ""link_map->l_name /usr/lib64/libsecurec.so

    link_map->l_name 0x7fab00000001link_map->l_name /usr/lib64/libalarm.so

    link_map->l_name /usr/lib64/libc.so.6link_map->l_name /usr/lib64//lib64/ld-linux-x86-64.so.2

    查看C依赖的动态库:

    :/home # ldd C

    linux-vdso.so.1 => (0x00007ffff27e3000)

    libsecurec.so=> /usr/lib64/libsecurec.so (0x00007fea37fb2000)

    libpthread.so.0 => /usr/lib64/libpthread.so.0 (0x00007fea37d95000)

    libalarm.so=> /usr/lib64/libalarm.so (0x00007fea37b92000)

    libc.so.6 => /usr/lib64/libc.so.6 (0x00007fea377d2000)/lib64/ld-linux-x86-64.so.2 (0x00007fea381c5000)

    可以知道l_name对应的值应为/usr/lib64/libpthread.so。

    这里我们插播一个小知识点:

    3、动态库符号解析

    这个问题暂时也没有什么思路,就先从调用栈入手吧,这一族函数,_dl_runtime_resolve->_dl_fixup->_dl_lookup_symbol_x->do_lookup_x->_dl_name_match_p->strcmp是与函数的动态解析有关系,上网查看了相关资料,简单说涉及两个方面:

    1、动态的加载,就是当这个运行的模块在需要的时候才被映射入运行模块的虚拟内存空间中,如一个模块在运行中要用到mylib.so中的myget函数,而在没有调用mylib.so这个模块中的其它函数之前,是不会把这个模块加载到你的程序中(也就是内存映射)。

    2、动态的解析,就是当要调用的函数被调用的时候,才会去把这个函数在虚拟内存空间的起始地址解析出来,再写到专门在调用模块中的储存地址内,如前面所说的你已经调用了myget,所以mylib.so模块肯定已经被映射到了程序虚拟内存之中,而如果你再调用 mylib.so中的myput函数,那它的函数地址就在调用的时候才会被解析出来。

    调用栈是地址解析相关的东西,那么说明调用的函数在之前是没有被解析过的,即在此进程中是第一次调用。segfault是在调用prctl时产生的,函数中会先fork出子进程,然后在子进程中调用prctl函数,而prctl这个函数恰恰就是只有在这个子进程中才会调用,至此这个问题的一些现象就可以解释清楚了:动态库相关的link_map结构的某个字段被踩,fork出的子进程,虽然会从父进程那里得到所有信息的副本,但是父进程没有调用过prctl这个函数,也就不会有这个函数的解析地址信息,那么在子进程中调用prctl函数时,就会走入动态解析的流程,在进行strcmp比较时出现段错误。因为父进程没有segfault,父进程还在继续运行,还会再继续fork子进程,因此core文件中会产生多个相同大小,但是进程id,时间戳不同的core文件。

    4、踩内存问题的定位手段

    1)gdb watch

    排查代码,找到了存储link_map的全局变量_rtld_global,在C模块的main函数中,守护进程拉起后,加入了延时(这样我们有时间可以根据全局变量找到总是被踩的内存的地址),重新编译代码,替换bin文件,然后ps –aux | grep C,查看守护进程的id,再gdb –p PID,进入守护进程的gdb调试,先p _rtld_global,然后找到这个全局变量的第一个变量,p *(struct link_map*) 这个地址,这个就是link_map链表的第一个成员的地址,然后按照next的地址依次找到存放pthread.so信息的link_map结构的地址,然后watch *(int*)这个地址。如果有应用程序在改写这个地址,那么当被写的时候,gdb就会断住,并且会打印调用栈信息。

    但是不幸的是,已经发生了segfault,但并没有调用栈信息记录。在gdb中,如果watch不到,很有可能是内核改写了内存。

    2)将被踩内存设置为只读

    将这个地址设置为只读,那么当有程序去改写这个内存时,就会生成对应的core文件,通过core,我们就可以知道调用栈,可以清楚是谁改写内存了。

    void set_page_ro(void)

    {char *p =NULL;

    p= find_link_map("/usr/lib64/libpthread.so.0");

    mprotect((void*)((unsigned long)p & 0xfffffffffffff000UL), 4096, PROT_READ);

    }

    代码中,先找到libpthread.so对应的地址,然后设置这个地址所在的页为只读的。但是实践后,发现这个办法不行,因为只能对4k的页设置只读,那么很多在4k范围内的正常写入也会产生core文件。

    那么既然怀疑是内核在改写这个内存,那么我们就开始从内核入手吧。

    3)通过系统调用来获取内核踩内存的调用栈

    需要先编译出一个ko,然后ismod这个ko,再在C模块的main函数中,加入syscall(SYS_afs_syscall, p); p为获取到的那个pthread.so对应的地址。然后编译可执行文件,替换bin,启动C服务,当p这个地址被改写时,通过dmesg就可以看到调用栈了。

    [ 6322.163418] Call Trace:

    [6322.166046] [] dump_stack+0x19/0x1b[6322.171993] [] sample_hbp_handler+0x2a/0x30[a]

    [6322.178361] [] __perf_event_overflow+0xa1/0x250[6322.184733] [] perf_swevent_overflow+0x51/0xe0[6322.191013] [] perf_swevent_event+0x6f/0x90[6322.197031] [] perf_bp_event+0x99/0xc0[6322.202618] [] hw_breakpoint_exceptions_notify+0x120/0x150[6322.209944] [] notifier_call_chain+0x4c/0x70[6322.216025] [] notify_die+0x45/0x60[6322.221351] [] do_debug+0xaf/0x230[6322.226589] [] debug+0x2b/0x40[6322.231484] [] ? kfree_skbmem+0x37/0x90[6322.237163] [] ? copy_user_enhanced_fast_string+0xb/0x20[6322.244278] <> [] ? move_addr_to_user+0xb2/0xd0[6322.251210] [] ___sys_recvmsg+0x14d/0x2b0[6322.257052] [] ? schedule+0x29/0x70[6322.262383] [] ? handle_mm_fault+0x65c/0xf50[6322.268493] [] __sys_recvmsg+0x51/0x90[6322.274052] [] SyS_recvmsg+0x12/0x20[6322.279465] [] system_call_fastpath+0x16/0x1b[6322.285796] --- 14678, 00007f73450a3000 value is changed

    后来通过代码排查,找出所有recvmsg调用的地方,再结合出现问题的模块,最后发现是与msg结构体没有初始化有关系。下面我们把这个问题,从原因开始再分析一遍。

    5、问题原因

    1)C服务刚启动时,进程空间是新分配的,所以内存的值应该都是清零的。拉起一个线程,msg为该线程申请的一个局部变量,虽然没有对其初始化,但此时的整片内存的值都是0。

    [2016-08-09:10:23:04]C[690]: C starting up

    [2016-08-09:10:23:04]C[690]: --- nlh[0x23750e0]: typ[32676] seq[677353752]; msg[0x7fa424236d10]: name[(nil)] len[(nil)] control[(nil)] clen[0] flags[0]

    2)开始执行长稳测试用例,看C的日志,发现大概1s中有40条监控的记录,然后会recvmsg出错,错误码为105,意思是No buffer space available。

    [2016-08-10:07:59:59]C[690]: recvmsg from NETLINK_SIGNO socket failed [105]

    3)  之后信号监控线程会退出,守护进程会再拉起信号监控的线程,那么此时的LWP进程空间已经不是全新的了,就不会是全0的了。可以看到msg,name的值已经不是0了。

    [2016-08-10:07:59:59]C[690]: C starting up

    [2016-08-10:07:59:59]C[690]: --- nlh[0x2376fd0]: typ[32676] seq[677353560]; msg[0x7fa424236d10]: name[0x7fa428e50000] len[0x7fa428c3ae6f] control[0x5] clen[0] flags[1]

    可以看到那么中的地址0x7fa428e50000,那么这个地址就是对应的该进程空间中的链接库的地址。

    导致出现这个问题,其实是开发人员编程习惯和规范的问题,我们在调用recvmsg函数时,传入的参数中有几个变量没有赋初值。

    再回来说到recvmsg,函数原型如下:

    ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

    这里msghdr定义如下:

    structmsghdr {void *msg_name; /*protocol address*/socklen_t msg_namelen;/*sieze of protocol address*/

    struct iovec *msg_iov; /*scatter/gather array*/

    int msg_iovlen; /*# elements in msg_iov*/

    void *msg_control; /*ancillary data ( cmsghdr struct)*/socklen_t msg_conntrollen;/*length of ancillary data*/

    int msg_flags; /*flags returned by recvmsg()*/}

    我们在使用这个函数时,对msghdr这个结构的msg_iov,msg_iovlen做了赋值,其他变量都没有处理,也没有赋初值。当C服务启动后,C服务的进程空间是新分配的,这块空间都会被初始化为0(因为这块空间是内核分配的一块物理内存,分配给用户态,一定要清零,不然用户就会读到内核内容),C服务的进程在创建线程后,线程是共享进程空间的,所以线程的空间也是清零,我们在这个线程中定义一个struct msghdr类型的变量时,这块内存也是全零的,即使我们没有赋初值,在正常情况下,不会出现问题。

    但异常情况,比如我们现在的压力测试,cpu,内存加压的情况下,什么异常都有可能会出现。当出现异常后,这个线程出现了异常退出,C服务的守护进程会再拉起该线程,那么这个重新拉起来的线程,就不会那么巧合,分配到一个为0的空间。在使用msg值,代码里只对两个成员赋值,其他的值未知,刚好存储msg这个变量的地址曾经被动态库访问过,里面还残留着之前使用这个地址的局部变量,而这个值刚好就是pthread.so的link_map结构l_name的地址。这样,我们在调用recvmsg时,就把这些参数传给了内核,内核以为我们把msg_name的地址传给了它,于是它给我们返回了socket name,即内核往这个错误的地址上写了数据。那么,再往后就导致了segfault的问题的发生。

    现在来回顾下公司的编程规范,是不是这么一个简单的要求就可以避免后面我们做了那么多的debugging工作呢。

    展开全文
  • 本文简介本文主要介绍了Linux SLUB分配的产生原因、设计思路及其代码分析。适合于对Linux内核,特别是对Linux内存分配器感兴趣的读者。1.为何需要SLUB?Linux SLUB内存分配器合入Linux主分支已经整整10年了!并且是...
  • 所谓共享内存就是使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。其他进程能把同一段共享...
  • Linux线程栈内存总结

    2021-05-14 18:03:47
    虚拟内存、物理内存、anon内存的联系anon与线程的联系glibc源码库线程创建与销毁anon关系使用pmap分析虚拟地址空间以及anon内存何为线程栈空间泄露?【栈空间泄露】:简单了说就是,创建了线程,系统分配了内存,...
  • Linux程序内存越界定位分析总结

    千次阅读 2020-01-30 14:23:54
    问题描述:最近在工作中遇到这样一个奇葩问题,程序里面需要使用到一个.so库,同一份源码用我的电脑编译出来的库放到程序...问题1:定位踩内存的地方 现象为触发某个业务条件,会导致程序其他业务运行不正常,异...
  • 内核由于共享内存地址空间,如果没有合适的工具,很多踩内存的问题即使复现,也无法快速定位; 在新的内核版本中引入了一个新工具hardware breakpoint,其能够监视对指定的地址的特定类型(读/写)的数据访问,有利于该...
  • 踩内存专题分析

    千次阅读 2016-10-11 16:01:54
    先了解下kernel内存种类,才知道如何针对分析。 全局变量 这个最直接,直接在驱动里定义全局变量就可以拿来使用,不过要注意互斥访问。 内存是分配在.data/.bss段的。 局部变量 定义在函数内部的变量...
  • valgrind通常用来成分析程序性能及程序中的内存泄露错误一 Valgrind工具集简绍Valgrind包含下列工具:1、memcheck:检查程序中的内存问题,如泄漏、越界、非法指针等。2、callgrind:检测程序代码的运行时间和调用...
  • 常用工具汇总常用的内存分析工具PurifyPlus不支持常用的Linux 64系统[Ubuntu redhat],悲剧Valgrind简介MemcheckMemcheck 工具主要检查下面的程序错误:使用未初始化的内存(Use of uninitialised memory)使用已经...
  • 这里是我自己过的坑——项目需要测试DDR (1)在uboot的引导中,到了board_init()板级初始化时,uboot会执行DDR的初始化操作。我们可以在哪里获取到DDR总线的物理地址——假设为DDR_BASE (2)设备启动后,执行...
  • 目的:本文是《一种定位内存泄露的方法(Solaris)》对应的Linux版本,调试器使用gdb。主要介绍实例部分。其他请见《一种定位内存泄露的方法(Solaris)》。实例:模拟new失败的程序:#includeclass ABC{public:virtual ...
  • Linux内核程序员几乎每天都在和各种问题互相对峙: 内核崩溃了,需要排查原因。 系统参数不合适,需要更改,却没有接口。 改一个变量或一条if语句,就要重新编译内核。 想稍微调整下逻辑却没有源码,没法编译...
  • 官网Valgrind介绍是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。这些工具之中最有名的是Memcheck。它能够识别很多C或者C++程序中内存相关的错误,这些错误会导致程序崩溃或者出现不可预知的行为。 ...
  • 内核模块踩内存问题定位利器- hardware breakpoint https://blog.csdn.net/phenix_lord/article/details/41415559 Linux中mprotect()函数的用法 https://blog.csdn.net/roland_sun/article/details/33728955 Cache...
  • 有同事报他的机器上nginx存在内存泄露,都吃了4G内存没法忍了,于是赶紧查一查。问题定位1、先top -u work 查看进程内存占用情况,确认确实是占了4G没法忍了(下图只是整理文档时补的示例)。2、ps -ef | grep nginx |...
  • 内存泄露问题是c语言很容易出现的问题,小程序可以很容易的发现,但是大程序就比较难发现了。 内存泄露是由于动态分配的内存没有被释放,可以使用valgrind等工具检查出来。 常见导致内存泄露的函数有malloc、...
  • C/C++等底层语言在提供强大功能及性能的同时,其灵活的内存访问也带来了各种纠结的问题。如果crash的地方正是内存使用错误的地方,说明你人品好。如果crash的地方内存明显不是consistent的,或者内存管理信息都已被...
  • Valgrind通常用来成分析程序性能及程序中的内存泄露错误 一 Valgrind工具集简绍 Valgrind包含下列工具:  1、memcheck:检查程序中的内存问题,如泄漏、越界、非法指针等。  2、callgrind:检测...
  • 但是这种方法有个先天的劣势:程序跑死的点和内存的点往往不在同一个地方,需要分析代码寻找真正的问题点。如果程序只是逻辑出错没有跑死,定位起来会更加困难。 有没有方法可以让程序告诉我们是谁内存
  • 项目中由于各方面因素,总是有人抱怨存在...Valgrind是linux环境下的一款功能齐全的内存调试和性能分析工具集,它包括Memcheck、Callgrind、Cachegrind、Helgrind、Massif等工具。本文旨在介绍Valgrind工具集中的内存
  • Linux下利用Vagrind工具进行内存泄露检测和性能分析Linux下利用Valgrind工具进行内存泄露检测和性能分析[日期:2012-06-25] 来源:Linux社区 作者:yanghao23Valgrind通常用来成分析程序性能及程序中的内存泄露错误...
  • 内存泄露是我们在调试程序时经常遇到的问题,内存泄露分析软件也比较多,本文主要分析Valgrind工具的使用。 Valgrind的简介及安装 Valgrind官网下载: http://valgrind.org/downloads/current.html#current 目前最新...
  • 并且对共享内存,凡是进程过的共享内存都会被统计到RSS里(即使已不再使用),所以建议同时参考这两个值。 堆内存 通过malloc等分配的内存,现成的统计工具很多,推荐gperf的heap-profiler,用tcmalloc工具...
  • linux valgrind内存泄漏

    2018-12-29 16:53:52
    // error2:踩内存 free ( ptr ) ; // free ( ptr ) ; // error3:重复释放 int * p1 ; // error4:指针变量没初始化 * p1 = 1 ; // error5:非法指针 } int main ( void ) { test ( ) ; ...
  • Linux栈浅析

    2021-05-16 10:13:52
    调试时必需的栈知识栈(stack)是程序存放数据的内存区域之一,其特征是LIFO(Last In First Out, 后进先出)式数据结构,即后放进的数据最先被取出。向栈中存储数据的操作称为PUSH(压入),从栈中取出数据称为POP(弹出)...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,783
精华内容 1,513
关键字:

linux分析踩内存

linux 订阅