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 检测时候的不同功能, 还可以使用不同的命令进行内存的检查, 例如:
make_memory [noaccess|undefined|defined|Definedifaddressable] <addr> [<len>]
规定内存的方式形式以及权限, 将处的 (默认为 1)字节范围标记为具有给定状态。 参数 noaccess
将范围标记为不可访问,因此 Memcheck 将在对其进行任何访问时报告错误.undefined
或Defined
将该区域标记为可访问,但 Memcheck 分别将其中的字节视为具有未定义或已定义的值。Definedifaddressable
将范围内已可寻址的字节标记为已定义,但不会更改范围内不可寻址的字节的状态。 请注意,Definedifaddressable 的第一个字母是大写 D,以避免与 Defined 混淆.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
发起请求