Linux下core dump及valgrind学习

    技术2022-07-11  129

    reference

    目录

    valgrindvalgrind quick startmemcheck 错误信息解释1 非法读写错误2 在系统调用中使用未初始化或无法寻址的值3 error `Address 0xa2edd18 is 0 bytes after a block of size 8 alloc'd`4 使用未初始化的值

    valgrind

    Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。 对于结构复杂的程序,如涉及模板类及复杂的调用,gdb得出了出错位置,似乎这还不够,这时候要使用更为专业的工具——valgrind。 valgrind是一款专门用作内存调试,内存泄露检测的开源工具软件,valgrind这个名字取自北欧神话英灵殿的入口,不过,不能不承认,它确实是Linux下做内存调用分析的神器。一般Linux系统上应该没有自带valgrind,需要自行进行下载安装。 下载地址:http://valgrind.org/downloads/current.html 进入下载文件夹,分别执行(需要root权限,且必须按默认路径安装,否则有加载错误):

    ./configure make make install

    安装成功后,使用类似如下命令启动程序:

    valgrind --tool=memcheck --leak-check=full --track-origins=yes --leak-resolution=high --show-reachable=yes --log-file=memchecklog ./controller_test

    其中,–log-file=memchecklog指记录日志文件,名字为memchecklog;–tool=memcheck和–leak-check=full用于内存检测。 可以得到类似的记录:

    ==23735== ==23735== Thread 1: ==23735== Invalid read of size 4 ==23735== at 0x804F327: ResourceHandler<HBMessage>::~ResourceHandler() (ResourceHandler.cpp:48) ==23735== by 0x804FDBE: ConnectionManager<HBMessage>::~ConnectionManager() (ConnectionManager.cpp:74) ==23735== by 0×8057288: MainThread::~MainThread() (MainThread.cpp:73) ==23735== by 0x8077B2F: main (Main.cpp:177) ==23735== Address 0×0 is not stack’d, malloc’d or (recently) free’d ==23735==

    可以看到说明为无法访问Address 0x0,明显为一处错误。

    这样valgrind直接给出了出错原因以及程序中所有的内存调用、释放记录,非常智能,在得知错误原因的情况下,找出错误就效率高多了。

    再说一句,valgrind 同时给出了程序的 Memory Leak情况的报告,给出了new-delete对应情况,所有泄漏点位置给出,这一点在其他工具很难做到,十分好用。

    valgrind quick start

    valgrind quick start

    编译准备 使用-g编译程序以包含调试信息,以便Memcheck的错误消息包含确切的行号。如果可以忍受减速,则使用-O0也是一个好主意。在错误消息中使用-O1行号可能是不准确的,尽管通常来说,在-O1编译的代码上运行Memcheck效果很好,并且与运行-O0相比,速度提高非常明显。不建议使用-O2及更高版本,因为Memcheck偶尔会报告实际上不存在的未初始化值错误。使用Memcheck运行程序 通常运行命令: myprog arg1 arg2

    改为:

    valgrind --leak-check=yes myprog arg1 arg2

    Memcheck是默认工具。 --leak-check选项打开详细的内存泄漏检测器。 您的程序将比正常运行慢得多(例如20到30倍),并且使用更多的内存。 Memcheck将发出有关内存错误和它检测到的泄漏的消息。

    3.解析Memcheck的输出 示例程序:

    #include <stdlib.h> void f(void) { int* x = malloc(10 * sizeof(int)); x[10] = 0; // problem 1: heap block overrun } // problem 2: memory leak -- x not freed int main(void) { f(); return 0; }

    大多数错误消息如下所示,描述了问题1,堆块溢出:

    ==19182== Invalid write of size 4 ==19182== at 0x804838F: f (example.c:6) ==19182== by 0x80483AB: main (example.c:11) ==19182== Address 0x1BA45050 is 0 bytes after a block of size 40 alloc'd ==19182== at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130) ==19182== by 0x8048385: f (example.c:5) ==19182== by 0x80483AB: main (example.c:11)

    注意事项: 每个错误消息中都有很多信息;仔细阅读。 19182是进程ID;通常不重要。 第一行(“无效写入…”)告诉您这是一种错误。在这里,由于堆块溢出,程序写入了一些不应该有的内存。 第一行下面是一个堆栈跟踪,告诉您问题出在哪里。堆栈跟踪可能会变得很大,并且会造成混乱,尤其是在使用C ++ STL的情况下。从头开始阅读它们会有所帮助。如果堆栈跟踪不够大,请使用–num-callers选项将其增大。 代码地址(例如0x804838F)通常并不重要,但有时对于跟踪怪异的bug至关重要。 一些错误消息具有第二个组件,该组件描述了所涉及的内存地址。这表明在example.c的第5行上,写入的内存刚好超过了使用malloc()分配的块的末尾。

    按照报告顺序修复错误是有效的,因为以后的错误可能是由先前的错误引起的。未能做到这一点是Memcheck遇到困难的常见原因。 内存泄漏消息如下所示:

    ==19182== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==19182== at 0x1B8FF5CD: malloc (vg_replace_malloc.c:130) ==19182== by 0x8048385: f (a.c:5) ==19182== by 0x80483AB: main (a.c:11)

    堆栈跟踪告诉您泄漏内存的分配位置。不幸的是,Memcheck无法告诉您为什么内存泄漏。 (忽略“ vg_replace_malloc.c”,这是一个实现细节。) 两个最重要的泄漏类别是: “绝对丢失”:您的程序正在泄漏内存-对其进行修复! “可能丢失”:您的程序正在泄漏内存,除非您使用指针做一些有趣的事情(例如将它们移至指向堆块的中间)。 Memcheck还报告未初始化值的使用,最常见的消息是“有条件的跳转或移动取决于未初始化值”。确定这些错误的根本原因可能很困难。尝试使用–track-origins = yes获取更多信息。这使Memcheck的运行速度变慢,但是您经常获得的额外信息可以节省大量时间来确定未初始化值的来源。 如果您不理解错误消息,请查阅Valgrind用户手册中的Memcheck的错误消息说明,其中提供了Memcheck产生的所有错误消息的示例。

    memcheck 错误信息解释

    1 非法读写错误

    Invalid read of size 4 at 0x40F6BBCC: (within /usr/lib/libpng.so.2.1.0.9) by 0x40F6B804: (within /usr/lib/libpng.so.2.1.0.9) by 0x40B07FF4: read_png_image(QImageIO *) (kernel/qpngio.cpp:326) by 0x40AC751B: QImageIO::read() (kernel/qimage.cpp:3621) Address 0xBFFFF0E0 is not stack'd, malloc'd or free'd

    当您的程序在Memcheck认为不应该的位置读取或写入内存时,会发生这种情况。在此示例中,程序在系统提供的库libpng.so.2.1.0.9中某个位置的地址0xBFFFF0E0处进行了4字节读取,该库是从同一个库中的其他位置(从qpngio.cpp的第326行调用)调用的, 等等。

    Memcheck尝试确定非法地址可能与之相关,因为这通常很有用。因此,如果它指向已经释放的内存块,则会通知您这一点以及释放该块的位置。同样,如果它刚好位于堆块的末尾,这是数组下标出现一次错误的常见结果,那么您将被告知这一事实以及分配块的位置。如果使用–read-var-info选项,则Memcheck的运行速度会更慢,但可能会提供有关任何非法地址的更详细说明。

    在此示例中,Memcheck无法识别地址。该地址实际上在堆栈上,但是由于某种原因,这不是有效的堆栈地址,它位于堆栈指针下方,因此是不允许的。在这种情况下,可能是因为GCC生成了无效代码,这是某些古老版本的GCC中已知的错误。

    请注意,Memcheck仅告诉您程序将要在非法地址访问内存。它不能阻止访问的发生。因此,如果您的程序进行的访问通常会导致分段错误,那么您的程序仍然会遭受同样的命运-但您会在此之前立即收到Memcheck的消息。在此特定示例中,读取堆栈上的垃圾是不严重的,程序将保持活动状态。

    2 在系统调用中使用未初始化或无法寻址的值

    Memcheck检查系统调用的所有参数:

    它检查所有直接参数本身,是否已初始化。另外,如果需要从程序提供的缓冲区中读取系统调用,Memcheck会检查整个缓冲区是否可寻址,并且其内容已初始化。另外,如果系统调用需要写入用户提供的缓冲区,则Memcheck会检查缓冲区是否可寻址。

    系统调用之后,Memcheck更新其跟踪信息以精确反映系统调用引起的内存状态的任何变化。

    这是两个带有无效参数的系统调用的示例:

    #include <stdlib.h> #include <unistd.h> int main( void ) { char* arr = malloc(10); int* arr2 = malloc(sizeof(int)); write( 1 /* stdout */, arr, 10 ); exit(arr2[0]); }

    获得如下抱怨。。。

    Syscall param write(buf) points to uninitialised byte(s) at 0x25A48723: __write_nocancel (in /lib/tls/libc-2.3.3.so) by 0x259AFAD3: __libc_start_main (in /lib/tls/libc-2.3.3.so) by 0x8048348: (within /auto/homes/njn25/grind/head4/a.out) Address 0x25AB8028 is 0 bytes inside a block of size 10 alloc'd at 0x259852B0: malloc (vg_replace_malloc.c:130) by 0x80483F1: main (a.c:5) Syscall param exit(error_code) contains uninitialised byte(s) at 0x25A21B44: __GI__exit (in /lib/tls/libc-2.3.3.so) by 0x8048426: main (a.c:8)

    因为程序已(a)将未初始化的垃圾从堆块写入标准输出,并且(b)传递了未初始化的值退出。请注意,第一个错误是指buf指向的内存(不是buf本身),但是第二个错误直接涉及到出口的参数arr2 [0]。

    3 error Address 0xa2edd18 is 0 bytes after a block of size 8 alloc'd

    发生于使用realloc出错。 示例

    if (*filename == NULL ) { *filename = (char*)realloc(*filename, strlen(*collection_name)*sizeof(char)+4); strcpy(*filename, *collection_name); strcat(*filename, ".tde"); # line 69 }

    strcpy会添加在字符串后面添加‘\0’,需要在记得申请空间给它。

    *filename = (char*)realloc(*filename, strlen(*collection_name)*sizeof(char)+5);

    上述代码有一个常见的问题,将重新分配的结果直接分配给要重新分配的指针。重新分配成功时,这很好,但失败时会导致内存泄漏。要解决此错误,需要将realloc的结果存储在一个单独的变量中,并在将值分配回* filename之前检查它是否为NULL:

    char *tmp = (char*)realloc(*filename, strlen(*collection_name)*sizeof(char)+5); if (tmp != NULL) { *filename = tmp; } else { // Do something about the failed allocation }

    直接分配给* filename会导致内存泄漏,因为* filename指向下面的指针将在失败时被覆盖,变得不可恢复。 假设该块起始于0x8000,长度为8个字节。那么该块的最后一个有效地址将是0x8007,而0x8008将是其后的第一个无效地址。当valgrind在0x8008处看到写操作时,会将其报告为对非法块的初始字节的写操作,就好像它是字节数组一样,并使用从零开始的符号来报告偏移量。

    4 使用未初始化的值

    Use of uninitialised values;

    Conditional jump or move depends on uninitialised value(s) at 0x402DFA94: _IO_vfprintf (_itoa.h:49) by 0x402E8476: _IO_printf (printf.c:36) by 0x8048472: main (tests/manuel1.c:8)

    当您的程序使用尚未初始化的值时,即未定义的值时,将报告未初始化的值使用错误。在这里,未定义的值在C库的printf机械内部的某处使用。运行以下小程序时,报告了此错误:

    int main() { int x; printf ("x = %d\n", x); }

    重要的是要了解您的程序可以随意复制垃圾数据(未初始化的数据)。 Memcheck会观察到这一点并跟踪数据,但不会抱怨。仅当您的程序试图以可能会影响程序的外部可见行为的方式使用未初始化的数据时,才会发出投诉。在此示例中,x未初始化。 Memcheck会观察到传递给_IO_printf的值,然后传递给_IO_vfprintf的值,但不作任何评论。但是,_IO_vfprintf必须检查x的值,以便可以将其转换为相应的ASCII字符串,而此时Memcheck抱怨。

    未初始化数据的来源通常是:

    如上例所示,未初始化的过程中的局部变量。在您(或构造函数)在其中写入内容之前,堆块的内容(与malloc,new或类似的函数一起分配)。 要查看程序中未初始化数据源的信息,请使用–track-origins = yes选项。这使Memcheck的运行速度更慢,但使查找未初始化的值错误的根本原因变得容易得多。 示例 char * m_pBuffer = NULL; m_pBuffer = (char*) malloc(MAX_FILE_LENGTH * sizeof(char)); // std::string str=m_pBuffer;

    malloc 获取的内存地址上可能有其他垃圾数据存留,此时将char* 传递给string ,由于未明确定义字符串结束符'\0' ,会产生使用未初始化的值的错误。 变更为

    char * m_pBuffer = NULL; m_pBuffer = (char*) malloc(MAX_FILE_LENGTH * sizeof(char)); memset(m_pBuffer, 0x00, MAX_FILE_LENGTH * sizeof(char)); std::string str=m_pBuffer;
    Processed: 0.011, SQL: 10