Valgrind GDB使用示例

Valgrind 使用示例

在使用GDB 与 Valgrind 交互式调试的过程中, 程序本质上是运行在 Valgrind 的虚拟 CPU 上的, 而不是真实的CPU. 所以 Valgrind 不能和 Mesh 交互, Mesh 运行的时候,其他进程运行在真实 CPU 上,通过边车进行数据交互数据.

如果一个进程在 Valgrind 上面运行, 那么它就不能在为其他进程提供服务, 只能接受其他进程(应用) 通过边车传递来的消息.

客户端请求机制

客户端请求机制可以允许用户告诉 Valgrind 原本不知道的事情,从而获得不同的结果.

#include <stdio.h>
#include <valgrind/valgrind.h>

// 定义Client Request标识
#define MY_CLIENT_REQUEST 123

// 自定义的回调函数
void my_callback(void) {
    printf("Custom callback function called!\n");
}

int main() {
    // 注册回调函数与Client Request标识
    VALGRIND_DO_CLIENT_REQUEST_STMT(MY_CLIENT_REQUEST, my_callback());

    // 模拟需要检测的内存操作
    int *ptr = malloc(sizeof(int));
    *ptr = 42;

    // 触发Client Request
    VALGRIND_DO_CLIENT_REQUEST_STMT(MY_CLIENT_REQUEST, /* empty */);

    free(ptr);

    return 0;
}

实现往往是使用回调函数来实现的, 将我们定义的函数作为参数, 表示发起请求之后执行情况. 但是需要将 Valgrind 的代码嵌入到源代码中, 并且是在 Valgrind 的虚拟 CPU 上运行的, 同时不必担心在真实 CPU 的运行结果,插入的 Valgrind 代码不会对程序在真实的 CPU 上运行的结果造成影响.

例如我们想在程序运行的不同位置检查内存是否存在泄露, 我们可以在代码中插入检查内存的位置, 例如:

#include <valgrind/memcheck.h>
#include <stdio.h>
#include <stdlib.h>

// ...

void yourFunction(int ** data) {
    int a[100] = {0};
    for (int i = 0; i < 100; i++)
    {
        a[i] = i+i;
    }
    *data = (int *)malloc(5 * sizeof(int));  // 分配内存
    VALGRIND_DO_LEAK_CHECK;
    
    // 更多代码
}

int main() {
    // 初始化Valgrind
    VALGRIND_DO_LEAK_CHECK;
    int *data;
    yourFunction(&data);
    int *data2 = (int *)malloc(5 * sizeof(int));  // 分配内存
    // 更多代码
    VALGRIND_DO_LEAK_CHECK;
    free(data);
    VALGRIND_DO_LEAK_CHECK;
    return 0;
}

我们可以在程序的入口以及函数退出的位置检查内存的分配情况, 是否存在内存泄漏. 然后我们使用 valgrind --leak-check=full --show-leak-kinds=all ./memcheck_request 在 Valgrind 的虚拟 CPU 上运行这个程序, 可以得到如下的结果:

==27186== Command: ./memcheck_request
==27186== 
==27186== All heap blocks were freed -- no leaks are possible
==27186== 
==27186== 20 bytes in 1 blocks are still reachable in loss record 1 of 1
==27186==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==27186==    by 0x10947C: yourFunction (memcheck_request.c:13)
==27186==    by 0x1095AF: main (memcheck_request.c:23)
==27186== 
==27186== LEAK SUMMARY:
==27186==    definitely lost: 0 bytes in 0 blocks
==27186==    indirectly lost: 0 bytes in 0 blocks
==27186==      possibly lost: 0 bytes in 0 blocks
==27186==    still reachable: 20 bytes in 1 blocks
==27186==         suppressed: 0 bytes in 0 blocks
==27186== 
==27186== 20 bytes in 1 blocks are still reachable in loss record 1 of 2
==27186==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==27186==    by 0x10947C: yourFunction (memcheck_request.c:13)
==27186==    by 0x1095AF: main (memcheck_request.c:23)
==27186== 
==27186== 20 bytes in 1 blocks are still reachable in loss record 2 of 2
==27186==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==27186==    by 0x1095B9: main (memcheck_request.c:24)
==27186== 
==27186== LEAK SUMMARY:
==27186==    definitely lost: 0 bytes in 0 blocks
==27186==    indirectly lost: 0 bytes in 0 blocks
==27186==      possibly lost: 0 bytes in 0 blocks
==27186==    still reachable: 40 bytes in 2 blocks
==27186==         suppressed: 0 bytes in 0 blocks
==27186== 
==27186== 20 bytes in 1 blocks are still reachable in loss record 2 of 2
==27186==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==27186==    by 0x1095B9: main (memcheck_request.c:24)
==27186== 
==27186== LEAK SUMMARY:
==27186==    definitely lost: 0 bytes in 0 blocks
==27186==    indirectly lost: 0 bytes in 0 blocks
==27186==      possibly lost: 0 bytes in 0 blocks
==27186==    still reachable: 20 bytes in 1 blocks
==27186==         suppressed: 0 bytes in 0 blocks
==27186== 
==27186== 
==27186== HEAP SUMMARY:
==27186==     in use at exit: 20 bytes in 1 blocks
==27186==   total heap usage: 2 allocs, 1 frees, 40 bytes allocated
==27186== 
==27186== 20 bytes in 1 blocks are definitely lost in loss record 2 of 2
==27186==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==27186==    by 0x1095B9: main (memcheck_request.c:24)
==27186== 
==27186== LEAK SUMMARY:
==27186==    definitely lost: 20 bytes in 1 blocks
==27186==    indirectly lost: 0 bytes in 0 blocks
==27186==      possibly lost: 0 bytes in 0 blocks
==27186==    still reachable: 0 bytes in 0 blocks
==27186==         suppressed: 0 bytes in 0 blocks
==27186== 
==27186== For lists of detected and suppressed errors, rerun with: -s
==27186== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

可以看到, 我们每次对内存进行操作的时候, Valgrind 都会检测一次内存使用的情况, 使用的分配的内存块从一块到两块, 然后释放一块, 最后检测到一块未释放. 除了 VALGRIND_DO_LEAK_CHECK, Valgrind 还定义了各种各样的请求, 例如: VALGRIND_MAKE_MEM_[DEFINED | NOACCESS | UNDEFINED] 可以标记某一段内存被定义, 不可访问, 或者未被定义.

例如可以在源码中看到参数列表以及名称:

/* Mark memory at _qzz_addr as unaddressable for _qzz_len bytes. */
#define VALGRIND_MAKE_MEM_NOACCESS(_qzz_addr,_qzz_len)           \
    VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */,      \
                            VG_USERREQ__MAKE_MEM_NOACCESS,       \
                            (_qzz_addr), (_qzz_len), 0, 0, 0)

Valgrind 和 GDB 合作调试

GDB 可以调试不同机器上的进程, 使用 GDB 定义的协议通信, 传输的信息包括内存以及寄存器的值, 服务器端需要有一个 gdbserver 为 GDB 提供服务, 在 Vlagrind 中, Valgrind 提供的 gdbserver,是通过管道和一个名为 vgdb 的程序完成的,该程序充当中介. 如果没有使用 GDB,vgdb 还可以用于从 shell 命令行向 Valgrind gdbserver 发送监控命令.

GDB 连接 Valgrind 的 gdbserver

通常可以打开两个 Shell 页面, 然后使用如下命令在一个页面启动 Valgrind gdbserver

valgrind --tool=memcheck --vgdb=yes --vgdb-error=0 ./prog

我们可以看到类似下面的输出:

==23650== Memcheck, a memory error detector
==23650== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==23650== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==23650== Command: ./test_memory
==23650== 
==23650== (action at startup) vgdb me ... 
==23650== 
==23650== TO DEBUG THIS PROCESS USING GDB: start GDB like this
==23650==   /path/to/gdb ./test_memory
==23650== and then give GDB the following command
==23650==   target remote | /usr/lib/x86_64-linux-gnu/valgrind/../../bin/vgdb --pid=23650
==23650== --pid is optional if only one valgrind process is running

表示程序正在等待另一个 Shell 启动 GDB, 我们可以用命令 /path/to/gdb ./test_memory 调试进程 test_memory. 进入 GDB 调试窗口之后, 使用命令 target remote | vgdb 设置与 Valgrind 交互调试. 我们使用下面的例子说明:

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

int main() {
    int *data = (int *)malloc(5 * sizeof(int));  // 分配内存

    // 存储数据
    for (int i = 0; i < 5; i++) {
        data[i] = i * 10;
    }

    // 程序结束时忘记释放内存
    int flag = 0;
    scanf("%d", &flag);
    if(flag == 2) {
        free(data);
    }
    return 0;
}

在使用 GDB 调试的时候需要注意将输入重定向, 因为Valgrind对程序进行检测时,会对程序的内存访问和系统调用进行跟踪。这可能会导致与直接在控制台输入数据时不同的程序行为。某些程序在与Valgrind一起使用时,可能会无法从控制台接收输入,因为Valgrind的跟踪可能会干扰输入流的正常工作. 在我们的例子中, 使用的例子是: 将输入重定向到文件中, 然后使用下面的命令:

 valgrind --tool=memcheck --vgdb=yes --vgdb-error=0 ./test_memory < input.txt

进入之后, 执行如下:

Type "apropos word" to search for commands related to "word"...
Reading symbols from ./test_memory...
(gdb) target remote | vgdb
Remote debugging using | vgdb
relaying data between gdb and process 6202
warning: remote target does not support file transfer, attempting to access files from local filesystem.
Reading symbols from /lib64/ld-linux-x86-64.so.2...
Reading symbols from /usr/lib/debug/.build-id/45/87364908de169dec62ffa538170118c1c3a078.debug...
0x0000000004001100 in _start () from /lib64/ld-linux-x86-64.so.2
(gdb) break 13
Breakpoint 1 at 0x109208: file /home/windy/Valgrind_test/test_memory.c, line 13.
(gdb) continue
Continuing.

Breakpoint 1, main () at /home/windy/Valgrind_test/test_memory.c:13
13          int flag = 0;
(gdb) n
14          scanf("%d", &flag);
(gdb) n
15          if(flag == 2) {
(gdb) n
18          return 0;
(gdb) n
19      }
(gdb) n

对不同的输入, Valgrind 检测的输出也会不同,

分别是检测到内存泄漏与内存完全没有问题:

==6112== HEAP SUMMARY:
==6112==     in use at exit: 0 bytes in 0 blocks
==6112==   total heap usage: 2 allocs, 2 frees, 4,116 bytes allocated
==6112==
==6112== All heap blocks were freed -- no leaks are possible
==6112==
==6112== For lists of detected and suppressed errors, rerun with: -s
==6112== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

==6202== HEAP SUMMARY:
==6202==     in use at exit: 20 bytes in 1 blocks
==6202==   total heap usage: 2 allocs, 1 frees, 4,116 bytes allocated
==6202==
==6202== LEAK SUMMARY:
==6202==    definitely lost: 20 bytes in 1 blocks
==6202==    indirectly lost: 0 bytes in 0 blocks
==6202==      possibly lost: 0 bytes in 0 blocks
==6202==    still reachable: 0 bytes in 0 blocks
==6202==         suppressed: 0 bytes in 0 blocks
==6202== Rerun with --leak-check=full to see details of leaked memory

还可以在 GDB 中实时的查看当前断点处的内存分配情况, 例如设置断点之后, 在当前断点处(可以是任意程序执行到的地方) 执行如下命令检查当前的内存分配情况,

Breakpoint 1, main () at /home/windy/Valgrind_test/test_memory.c:13
13          int flag = 0;
(gdb) monitor leak_check full reachable any
==22872== 20 bytes in 1 blocks are still reachable in loss record 1 of 1
==22872==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==22872==    by 0x1091CD: main (test_memory.c:5)
==22872==
==22872== LEAK SUMMARY:
==22872==    definitely lost: 0 bytes in 0 blocks
==22872==    indirectly lost: 0 bytes in 0 blocks
==22872==      possibly lost: 0 bytes in 0 blocks
==22872==    still reachable: 20 bytes in 1 blocks
==22872==         suppressed: 0 bytes in 0 blocks

在上图的例子中, 在第 13 行设置了断点, 并且在此检查内存的分配情况, 可以看到 0x483B7F3 处分配的内存还没有释放.

根据在使用 Valgrind 检测时候的不同功能, 还可以使用不同的命令进行内存的检查, 例如:

  1. make_memory [noaccess|undefined|defined|Definedifaddressable] <addr> [<len>] 规定内存的方式形式以及权限, 将 处的 (默认为 1)字节范围标记为具有给定状态。 参数 noaccess 将范围标记为不可访问,因此 Memcheck 将在对其进行任何访问时报告错误. undefinedDefined 将该区域标记为可访问,但 Memcheck 分别将其中的字节视为具有未定义或已定义的值。 Definedifaddressable 将范围内已可寻址的字节标记为已定义,但不会更改范围内不可寻址的字节的状态。 请注意,Definedifaddressable 的第一个字母是大写 D,以避免与 Defined 混淆.
  2. check_memory [addressable|defined] <addr> [<len>] 可以检查地址 <addr> 处长度为 <len> 的内存的访问权限, 是否可以访问, 在哪里被定义, 例如在上面的例子中, 我们可以看到 :
(gdb) monitor check_memory defined 0x483B7F3 1
Address 0x483B7F3 len 1 defined
 Address 0x483b7f3 is in the Text segment of /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so
==24721==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)

0x483B7F3 也就是我们 malloc 的内存是已经被定义了的.

其他进程与 Valgrind 交互的几种方式

  • 使用标准的 vgdb, 或者使用 GDB 与其交互, 在 GDB 中使用 Valgrind 的 gdbserver
  • 直接使用 GDB 中的 "valgrind" front end commands
  • 使用客户端请求程序, 在程序中使用 VALGRIND_MONITOR_COMMAND 函数向Valgrind发起请求
posted @ 2023-09-06 19:16  虾野百鹤  阅读(186)  评论(0编辑  收藏  举报