一、输出信息重定向:
默认情况下,valgrind输出的信息会重定向到标准错误输出流(stderr,fd=2)。但有时我们往往需要将输出信息重定向到指定文件,有以下几种方式:
1. --log-fd=N:
通过这种方式直接将输出信息重定向到fd=N的文件中。
2. --log-file=filename:
将输出重定向到filename指向的文件。
3. --log-socket=host_ip:port:
举例:--log-socket=192.168.0.1:18888 或者 --log-socket=192.168.0.1
通过这种方式可以将输出重定向到一台远程主机。如果端口号未填写,则默认1500。对于这种方式,valgrind不支持使用host_name,只能使用host_ip。
valgrind还支持添加listener,可以同时处理50个valgrind进程的输出。
二、屏蔽错误信息:
我们的程序一般都会依赖一些系统库或者C运行时库。在使用valgrind进行memcheck时,会产生大量关于这些运行时库的错误信息,而这些信息通常是我们无法处理的。
valgrind会在启动时,读取一个默认的suppresion file,屏蔽里面添加的错误。suppression file是在运行configure脚本时产生的。你可以根据自身项目需要,修改suppression file。
详情可以查阅官方文档2.5节Suppressing Error。
三、关键命令行选项:
1. Tool-selection Option:
--tool=
目前valgrind可以使用的工具包括:memcheck、cachegrind,callgrind,helgrind,drd,massif,lackey,none,exp-sgcheck,exp-bbv,exp-dhat等。
2. Basic Options:
以下只列出一些相关关键的命令行选项。
a. -v, --verbose:
输出更为详尽的提示信息,比如,所加载的共享对象的信息(the shared objects loaded)、输出屏蔽信息(the suppressions used)、注入(instrumentation)和执行(execution)引擎的工作过程,以及其他无用的告警。
===== 以下开关跟子进程相关 =====
b. --trace-children=
如果这个开关打开,valgrind将会跟踪当前进程创建的子进程。子进程必须通过exec()系统调用创建,目前valgrind无法对通过fork()创建出来的子进程进行跟踪。
c. --trade-children-skip=patt1,patt2,...
当--trace-children=yes时,这个开关允许valgrind不对满足条件(patt1,patt2,....)的当前进程的子进程进行跟踪。这个也很好理解,因为被调试的进程可能会创建若干子进程,而我们并非关心全部子进程。
补充说明:patt1,patt2实际上是进程的名字,支持?和*通配符。
d. --child-silent-after-fork=
如果开启这个开关,valgrind不会输出任何由fork()创建出来的子进程。这个开关通常可以避免子进程输出信息混淆父进程输出信息。
===== 以下开关跟GDB相关 =====
e. --vgdb=
当--vgdb=yes或full时,valgrind将提供gdbserver的功能,允许GDB调试运行在valgrind之上的目标进程。(后面的章节会详细说明)
f. --vgdb-error=
当--vgdb=yes或full时,这个开关才能生效。valgrind检测到的error个数达到指定number时,将挂起目标程序,并允许用户通过GDB调试目标程序。
如果number指定为0,在valgrind运行目标程序前,会先启动gdbserver。这个很有用,可以允许用户在目标程序启动前,向目标程序插入断点(breakpoints)。
g. --vgdb-stop-at=
当--vgdb=yes或full时,这个开关才能生效。通常,当valgrind检测到的错误数量达到--vgdb-error指定的错误数时,之后每触发一个错误,gdbserver都将会被调用(invoked)。
通常,valgrind还允许用户在指定的事件发生时,触发针对gdbserver的调用。如下所示:
*** 在目标程序启动(startup)、目标程序退出(exit),以及valgrind异常终止(valgrindabexit)时触发。
注意:startup和--vgdb-error=0都会导致gdbserver在目标程序启动之前被调用。二者之间的不同点在于,--vgdb-error=0还会导致目标程序在启动之后每一个错误发生时,都触发gdbserver的调用。
*** --vgdb-stop-at=all 等价于 --vgdb-stop-at=startup,exit,valgrindabexit
*** --vgdb-stop-at=none (默认情况)
3. Erro-related Options:
a. --demangle=
C++中默认会对符号进行名称修饰(name mangling)。默认情况下--demangle=yes,valgrind会将符号还原为未经过名称修饰前的样子。
b. --num-callers=
这个参数主要指定调用链(call chain)嵌套的深度。默认情况下,valgrind能够识别的调用链嵌套深度为12,能够支持的最大值为500。当--num-caller=500时,valgrind运行速度会下降,内存占用也会上升。对于调用链嵌套很深的程序,这个选项比较有用。
c. --sigill-diagnostics=
这个参数主要用于在发生SIGILL信号时,valgrind是否输出必要的诊断信息。通常,使用valgrind调试目标程序时,当valgrind无法decode或translate一条特定的指令时,将产生SIGILL信号。产生SIGILL的根源有两种情况,第一,目标程序本身存在bug;第二,目标程序使用了valgrind无法识别的特殊指令。
d. --show-below-main=
这个参数如果设置为yes,将会输出C/C++运行时库中检测到的错误。通常,我们并不需要关心运行时库中的错误。
e. --fullpath-after=
默认情况下,valgrind在输出堆栈信息时(statck traceback)仅输出文件名。对于大型工程,不同模块的源代码处于不同目录,可能会带来不便。通过这个参数,用户可以指定源代码所在路径,这样在valgrind输出调用栈信息时,会输出文件所在路径信息。
f. --extra-debuginfo-path=
在Linux发布版本中,系统库一般是不带调试信息的。确切说,二进制文件和调试信息本身是分离的。默认情况下,valgrind会扫描/usr/lib/debug目录,以查找系统库对应的默认调试信息。但是,有些情况下,用户依然系统自己指定二进制文件对应的调试信息目录,此时,这个命令行参数就能派上用场。
g. --max-stackframe=
指定堆栈上每一个栈帧(stack frame)大小上限。如果堆栈指针(stack pointer, esp/rsp)移动超过这个阈值,valgrind将假设程序将切换到另一个栈帧。
一般情况下,如果一个程序会在堆栈上分配一个很大的数组,则在使用valgrind调试时,可能需要设置这个参数。当然,在栈上分配很大的数组通常不是一个好的做法,因为很容易将堆栈空间耗尽。另外,valgrind memcheck工具针对堆的内存检测,要比栈空间更高效。
h. --main-stacksize=
指定主线程的堆栈大小。为了简化内存管理,valgrind在启动时,为主线程调用栈预留全部内存空间。这使得在启动时必须确定需要多大的栈空间。
默认情况下,valgrind将使用ulimit中的stack size,或者16MB,作为默认值。
注意:--main-stacksize和--main-stackframe时有差别的。前者代表当前线程整个堆栈需要多大的内存空间。后者代表堆栈上某一栈帧最大允许字节。
i. --max-threads=
默认情况下,valgrind可以同时处理最多500个线程。
4. Malloc-related Options:
对于Valgrind中的Memcheck、Massif、Helgrind、DRD等工具,往往需要使用自己定义的malloc、realloc版本。因此,可能会涉及以下选项:
a. --alignment=
对于Valgrind中使用的malloc、realloc,默认情况下,其分配的内存的起始地址为8字节或16字节对齐的。具体时8字节对齐,还是16字节对齐,跟具体的平台相关。该命令行选项允许用户指定一个不同的对齐方式,其中number必须必默认的对齐方式(即8字节或16字节)要大,并且必须小于等于4096。此外,还必须是2的倍数。
b. --redzone-size=
Valgrind中使用的malloc和realloc,往往会在每一个从堆(Heap)上分配的bock之前及之后,插入padding block。这些padding block也被称为red zone。Red zone的大小默认16字节。可见,默认情况下,valgrind能够检测出当前block向前及向后16字节内内存读写错误。
本选项可以设置padding block的大小。将该配置设置得过大,会消耗更多的内存。
四、关于多线程:
对于多线程程序,valgrind实际上会将所有线程的执行串行化。也就是说,即使运行环境拥有多CPU或者多核,对于运行在valgrind之上的(多线程)程序,同时只会有一个线程运行,即同时只会占据CPU的一个核。这样做主要是为了简化valgrind设计,避免因考虑多线程而引入不必要的设计上的复杂度。
Valgrind实际上并不直接调度线程。仅仅是通过一种简单的锁机制,来确保同时只有一个线程运行。真正的线程调度还是由操作系统内核控制。因此,目标程序运行在Valgrind下,同直接运行在操作系统之上相比,将会有截然不同的行为。因为Valgrind将目标程序串行化,因此目标程序运行于Valgrind之上时,将会比正常运行慢很多。
关于Valgrind线程调度及多线程性能
线程占据CPU执行之前,首先必须获取锁。执行完一定数量指令之后,运行线程释放锁。其他就绪线程将继续争抢锁。
命令行选项 --fair-sched 用来设置valgrind使用的锁机制,用来串行化目标程序中所有线程。
默认情况下,--fair-sched=no,此时valgrind采用基于Pipe的锁机制,适用于所有平台。这种机制不保证线程之间公平性,极有可能出现一个运行线程释放锁后立马又获取锁,即使此刻其他线程已经就绪。使用这种调度策略,目标程序每次运行都将产生截然不同的行为。
当 --fair-sched=yes 或 --fair-sched=try时,valgrind将采用基于mutex的锁机制,仅适用于部分平台。基于mutex的锁机制可以确保线程间公平调度(Round-Robin,轮询),如果多个线程同时就绪,则锁将分配给第一个就绪的线程。注意,阻塞在系统调用的线程不能请求占用锁,只有从系统调用唤醒后,才能请求占用锁。