目录

3.1。客户端请求机制3.2。使用Valgrind gdbserver和GDB调试程序
3.2.1。快速入门:3步调试3.2.2。Valgrind gdbserver整体组织3.2.3。将GDB连接到Valgrind gdbserver3.2.4。连接到Android gdbserver3.2.5。监视Valgrind gdbserver的命令处理3.2.6。Valgrind gdbserver线程信息3.2.7。检查和修改Valgrind影子寄存器3.2.8。Valgrind gdbserver的限制3.2.9。vgdb命令行选项3.2.10。Valgrind监视器命令
3.3。功能包装
3.3.1。一个简单的例子3.3.2。包装规格3.3.3。包装语义3.3.4。调试3.3.5。限制 - 控制流程3.3.6。限制 - 原始功能签名3.3.7。例子

本章介绍了Valgrind核心服务的高级方面,主要是为希望以某些有用的方式自定义和修改Valgrind的默认行为的用户感兴趣。涵盖的主题是:

  • “客户请求”机制

  • 使用Valgrind的gdbserver和GDB调试程序

  • 功能包装

3.1。客户端请求机制

Valgrind具有一个陷门机制,通过该机制,客户端程序可以将所有方式的请求和查询传递给Valgrind和当前工具。在内部,这广泛用于使各种事情发挥作用,尽管从外部看不到。

为了方便起见,提供了这些所谓的客户端请求的一部分,以便您可以告诉Valgrind有关程序行为的事实,也可以进行查询。特别是,您的程序可以告诉Valgrind有关其他方面不会知道的事情,从而获得更好的结果。

客户端需要包含头文件才能使其工作。哪个头文件取决于您使用的客户端请求。一些客户端请求由内核处理,并在头文件中定义valgrind/valgrind.h。工具特定的头文件以工具命名,例如 valgrind/memcheck.h。每个特定于工具的头文件都包含在内valgrind/valgrind.h,因此如果您包含特定于工具的头,则不需要将其包含在客户端中。所有头文件可以在安装include/valgrindValgrind的目录中找到。

这些头文件中的宏具有魔术属性,它们可以生成Valgrind可以发现的代码。但是,代码在Valgrind上运行时不执行任何操作,因此您不会因为使用此文件中的宏而被迫在Valgrind下运行程序。此外,您不需要将程序与任何额外的支持库链接。

添加到您的二进制代码的代码可以忽略不计的性能影响:在x86,amd64,ppc32,ppc64和ARM上,开销是6个简单的整数指令,除非是紧密循环,否则可能无法检测。但是,如果您真的希望编译客户端请求,可以使用-DNVALGRIND(类似于 -DNDEBUG“效果” assert)进行编译。

我们鼓励您将标题复制valgrind/*.h到项目的包含目录中,这样您的程序就没有编译时依赖于正在安装的Valgrind。Valgrind标题与其他代码大部分不同,是BSD风格的许可证,所以您可以不必担心许可证不兼容。

以下是对可用宏的简要说明 valgrind.h,可以使用多种工具(请参阅特定于工具的文档以了解工具特定的宏)。

RUNNING_ON_VALGRIND

如果在Valgrind上运行,则返回1,如果在实际CPU上运行则返回0。如果您运行Valgrind本身,则返回您运行的Valgrind仿真层数。

VALGRIND_DISCARD_TRANSLATIONS

丢弃指定地址范围内的代码翻译。如果您正在调试JIT编译器或其他动态代码生成系统,则很有用。在此调用之后,尝试在无效地址范围内执行代码将导致Valgrind对该代码进行新的翻译,这可能是您想要的语义。请注意,代码无效是昂贵的,因为快速找到所有相关的翻译是非常困难的,所以尽量不要经常打电话。请注意,您可以很聪明:只有当以前包含代码的区域被新的代码覆盖时,才需要调用它。您可以选择将代码写入新鲜的内存,并且偶尔调用这些代码一次丢弃大量的旧代码。

或者,对于ppc32 --smc-check=all/ Linux,ppc64 / Linux或ARM / Linux 的透明自修改代码支持,使用或运行。

VALGRIND_COUNT_ERRORS

返回Valgrind到目前为止发现的错误数。与--log-fd=-1选项结合使用时,可用于测试工具代码 ; 这个默认运行Valgrind,但客户端程序可以检测何时发生错误。仅对报告错误的工具有用,例如它对Memcheck有用,但对于Cachegrind,它总是返回零,因为Cachegrind不报告错误。

VALGRIND_MALLOCLIKE_BLOCK

如果您的程序管理自己的内存,而不是使用标准的mallocnewnew[],则跟踪有关堆块的信息的工具不会做得很好。例如,Memcheck将不会检测到几乎相同的错误,并且错误消息将不会提供信息。为了改善这种情况,在您的自定义分配器分配一些新内存之后使用此宏。有关valgrind.h如何使用它的信息,请参阅注释 。

VALGRIND_FREELIKE_BLOCK

这应该与之配合使用 VALGRIND_MALLOCLIKE_BLOCK。再次看看valgrind.h有关如何使用它的信息。

VALGRIND_RESIZEINPLACE_BLOCK

通知Valgrind工具,分配的块的大小已被修改,但不是其地址。查看valgrind.h有关如何使用它的更多信息。

VALGRIND_CREATE_MEMPOOL, VALGRIND_DESTROY_MEMPOOL, VALGRIND_MEMPOOL_ALLOC, VALGRIND_MEMPOOL_FREE, VALGRIND_MOVE_MEMPOOL, VALGRIND_MEMPOOL_CHANGE, VALGRIND_MEMPOOL_EXISTS

这些类似于 VALGRIND_MALLOCLIKE_BLOCK和 VALGRIND_FREELIKE_BLOCK ,但偏向于使用内存池的代码定制。有关详细说明,请参阅 内存池。

VALGRIND_NON_SIMD_CALL[0123]

在客户端程序中执行实际 CPU 上的功能 ,而不是Valgrind正常运行代码的虚拟CPU。该函数必须采用一个整数(保持线程ID)作为第一个参数,然后将0,1,2或3个参数(取决于使用哪个客户端请求)。这些在Valgrind内部以各种方式使用。它们可能对客户端程序有用。

警告:只有使用这些,如果你 真的知道你在做什么。它们并不完全可靠,可能导致Valgrind崩溃。查看 valgrind.h更多详情。

VALGRIND_PRINTF(format, ...)

将Printf样式的消息打印到Valgrind日志文件。该消息以一对**标记之间的PID为前缀 。(与所有客户端请求一样,如果客户端程序未在Valgrind下运行,则不会输出任何内容。)直到遇到换行符或后续的Valgrind输出打印时才会产生输出; 这允许您通过多个呼叫建立单行输出。返回输出的字符数,不包括PID前缀。

VALGRIND_PRINTF_BACKTRACE(format, ...)

喜欢VALGRIND_PRINTF(特别是返回值是相同的),但是之后立即打印一个堆栈回溯。

VALGRIND_MONITOR_COMMAND(command)

执行给定的monitor命令(一个字符串)。如果识别到命令,则返回0。如果无法识别命令,则返回1。请注意,某些监视器命令提供对通过特定客户端请求可访问的功能的访问。例如,可以使用VALGRIND_DO_LEAK_CHECK或通过监视器命令“leak_search”从客户端程序请求memcheck泄漏搜索。请注意,命令字符串的语法仅在运行时验证。因此,如果存在,最好使用特定的客户端请求对参数进行更好的编译时验证。

VALGRIND_STACK_REGISTER(start, end)

注册一个新的堆栈。通知Valgrind,start和end之间的内存范围是唯一的堆栈。返回可与其他VALGRIND_STACK_*调用一起使用的堆栈标识符 。

Valgrind将使用此信息来确定堆栈指针的更改是否是推送到堆栈上的项目或更改为新堆栈。如果您正在使用用户级线程包,并注意到堆栈跟踪记录中的崩溃或Valgrind关于未初始化内存读取的虚假错误,请使用此方法。

警告:不幸的是,这个客户端请求是不可靠的,最好避免。

VALGRIND_STACK_DEREGISTER(id)

取消注册以前注册的堆栈。通知Valgrind以前注册的堆栈ID的内存范围 id不再是堆栈。

警告:不幸的是,这个客户端请求是不可靠的,最好避免。

VALGRIND_STACK_CHANGE(id, start, end)

更改以前注册的堆栈。通知Valgrind,堆栈id的先前注册的堆栈 id已更改其起始值和结束值。如果您的用户级线程包实现堆栈增长,请使用此选项。

警告:不幸的是,这个客户端请求是不可靠的,最好避免。

3.2。使用Valgrind gdbserver和GDB调试程序

在Valgrind下运行的程序不会直接由CPU执行。而是运行在Valgrind提供的合成CPU上。这就是为什么调试器在Valgrind上运行时无法调试程序的原因。

本节介绍GDB如何与Valgrind gdbserver进行交互,以在Valgrind下提供完全可调试的程序。以这种方式使用,GDB还提供了Valgrind核心或工具功能的交互式使用,包括Memcheck和按需Massif快照生产的增量泄漏搜索。

3.2.1。快速入门:3步调试

开始使用最简单的方法是使用标志运行Valgrind --vgdb-error=0。然后按照屏幕上的方向进行操作,这样您就可以获得启动GDB并将其连接到程序所需的精确命令。

否则,这里有一个更详细的概述。

如果要在使用Memcheck工具时使用GDB调试程序,请启动Valgrind,如下所示:

valgrind --vgdb = yes --vgdb-error = 0 prog

在另一个shell中,启动GDB:

gdb程序

然后给GDB发送以下命令:

(gdb)target remote | vgdb

您现在可以调试程序,例如插入断点,然后使用GDB continue 命令。

这个快速启动信息足以基本使用Valgrind gdbserver。以下部分描述了Valgrind和GDB组合提供的更高级的功能。请注意,--vgdb=yes可以省略命令行标志,因为这是默认值。

3.2.2。Valgrind gdbserver整体组织

GNU GDB调试器通常用于调试在同一台机器上运行的进程。在这种模式下,GDB使用系统调用来控制和查询被调试的程序。这样做很好,但只允许GDB调试在同一台计算机上运行的程序。

GDB还可以调试在不同计算机上运行的进程。为了实现这一点,GDB定义了一个协议(即一组查询和回复数据包),有助于获取内存或寄存器的值,设置断点等。gdbserver是“GDB远程调试”协议的实现。要调试在远程计算机上运行的进程,gdbserver(有时称为GDB存根)必须在远程计算机端运行。

Valgrind内核提供了一个内置的gdbserver实现,它使用--vgdb=yes 或激活--vgdb=full。该gdbserver允许在Valgrind的合成CPU上运行的进程远程调试。GDB向Valgrind嵌入式gdbserver发送协议查询数据包(如“获取寄存器内容”)。gdbserver执行查询(例如,它将获得合成CPU的寄存器值),并将结果返回给GDB。

GDB可以使用各种通道(TCP / IP,串行线等)与gdbserver进行通信。在Valgrind的gdbserver的情况下,通过一个管道和一个名为vgdb的小帮助程序完成通信,该程序充当中间人。如果没有使用GDB,vgdb也可以用于从shell命令行将监控命令发送到Valgrind gdbserver。

3.2.3。将GDB连接到Valgrind gdbserver

要调试程序“ prog” Valgrind的下运行,你必须确保Valgrind的gdbserver的是通过指定激活--vgdb=yes 或--vgdb=full。辅助命令行选项, --vgdb-error=number可以用于告知gdbserver在显示指定数量的错误后才会变为活动状态。因此,值为零将导致gdbserver在启动时变为活动状态,这允许您在开始运行之前插入断点。例如:

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

Valgrind gdbserver在启动时被调用,并指示它正在等待来自GDB的连接:

== 2418 == Memcheck,一个内存错误检测器
== 2418 ==版权所有(C)2002-2010和GNU GPL'd,由Julian Seward等人
== 2418 ==使用Valgrind-3.7.0.SVN和LibVEX; 用-h重新运行版权信息
== 2418 ==命令:./prog
== == 2418
== 2418 ==(启动时的动作)vgdb me ...

然后可以将GDB(在另一个shell中)连接到Valgrind gdbserver。为此,必须在程序上启动GDB prog

gdb ./prog

然后,您向GDB指出您要调试远程目标:

(gdb)target remote | vgdb

GDB然后启动一个vgdb中继应用程序与Valgrind嵌入式gdbserver进行通信:

(gdb)target remote | vgdb
远程调试使用| vgdb
在gdb和进程2418之间中继数据
从/lib/ld-linux.so.2...done读取符号。
从/usr/lib/debug/lib/ld-2.11.2.so.debug...done读取符号。
/lib/ld-linux.so.2的加载符号
[切换到线程2418]
来自/lib/ld-linux.so.2的_start()中的0x001f2850
(GDB)

请注意,vgdb是作为Valgrind分发的一部分提供的。您不需要单独安装。

如果vgdb检测到可以连接多个Valgrind gdbservers,它将列出所有这些服务器及其PID,然后退出。然后,您可以重新发行GDB“目标”命令,但指定要调试的进程的PID:

(gdb)target remote | vgdb
远程调试使用| vgdb
没有--pid = arg给定和多个valgrind pids找到:
使用--pid = 2479 for valgrind --tool = memcheck --vgdb = yes --vgdb-error = 0 ./prog
使用--pid = 2481 for valgrind --tool = memcheck --vgdb = yes --vgdb-error = 0 ./prog
使用--pid = 2483 for valgrind --vgdb = yes --vgdb-error = 0 ./another_prog
远程通信错误:资源暂时不可用。
(gdb)target remote | vgdb --pid = 2479
远程调试使用| vgdb --pid = 2479
在gdb和进程2479之间中继数据
从/lib/ld-linux.so.2...done读取符号。
从/usr/lib/debug/lib/ld-2.11.2.so.debug...done读取符号。
/lib/ld-linux.so.2的加载符号
[切换到线程2479]
来自/lib/ld-linux.so.2的_start()中的0x001f2850
(GDB)

一旦GDB连接到Valgrind gdbserver,它就可以像你在本机调试程序一样使用:

  • 断点可插入或删除。

  • 可以检查或修改变量和寄存器值。

  • 可以配置信号处理(打印,忽略)。

  • 执行可以被控制(继续,步骤,下一步,stepi等)。

  • 程序执行可以使用Control-C中断。

等等。有关GDB功能的完整说明,请参阅GDB用户手册。

3.2.4。连接到Android gdbserver

开发Android应用程序时,通常会使用开发系统(安装了Android NDK)来编译应用程序。将使用Android目标系统或仿真器来运行应用程序。在此设置中,Valgrind和vgdb将在Android系统上运行,而GDB将在开发系统上运行。GDB将使用Android NDK'adb forward'应用程序连接到在Android系统上运行的vgdb。

示例:在Android系统上,执行以下操作:

valgrind --vgdb-error = 0 --vgdb = yes prog
然后在另一个shell中运行:
vgdb --port = 1234

在开发系统中,执行以下命令:

adb forward tcp:1234 tcp:1234
gdb程序
(gdb)target remote:1234

GDB将使用本地tcp / ip连接来连接到Android adb转发器。Adb将在主机系统和Android目标系统之间建立中继连接。确保使用Android NDK系统中提供的GDB(通常是arm-linux-androideabi-gdb),因为主机GDB可能无法调试Android手臂应用程序。请注意,本地端口nr(由GDB使用)不一定等于vgdb使用的端口号:adb可以在不同端口号之间转发tcp / ip。

在当前版本中,默认情况下,由于建立一个合适的目录,因为Valgrind可以创建必要的FIFO(命名管道)用于通信目的,GDB服务器未启用。您可以尝试使用GDB服务器,但是您需要使用该标志--vgdb=yes或 显式启用它 --vgdb=full

此外,您将需要选择一个临时目录(a)可由Valgrind写入,(b)支持FIFO。这是难点。通常/sdcard满足要求(a),但是(b)因为是VFAT文件系统而VFAT不支持管道而失败。可能你可以尝试是 /data/local, /data/local/Inst(如果你安装了Valgrind的存在),或者 /data/data/name.of.my.app,如果你正在运行特定的应用程序,它有它自己的这种形式的目录。这最后的可能性可能是最高的成功概率。

您可以通过--with-tmpdir=配置时间标志指定临时目录或在运行Valgrind时(在Android设备上,而不是Android NDK开发主机上)设置环境变量TMPDIR。另一个选择是使用--vgdb-prefix=Valgrind命令行选项来指定FIFO的目录。

我们希望将来有一个更好的故事,用于Android上的临时目录处理。困难在于,与标准Unixes不同,没有单个临时文件目录可靠地在所有设备和方案中工作。

3.2.5。监视Valgrind gdbserver的命令处理

Valgrind gdbserver通过“monitor命令”提供了额外的Valgrind特定功能。这样的监视命令可以从GDB命令行或从shell命令行发送,或者由客户端程序使用VALGRIND_MONITOR_COMMAND客户端请求发送。有关Valgrind核心监视器命令的列表,请参阅 Valgrind监视器命令,无论选择了Valgrind工具。

以下工具提供特定于工具的监视器命令:

  • Memcheck监视器命令

  • Callgrind监视器命令

  • Massif监视器命令

  • Helgrind监视器命令

特定于工具的监视器命令的示例是Memcheck监视器命令leak_check full reachable any。这要求对所分配的内存块进行全面报告。要执行此泄漏检查,请使用GDB命令:

(gdb)monitor leak_check完全可达任何

GDB将发送leak_check 命令到Valgrind gdbserver。如果Valgrind gdbserver将其识别为Valgrind core monitor命令,则会自动执行monitor命令。如果它不被认可,则假设它是具体的工具,并被交给该工具执行。例如:

(gdb)monitor leak_check完全可达任何
== 2418 == 1块中的100字节仍然可以在损失记录1中1
== 2418 == at 0x4006E9E:malloc(vg_replace_malloc.c:236)
== 2418 == by 0x804884F:main(prog.c:88)
== == 2418
== 2418 == LEAK SUMMARY:
== 2418 ==绝对丢失:0块0块
== 2418 ==间接丢失:0个字节,0个块
== 2418 ==可能丢失:0个字节的0个块
== 2418 ==仍然可达:100个字节的1个块
== 2418 == suppress:0个字节,0个块
== == 2418
(GDB)

与其他GDB命令一样,Valgrind gdbserver将接受缩写的监视器命令名称和参数,只要给定的缩写是明确的。例如,上述 leak_check 命令也可以键入:

(gdb)mo lfra

这些字母mo被GDB识别为缩写monitor。所以GDB将字符串发送l f r a到Valgrind gdbserver。这个字符串中提供的字母对Valgrind gdbserver来说是明确的。因此,这将给出与未缩写的命令和参数相同的输出。如果提供的缩写不明确,则Valgrind gdbserver会报告可匹配的命令列表(或参数值):

(gdb)mo v。n
v。可以匹配v.set v.info v.wait v.kill v.translate v.do
(gdb)mo vi n
n_errs_found 0 n_errs_shown 0(vgdb-error 0)
(GDB)

而不是从GDB发送监视器命令,您也可以从shell命令行发送这些命令。例如,以下命令行在shell中给出时,将导致进程3145执行相同的泄漏搜索:

vgdb --pid = 3145 leak_check完全可达任何
vgdb --pid = 3145 lfra

请注意,在单独调用vgdb之后,Valgrind gdbserver会自动继续执行该程序。从GDB发送的监视命令不会导致程序继续:使用GDB命令(如“继续”或“下一个”)显式控制程序执行。

3.2.6。Valgrind gdbserver线程信息

Valgrind的gdbserver info threads使用Valgrind特定的信息丰富了GDB 命令的输出。操作系统的线程号后跟Valgrind的该线程的内部索引(“tid”)和Valgrind调度程序线程状态:

(gdb)信息线程4 Thread 6239(tid 4 VgTs_Yielding)0x11f2832 in _ll_sysinfo_int80()from /lib/ld-linux.so.2
* 3线程6238(tid 3 VgTs_Runnable)make_error(s = 0x8048b76“从伦敦调用”)在prog.c:202 Thread 6237(tid 2 VgTs_WaitSys)0x001f2832 in _ll_sysinfo_int80()from /lib/ld-linux.so.21 prog 6234(tid 1 VgTs_Yielding)main(argc = 1,argv = 0xbedcc274)在prog.c:105
(GDB)

3.2.7。检查和修改Valgrind影子寄存器

当给出选项--vgdb-shadow-registers=yes时,Valgrind gdbserver将允许GDB检查和/或修改Valgrind的影子寄存器。需要GDB 7.1或更高版本才能工作。对于x86和amd64,需要GDB版本7.2或更高版本。

对于每个CPU寄存器,Valgrind内核保留两个影子寄存器集。这些影子寄存器可以通过给出一个后缀s1 或s2分别为第一个和第二个影子寄存器从GDB访问。例如,eax可以使用以下命令检查x86寄存器 及其两个阴影:

(gdb)p $ eax
$ 1 = 0
(gdb)p $ eaxs1
$ 2 = 0
(gdb)p $ eaxs2
$ 3 = 0
(GDB)

浮动影子寄存器由GDB显示为无符号整数值,而不是浮点值,因为预期这些阴影值主要用于memcheck有效位。

Intel / amd64 AVX寄存器ymm0 也ymm15有它们的影子寄存器。然而,GDB使用两个“半”寄存器呈现阴影值。例如,半影子寄存器 ymm9是 xmm9s1(组1的下半部分),ymm9hs1(组1的 上半部分), xmm9s2(组2的下半部分), ymm9hs2(组2的上半部分)。请注意半登记名称的不一致表示法:下半部分以a开头x,上半部分以a开头,y 并且h在阴影后缀之前。

AVX影子寄存器的特殊表现是由于GDB独立地检索寄存器的下半部分和上半部分ymm。然而,GDB不知道阴影半记录必须被显示组合。

3.2.8。Valgrind gdbserver的限制

使用Valgrind gdbserver进行调试与本机调试非常相似。Valgrind的gdbserver实现是相当完整的,因此提供了大部分GDB调试功能。但是有一些限制和特点:

  • “停止”命令的精度。

    诸如“step”,“next”,“stepi”,断点和观察点等GDB命令将停止执行进程。使用该选项--vgdb=yes,该过程可能不会在精确请求的指令中停止。相反,它可能继续执行当前的基本块,并停止在以下基本块之一。这与Valgrind gdbserver必须调整块以允许停止在所请求的精确指令的事实有关。目前,不支持重新检测当前正在执行的块。因此,如果GDB请求的动作(例如单步或插入断点)意味着重新检测当前块,则GDB操作可能无法精确执行。

    当正在执行的基本块尚未被调试时,此限制适用。当gdbserver由于工具报告错误或观察点而被激活时,通常会发生这种情况。如果在断点之后激活了gdbserver块,或者在执行断点之前已经插入了断点,则该块已经被调试了。

    如果使用该选项--vgdb=full,则GDB“stop-at”命令将被精确地遵守。不利之处在于,这需要对每个指令进行附加调用,调用gdbserver帮助函数,这相当于开销(对于memcheck为+ 500%) --vgdb=no--vgdb=yes与之相比,选项具有可忽视的开销--vgdb=no

  • 处理器寄存器和标志值。

    当Valgrind gdbserver停止错误时,在断点上,或者由于Valgrind内核进行了优化,单步执行,寄存器和标志值可能并不总是最新的。默认值 --vex-iropt-register-updates=unwindregs-at-mem-access 可确保在每次存储器访问(即内存异常点)时进行堆栈跟踪(通常为PC / SP / FP)所需的寄存器是最新的。使用以下值禁用某些优化将增加寄存器和标志值的精度(为每个选项给出对于memcheck的典型性能影响)。

    • --vex-iropt-register-updates=allregs-at-mem-access (+ 10%)确保所有寄存器和标志在每次存储器访问时都是最新的。
    • --vex-iropt-register-updates=allregs-at-each-insn (+ 25%)确保所有寄存器和标志在每个指令时都是最新的。

    请注意,--vgdb=full(+ 500%,见上文“停止”命令的精度)自动激活--vex-iropt-register-updates=allregs-at-each-insn

  • Valgrind gdbserver支持硬件观察点。

    如果选定的工具提供支持,Valgrind gdbserver可以模拟硬件观察点。目前,只有Memcheck提供硬件观察点模拟。由Memcheck提供的硬件观察点模拟比GDB软件观察点快得多,GDB软件观察点由GDB在每个指令之后检查被监视区域的值。硬件观察点模拟还提供读取观察点。Memcheck的硬件观察点模拟与实际硬件观察点相比有一些限制。但是,模拟观察点的数量和长度不受限制。

    通常,(实际)硬件观察点的数量有限。例如,x86架构最多支持4个硬件观察点,每个观察点观察1,2,4或8个字节。Valgrind gdbserver对模拟硬件观察点的数量没有任何限制。正在观看的内存区域的长度也没有限制。使用GDB版本7.4或更高版本可以充分利用Valgrind gdbserver的模拟硬件观察点的灵活性。以前的GDB版本不明白Valgrind gdbserver观察点没有长度限制。

    Memcheck通过将观看的地址范围标记为不可寻址来实现硬件观察点模拟。当硬件观察点被删除时,该范围被标记为可寻址并定义。可寻址但未定义的内存区域的硬件观察点模拟工作正常,但是在删除观察点时定义的区域具有不良的副作用。

    写入监视区域的准确指令可能不会报告写入点,除非--vgdb=full给出了选项。读取观察点将始终按照读取监视内存的准确说明进行报告。

    最好避免使用不可寻址(还))内存的硬件观察点:在这种情况下,GDB将会回退到非常慢的软件观察点。另外,如果不在两个调试会话之间退出GDB,如果在程序启动时监视的内存区域不可寻址,则前一个会话的硬件观察点将作为软件观察点重新插入。

  • 在ARM内部进行共享库。

    由于未知的原因,在ARM上进入共享库可能会失败。解决方法是使用 ldd命令查找共享库列表及其加载地址,并使用GDB命令“add-symbol-file”通知GDB加载地址。例:

    (gdb)shell ldd ./proglibc.so.6 => /lib/libc.so.6(0x4002c000)/lib/ld-linux.so。(0x40000000)
    (gdb)add-symbol-file /lib/libc.so.6 0x4002c000
    从文件“/lib/libc.so.6”中添加符号表.text_addr = 0x4002c000
    (y或n)y
    从/lib/libc.so.6...(找到调试符号)读取符号...完成。
    (GDB)
    
  • ARM和PPC32 / 64需要GDB版本。

    您必须使用能够读取gdbserver发送的XML目标描述的GDB版本。如果使用“expat”库配置和构建GDB,则这是标准设置。如果您的GDB未配置XML支持,则在使用“target”命令时会报告错误消息。调试将不起作用,因为GDB将无法从Valgrind gdbserver获取寄存器。对于使用Thumb指令集的ARM程序,必须使用7.1或更高版本的GDB版本,因为早期版本在Thumb代码中具有下一个/步骤/断点的问题。

  • 堆放在PPC32 / PPC64上。

    在PPC32 / PPC64,栈展开叶功能(即不调用任何其他函数的函数)正常工作,只有当你给的选项 --vex-iropt-register-updates=allregs-at-mem-access 或--vex-iropt-register-updates=allregs-at-each-insn。您还必须通过此选项,以便在信号被GDB捕获时获得精确的堆栈。

  • 断点遇到多次。

    一些指令(例如x86“rep movsb”)由Valgrind使用循环来翻译。如果在这样的指令上放置了一个断点,断点将被多次遇到 - 一次执行指令的“隐含”循环的每个步骤。

  • 执行Valgrind gdbserver的下层函数调用。

    GDB允许用户在被调试的进程内“调用”函数。这种呼叫在GDB术语中被称为“次要呼叫”。劣质呼叫的典型用途是执行打印复杂数据结构的人类可读版本的功能。要进行一个较低的调用,请使用GDB“print”命令,后跟该函数调用及其参数。作为示例,以下GDB命令会导致对被调试进程执行的libc“printf”函数的较低调用:

    (gdb)p printf(“正在调试的进程具有pid%d \ n”,getpid())
    $ 5 = 36
    (GDB)
    

    Valgrind gdbserver支持较差的函数调用。当一个劣质的电话正在运行时,Valgrind工具会像往常一样报告错误。如果您不希望有这样的错误停止执行劣质呼叫,您可以使用v.set vgdb-error在呼叫之前设置一个较大的值,然后在呼叫完成时手动将其重置为其原始值。

    为了执行劣质调用,GDB会更改寄存器,如程序计数器,然后继续执行程序。在多线程程序中,所有的线程都会继续进行,而不仅仅是线程指示进行较低的调用。如果另一个线程报告错误或遇到断点,则劣质评估的评估将被放弃。

    请注意,劣质函数调用是强大的GDB功能,但应谨慎使用。例如,如果正在调试的程序在函数“printf”中被停止,则强制通过劣质调用对printf的递归调用将非常可能产生问题。Valgrind工具还可能为劣质调用增加了另一个级别的复杂性,例如在Inferior调用期间报告工具错误或由于完成了仪器设置。

  • 连接到或中断Valgrind进程在系统调用中被阻止。

    连接或中断系统调用阻塞的Valgrind进程需要“ptrace”系统调用才能使用。出于安全考虑,这可能会在您的内核中被禁用。

    运行程序时,Valgrind的调度程序会定期检查是否有任何可以由gdbserver处理的工作。不幸的是,只有当进程的至少一个线程可运行时,才会进行该检查。如果进程的所有线程在系统调用中被阻止,则不会发生检查,Valgrind调度程序将不会调用gdbserver。在这种情况下,vgdb中继应用程序将“强制”调用gdbserver,而不需要Valgrind调度程序的干预。

    这种强制调用Valgrind gdbserver是由vgdb使用ptrace系统调用实现的。在正确实现的内核上,由vgdb完成的ptrace调用不会影响在Valgrind下运行的程序的行为。然而,如果他们这样做,给--max-invoke-ms=0vgdb中继应用程序的选项将禁用ptrace调用的使用。禁用vgdb中的ptrace使用的结果是,在系统调用中阻塞的Valgrind进程不能从GDB唤醒或中断,直到它执行足够的基本块,以使Valgrind调度程序的正常检查生效。

    当在vgdb中禁用ptrace时,可以通过为该选项赋予较低的值来增加Valgrind gdbserver对命令或中断的响应--vgdb-poll。如果您的应用程序在大多数情况下在系统调用中被阻止,则使用非常低的值--vgdb-poll会导致更快地调用gdbserver。Valgrind的调度程序执行的gdbserver轮询非常有效,因此增加的查询频率不应导致严重的性能下降。

    当在vgdb中禁用ptrace时,由GDB发送的查询数据包可能需要大量时间才能由Valgrind gdbserver处理。在这种情况下,GDB可能会遇到协议超时。为避免这种情况,您可以使用GDB命令“set remotetimeout”来增加超时值。

    Ubuntu 10.10及更高版本可能会将ptrace的范围限制为调用ptrace的进程的子进程。由于Valgrind进程不是vgdb的子进程,因此这种受限制的范围会导致ptrace调用失败。为避免这种情况,Valgrind将自动允许属于同一用户标识符的所有进程通过使用PR_SET_PTRACER“追踪”Valgrind进程。

    系统调用中阻止的取消阻止进程目前在Mac OS X和Android上尚未实施。因此,您无法连接或中断Mac OS X或Android系统调用中阻止的进程。

  • 更改寄存器值。

    当线程处于状态Runnable或Yielding时,Valgrind gdbserver将仅修改线程的寄存器的值。在其他状态(通常为WaitSys)中,尝试更改寄存器值将失败。除此之外,这意味着对系统调用中的线程不执行劣质调用,因为Valgrind gdbserver不会执行系统调用重启。

  • 不支持的GDB功能。

    GDB提供了大量的调试功能,并没有全部支持。具体来说,不支持以下内容:可逆调试和跟踪点。

  • 未知的限制或问题。

    GDB,Valgrind和Valgrind gdbserver的组合可能具有未知的其他限制和问题。如果遇到奇怪或意想不到的行为,请随时报告错误。但首先请确认GDB或GDB远程协议不是固有的限制或问题。您可以通过检查使用GDB包的标准gdbserver部分时的行为来执行此操作。

3.2.9。vgdb命令行选项

用法: vgdb [OPTION]... [[-c] COMMAND]...

vgdb(“Valgrind to GDB”)是一个小程序,用作Valgrind和GDB或shell之间的中介。因此,它有两种使用模式:

  1. 作为独立实用程序,它从shell命令行使用,将监视器命令发送到在Valgrind下运行的进程。对于此用法,vgdb OPTION(s)必须跟随monitor命令发送。要发送多个命令,请使用该-c选项分隔。

  2. 结合GDB“target remote |” 命令,它用作GDB和Valgrind gdbserver之间的中继应用程序。对于这种用法,只能给出OPTION,但不能给出COMMAND。

vgdb 接受以下选项:

--pid=<number>

指定vgdb必须连接到的进程的PID。如果可以连接多个Valgrind gdbserver,则此选项很有用。如果--pid没有给出参数并且多个Valgrind gdbserver进程正在运行,vgdb将报告这些进程的列表,然后退出。

--vgdb-prefix

如果要更改用于Valgrind gdbserver和vgdb之间的通信的FIFO(命名管道)的默认前缀,则必须将其赋予Valgrind和vgdb。

--wait=<number>

指示vgdb以指定的秒数搜索可用的Valgrind gdbservers。这样可以启动一个vgdb进程,然后再启动与目标vgdb进行通信的Valgrind gdbserver。当与--vgdb-prefix您要等待的进程唯一的情况一起使用时,此选项很有用。另外,如果--wait在GDB“target remote”命令中使用参数,则必须将GDB remotetimeout设置为大于-wait参数值的值。有关--max-invoke-ms设置remotetimeout值的示例,请参阅选项 (如下)。

--max-invoke-ms=<number>

提供毫秒数,之后vgdb将强制调用嵌入在Valgrind中的gdbserver。默认值为100毫秒。值为0将禁用强制调用。当vgdb连接到Valgrind gdbserver时,使用强制调用,Valgrind进程在系统调用中阻止其所有线程。

如果指定了较大的值,则可能需要将GDB“remotetimeout”的值从其默认值2秒增加。您应该确保超时(以秒为单位)大于该--max-invoke-ms值。例如,对于--max-invoke-ms=5000以下GDB命令是合适的:

    (gdb)set remotetimeout 6
--cmd-time-out=<number>

指示独立的vgdb退出,如果连接到其中的Valgrind gdbserver不以指定的秒数处理命令。默认值是永远不会超时。

--port=<portnr>

指示vgdb使用tcp / ip并在指定的端口nr上监听GDB,而不是使用管道与GDB进行通信。使用tcp / ip允许在一台计算机上运行GDB并调试在另一台目标计算机上运行的Valgrind进程。例:

#在目标计算机上,使用valgrind启动您的程序
valgrind --vgdb-error = 0 prog
然后在另一个shell中运行:
vgdb --port = 1234

在承载GDB的计算机上,执行以下命令:

gdb程序
(gdb)target remote targetip:1234

其中targetip是目标计算机的IP地址或主机名。

-c

要向独立的vgdb提供多个命令,请通过选项分隔命令-c。例:

vgdb v.set log_output -c leak_check any
-l

指示独立的vgdb报告运行的Valgrind gdbserver进程的列表,然后退出。

-D

指示独立的vgdb显示Valgrind gdbserver使用的共享内存的状态。显示Valgrind gdbserver共享内存状态后,vgdb将退出。

-d

指示vgdb产生调试输出。提供多个-d参数以增加详细程度。当给予-d中继vgdb时,您最好将vgdb的标准错误(stderr)重定向到一个文件,以避免GDB和vgdb调试输出之间的交互。

3.2.10。Valgrind监视器命令

本节介绍Valgrind监视器命令,无论选择了Valgrind工具,都可以使用。有关特定于工具的命令,请参阅Memcheck Monitor命令, Helgrind Monitor命令, Callgrind Monitor命令和 Massif Monitor命令。

监视器命令可以通过使用独立的vgdb或从GDB通过使用GDB的“监视器”命令(请参阅由Valgrind gdbserver监视命令处理)从shell命令行发送。客户端程序也可以使用VALGRIND_MONITOR_COMMAND客户端请求启动它们。

  • help [debug]指示Valgrind的gdbserver给出Valgrind内核和工具的所有监视器命令的列表。可选的“debug”参数还指出了针对Valgrind内部调试的监视器命令的帮助。

  • v.info all_errors 显示到目前为止发现的所有错误。

  • v.info last_error 显示最后发现的错误。

  • v.info location <addr>输出位置信息<addr>。可能的描述如下:全局变量,本地(堆栈)变量,分配或释放的块,...生成的信息取决于工具和给予valgrind的选项。一些工具(例如memcheck和helgrind)可以为客户端堆块生成更详细的信息。例如,这些工具显示堆栈跟踪分配堆块的位置。如果一个工具不能替代malloc / free / ...函数,则不会描述客户机堆块。使用该选项--read-var-info=yes获取有关全局或本地(堆栈)变量的更详细信息。

    (gdb)monitor v.info位置0x8050b20位置0x8050b20是全局var“mx”内的0个字节在tc19_shadowmem.c上声明:19(gdb)mo v.in loc 0x582f33c位置0x582f33c是0字节的局部变量“info”在tc19_shadowmem.c:282,在线程3的框架#1中声明
    (GDB)
    
  • v.info n_errs_found [msg]显示到目前为止发现的错误的数量,到目前为止显示的错误的nr和--vgdb-error参数的当前值。附加可选 msg(一个或多个单词)。通常,这可以用于在进程输出文件之间的几个测试之间插入标记,序列中只有一个进程启动。这允许将Valgrind报告的错误与产生这些错误的特定测试相关联。

  • v.info open_fds显示打开的文件描述符和与文件描述符相关的详细信息的列表。这只有--track-fds=yes 在Valgrind启动时才能使用。

  • v.set {gdb_output | log_output | mixed_output}允许重定向Valgrind输出(例如工具检测到的错误)。默认设置为 mixed_output

    使用mixed_outputValgrind输出到Valgrind日志(通常是stderr),而GDB显示交互式GDB监视器命令(eg v.info last_error)的输出。

    由于gdb_outputGDB显示了Valgrind输出和交互式GDB监视器命令输出。

    使用log_outputValgrind输出和交互式GDB监视器命令输出将转到Valgrind日志。

  • v.wait [ms (default 0)]指示Valgrind gdbserver以毫秒为单位睡眠“ms”,然后继续。当从独立的vgdb发送时,如果这是最后一个命令,则Valgrind进程将继续执行客户机进程。典型的用法是使用vgdb向Valgrind gdbserver发送一个“no-op”命令,以便继续执行guest虚拟机进程。

  • v.kill请求gdbserver终止进程。这可以从独立的vgdb使用,以正确地杀死当前正在期待vgdb连接的Valgrind进程。

  • v.set vgdb-error <errornr> 动态地更改--vgdb-error参数的值 。典型的用法是--vgdb-error=0从命令行开始 ,然后设置几个断点,将vgdb-error值设置为一个巨大的值,并继续执行。

以下Valgrind监视器命令可用于调查Valgrind或其gdbserver在发生问题或错误时的行为。

  • v.do expensive_sanity_check_general 执行各种理智检查。特别是Valgrind堆的理智得到验证。如果您怀疑您的程序和/或Valgrind有一个错误的Valgrind数据结构错误,这将非常有用。当Valgrind工具向连接的GDB报告客户端错误时,也可以使用它,以便在继续执行之前验证Valgrind的完整性。

  • v.info gdbserver_status显示gdbserver状态。在出现问题(例如通信)的情况下,这将显示一些相关Valgrind gdbserver内部变量的值。请注意,与断点和观察点相关的变量(例如断点地址数和观察点数)将为零,因为默认情况下,GDB将在执行停止时删除所有观察点和断点,并在恢复执行时重新插入调试过程。您可以使用GDB命令更改此GDB行为 set breakpoint always-inserted on

  • v.info memory [aspacemgr]显示了Valgrind内部堆管理的统计信息。如果提供了选项--profile-heap=yes,将会输出详细的统计数据。使用可选参数 aspacemgr。将输出由valgrind地址空间管理器维护的段列表。请注意,此列表的段始终在Valgrind日志中输出。

  • v.info exectxt显示有关Valgrind记录的“可执行上下文”(即堆栈跟踪)的信息。对于一些程序,Valgrind可以记录非常多的这种堆栈跟踪,导致高内存使用。此监视器命令显示所有记录的堆栈跟踪,然后显示一些统计信息。这可以用来分析大量堆栈跟踪的原因。通常,如果v.info memory“exectxt”竞技场显示出显着的内存使用,您将使用此命令。

  • v.info scheduler显示有关线程的各种信息。首先,它输出主机栈跟踪,即正在执行的Valgrind代码。然后,对于每个线程,它将输出线程状态。对于未终止的线程,状态后面是guest(客户端)堆栈跟踪。最后,对于每个活动线程或对于尚未重新使用的每个终止的线程槽,它显示valgrind堆栈的最大使用。

    显示客户端堆栈跟踪允许将由Valgrind展开器生成的堆栈跟踪与由GDB + Valgrind gdbserver生成的堆栈跟踪进行比较。注意GDB和Valgrind调度器状态有自己的线程编号方案。要使GDB线程号和对应的Valgrind调度程序线程号之间的链接,请使用GDB命令info threads。此命令的输出显示GDB线程号和valgrind'tid'。'tid'是输出的线程号v.info scheduler。当使用callgrind工具时,callgrind monitor命令 status输出内部callgrind信息,关于它保存的堆栈/调用图。

  • v.info stats显示各种valgrind核心和工具统计。有了这个,Valgrind和工具统计可以在运行时检查,即使没有选项--stats=yes

  • v.info unwind <addr> [<len>]显示地址范围[addr,addr + len-1]的CFI展开调试信息。默认值<len>为1,给出了<addr>中的指令的展开信息。

  • v.set debuglog <intvalue>将Valgrind调试日志级别设置为<intvalue>。这允许动态地更改Valgrind的日志级别,例如当检测到问题时。

  • v.set hostvisibility [yes*|no]值“yes”表示gdbserver指示GDB可以查看Valgrind的“主机”(内部)状态/内存。“否”禁用此访问权限。当启动主机时,GDB可以查看Valgrind全局变量。例如,要在x86上检查memcheck工具的Valgrind全局变量,请执行以下设置:

    (gdb)monitor v.set hostvisibility yes
    (gdb)add-symbol-file / path / to / tool / executable / file / memcheck-x86-linux 0x38000000
    从文件“/ path / to / tool / executable / file / memcheck-x86-linux”中添加符号表.text_addr = 0x38000000
    (y或n)y
    从/path/to/tool/executable/file/memcheck-x86-linux...done读取符号。
    (GDB)
    

    之后,可以访问memcheck-x86-linux中定义的变量,例如

    (gdb)p / x vgPlain_threads [1] .os_state
    $ 3 = {lwpid = 0x4688,threadgroup = 0x4688,parent = 0x0, valgrind_stack_base = 0x62e78000,valgrind_stack_init_SP = 0x62f79fe0, exitcode = 0x0,fatalsig = 0x0}
    (gdb)p vex_control
    $ 5 = {iropt_verbosity = 0,iropt_level = 2, iropt_register_updates = VexRegUpdUnwindregsAtMemAccess, iropt_unroll_thresh = 120,guest_max_insns = 60,guest_chase_thresh = 10, guest_chase_cond = 0'\ 000'}
    (GDB)
    
  • v.translate <address> [<traceflags>]显示address包含给定跟踪标志的块的翻译。该traceflags值位模式也有类似的含义Valgrind的的 --trace-flags选项。它可以十六进制(例如0x20)或十进制(例如32)或二进制1s和0s位(例如0b00100000)给出。traceflags的默认值为0b00100000,对应于“仪表后显示”。该命令的输出总是进入Valgrind日志。

    附加位标志0b100000000(位8)在--trace-flags选项中没有等效项。它可以跟踪gdbserver特定的仪器。请注意,该位8只能在跟踪中添加gdbserver仪器。如果gdbserver工具由于某些其他原因而处于活动状态,则将其设置为0将不会禁用跟踪,例如因为此地址处有断点或因为gdbserver处于单步执行模式。

3.3。功能包装

Valgrind允许调用某些指定的函数被拦截并重新路由到不同的用户提供的函数。这可以做任何它喜欢的,通常检查的论据,向原来的,并可能检查结果。可以包装任何数量的功能。

函数换行对于以某种方式对API进行测试非常有用。例如,Helgrind在POSIX pthreads API中包装函数,因此可以了解线程状态更改,核心能够将功能包含在MPI(消息传递)API中,以便知道与消息到达/离开。这些信息通常通过在包装函数中使用客户端请求传递给Valgrind,尽管确切的机制可能会有所不同。

3.3.1。一个简单的例子

假设我们要包装一些功能

int foo(int x,int y){return x + y; }

包装器是相同类型的函数,但是具有将其标识为包装的特殊名称foo。包装器需要包括支持宏valgrind.h。这是一个简单的包装器,它打印参数和返回值:

#include <stdio.h>
#include“valgrind.h”
int I_WRAP_SONAME_FNNAME_ZU(NONE,foo)(int x,int y)
{int结果;OrigFn fn;VALGRIND_GET_ORIG_FN(FN);printf(“foo's wrapper:args%d%d \ n”,x,y);CALL_FN_W_WW(result,fn,x,y);printf(“foo's wrapper:result%d \ n”,result);返回结果;
}

为了变得活跃,包装器只需要存在于与其包装的函数相同的进程'地址空间中的文本部分中,并且其ELF符号名称对于Valgrind可见。在实践中,这意味着要么编译到一个 .o和它连接,或编译的.so和 LD_PRELOAD在荷兰国际集团它。后者是更方便的,因为它不要求重新链接。

所有包装纸都有上述形式。有三个关键的宏:

I_WRAP_SONAME_FNNAME_ZU:这将生成包装器的真实名称。这是Valgrind读取符号表信息时注意到的编码名称。它说的是:我是一个命名的函数的包装器,foo它在ELF共享对象中找到一个空(“ NONE”)soname字段。规范机制是强大的,因为通配符可以用于声名和函数名。细节将在下面讨论。

VALGRIND_GET_ORIG_FN:一旦在包装器中,首先要抓住原始地址(以及需要的任何其他支持信息)。它存储在不透明类型的值中OrigFn。使用信息获取 VALGRIND_GET_ORIG_FN。在调用同一个线程中的任何其他包装函数之前,使这个宏调用非常重要。

CALL_FN_W_WW:最终我们想要调用被包装的函数。直接调用它不起作用,因为这只是让我们回到包装器并导致无限循环。相反,结果lvalue OrigFn和参数被交给一个形式的宏的家庭之一 CALL_FN_*。这些导致Valgrind调用原来的,并避免递归回包装。

3.3.2。包装规格

这个方案具有独立的优点。可以以正常方式编译包装器的对象代码,并且不依赖外部脚本告诉Valgrind哪个包装器涉及哪些原件。

每个包装器都有一个名字,在最常见的情况下说:我是名称匹配FNPATT,其ELF“soname”匹配SOPATT的任何函数的包装器。FNPATT和SOPATT都可以包含通配符(星号)和其他通常不被视为有效的C标识符名称的字符(空格,点,@等)。

需要这种灵活性才能为POSIX pthread函数编写强大的包装器,通常我们不完全确定函数名称或soname,或者我们想要一次包装一整套函数。

例如,pthread_create 在GNU libpthread中通常是一个版本化的符号 - 一个名字以...结尾的符号 @GLIBC_2.3。因此,我们不确定它的真实姓名。我们也想覆盖任何形式的声纳libpthread.so*。所以包装的标题将是

int I_WRAP_SONAME_FNNAME_ZZ(libpthreadZdsoZd0,pthreadZucreateZAZa)(...正式...){ ... 身体 ... }

为了将不寻常的字符写为有效的C函数名称,使用Z编码方案。名称是字面上的,除了一个首都Z作为转义字符,具有以下编码:

     Za编码*Zp +Zc:Zd。祖_Zh  - Zs(空格)ZA @ZZ ZZL(#仅在valgrind 3.3.0及更高版本ZR)#只在valgrind 3.3.0及更高版本

因此libpthreadZdsoZd0是的soname的编码libpthread.so.0 和pthreadZucreateZAZa是函数名的编码pthread_create@*

I_WRAP_SONAME_FNNAME_ZZ 构造了一个包装器名称,其中soname(第一个组件)和函数名称(第二个组件)都是Z编码的。编码函数名称可能是令人厌烦的,并且通常是不必要的,因此I_WRAP_SONAME_FNNAME_ZU可以使用第二个宏 。该_ZU变体对于为C ++函数编写包装也是有用的,其中函数名通常使用一些其他约定在Z中发挥重要作用而被破坏。不得不第二次编码很快就会变得混乱。

由于函数名字段可能包含通配符,它​​可以是任何东西,包括只是*。soname也是如此。但是,一些ELF对象(特别是主要的可执行文件)没有声名。任何缺少soname的对象都被视为它的soname NONE,这就是为什么上面的原始例子有一个名字 I_WRAP_SONAME_FNNAME_ZU(NONE,foo)

请注意,ELF对象的soname与其文件名不同,尽管它通常是相似的。您可以libfoo.so使用该命令 找到对象的soname readelf -a libfoo.so | grep soname

3.3.3。包装语义

包装器替代无限功能系列的能力是强大的,但是在ELF对象出现和消失的情况下(dlopen'd和dlclose'd)在飞行中会带来复杂性。Valgrind试图在这种情况下保持明智的行为。

例如,假设一个进程已经削减(带有soname的ELF对象)object1.so,其中包含 function1。它function1立即开始使用 。

过了一会儿,它变得浑浊wrappers.so,里面包含一个包装function1(soname) object1.so。所有后续调用 function1都将重新路由到包装器。

如果wrappers.so是以后dlclose'd,呼叫function1自然地路由到原来的。

或者,如果object1.so 是dlclose'd但wrappers.so仍然保留,则导出的包装器将wrappers.so 变为不活动的,因为无法获取 - 没有原始文件可以再调用。然而,Valgrind记得包装仍然存在。如果 object1.so最终再次出现,包装将再次活跃。

简而言之,valgrind会检查所有代码加载/卸载事件,以确保当前活动的包装器集合保持一致。

第二个可能的问题是冲突的包装器。很容易加载两个或更多的包装纸,这两个包装纸都称为包装纸,用于第三个功能。在这种情况下,Valgrind会在第二次出现时抱怨有冲突的包装者,并且只会荣获第一名。

3.3.4。调试

弄清楚发生了什么事情,因为包装的动态性质可能很困难。该 --trace-redir=yes选项可以通过在每个mmapmunmap 事件影响代码(文本)后显示重定向子系统的完整状态 。

有两个中心概念:

  • “重定向规范”是一个(soname pattern,fnname pattern)对与一个代码地址的绑定。这些绑定是通过使用I_WRAP_SONAME_FNNAME_{ZZ,_ZU} 宏创建的名称编写函数创建的 。

  • “活动重定向”是当前有效的代码地址绑定的代码地址。

包装和重定向子系统的状态包括一组规范和一组主动绑定。通过观看 代码(文本)部分的所有mmapmunmap事件来获取/丢弃规格 。从规范中重新计算主动绑定集,以及对规范集进行任何更改后,所有已知的符号名称。

--trace-redir=yes 在任何此类事件之后显示两个集合的内容。

-v 每次首次使用活动规范时,都会打印一行文本。

因此,为了最大化调试效率,您将需要使用这两个选项。

一个最后的评论。功能包装设备与Valgrind的替换(重定向)指定功能的能力密切相关,例如将调用重定向 malloc到其自身的实现。实际上,替换功能可以被认为是不称为原始的包装功能。然而,为了使实现更加健壮,两种截取(包装与替换)的处理方式不同。

--trace-redir=yes显示替换和包装功能的规范和绑定。为了区分两者,使用打印R->包装打印替换绑定 W->

3.3.5。限制 - 控制流程

在大多数情况下,函数包装实现是强大的。唯一重要的注意事项是:在包装器中,在调用任何其他包装功能之前,请OrigFn使用该信息 VALGRIND_GET_ORIG_FN。一旦你有了 OrigFn,任意调用之间的递归和longjumps之间的包装应该正常工作。包装函数之间从来没有任何交互,只是替换函数(例如malloc),所以你可以malloc从包装器中安全地调用 etc。

上述评论对于{x86,amd64,ppc32,arm,mips32,s390} -linux是正确的。由于ppc64-linux ABI(可能设计不佳)ppc64-linux功能的包装更加脆弱。这要求使用一个影子堆栈来跟踪包装和替换功能的条目/出口。这给出了两个限制:首先,包装器的长时间跳过会迅速导致灾难,因为阴影堆栈将无法正确清除。其次,由于阴影栈具有有限的大小,因此包装/替换功能之间的递归仅可能达到有限的深度,Valgrind不得不中止运行。这个深度目前是16个电话。

对于所有平台({x86,amd64,ppc32,ppc64,arm,mips32,s390} -linux),所有上述注释都适用于每个线程。换句话说,包装是线程安全的:每个线程必须单独遵守上述限制,但不需要进行任何类型的跨线程合作。

3.3.6。限制 - 原始功能签名

如上面的例子所示,要调用原来你必须使用宏的形式CALL_FN_*。由于技术原因,无法创建单个宏来处理所有参数类型和数字,因此提供了覆盖最常见情况的一系列宏。在下文中,'W'表示机器字型的值(指针或C long),'v'表示C的void类型。目前可用的宏是:

CALL_FN_v_v  - 调用类型为void fn(void)的原始文件
CALL_FN_W_v  - 调用long fn(void)类型的原始文件CALL_FN_v_W  - 调用类型为void fn(long)的原始文件
CALL_FN_W_W  - 调用long fn(long)类型的原始文件CALL_FN_v_WW  - 调用类型为void fn(long,long)的原始文件
CALL_FN_W_WW  - 调用long fn(long,long)类型的原始文件CALL_FN_v_WWW  - 调用类型为void fn(long,long,long)的原始文件
CALL_FN_W_WWW  - 调用long fn(long,long,long)CALL_FN_W_WWWW  - 调用long fn(long,long,long,long)类型的原始文件
CALL_FN_W_5W  - 调用long fn(long,long,long,long,long)
CALL_FN_W_6W  - 调用long fn(long,long,long,long,long,long)
等等,直到
CALL_FN_W_12W

可以根据需要扩展支持的类型集。令人遗憾的是,这种限制存在。功能包装已被证明难以实施,具有一定显然不可避免的水平。经过几次实施尝试,目前的安排似乎是最不利的权衡。至少它在动态链接和动态代码加载/卸载的情况下可靠地工作。

您不应尝试用不同类型签名的包装器包装一个类型签名的功能。这样的诡计一定会导致崩溃或奇怪的行为。这不是函数包装实现的限制,只是反映了如果你不小心,它给你扫射的力量来射击自己。想象一下,您可以通过编写一个与任何soname中的任何函数名称相匹配的包装器,实际上就是一个声称是进程中所有函数的包装器的破坏。

3.3.7。例子

在源代码树中, memcheck/tests/wrap[1-8].c提供了一系列的例子,从很简单到相当先进。

mpi/libmpiwrap.c是一个包装大而复杂的API(MPI-2接口)的例子。这个文件定义了近300个不同的包装器。

使用和了解Valgrind核心:高级主题相关推荐

  1. linux c 内存泄漏调试工具 《valgrind用户手册》 2. 使用和理解Valgrind核心

    valgrind 用户手册原版地址:https://valgrind.org/docs/manual/manual.html 此原文地址:https://valgrind.org/docs/manua ...

  2. 使用和了解Valgrind核心

    使用和了解Valgrind核心 目录 2.1.Valgrind与您的程序一致2.2.入门2.3.评论2.4.报告错误2.5.抑制错误2.6.核心命令行选项 2.6.1.工具选择选项2.6.2.基本选项 ...

  3. android开发笔记之高级主题—传感器的简单介绍

    今天我们开始进入讲解android中的一些高级主题的用法,比如传感器.GPS.NFC.语音和人脸识别等. 这次来对传感器的一个简单介绍: Android平台支持三大类的传感器: 位移传感器 这些传感器 ...

  4. ExoPlayer详解——高级主题(官方文档)

    ExoPlayer详解系列文章 ExoPlayer详解--入门(官方文档) ExoPlayer详解--媒体类型(官方文档) ExoPlayer详解--高级主题(官方文档) 一.数字版权管理 ExoPl ...

  5. Redis 高级主题之布隆过滤器(BloomFilter)

    最近计划准备整理几篇关于Reids高级主题的博文,本文整理的是关于布隆过滤器在Redis中如何应用,先来一张思维导图浏览全文. 1. 认识BloomFilter 1.1 原理 布隆过滤器,英文叫Blo ...

  6. Spread for Windows Forms高级主题(3)---单元格的编辑模式

    理解单元格的编辑模式 通常情况下,当终端用户双击单元格时,编辑控件将允许用户在该单元格中输入内容.在一个单元格中编辑的能力被称为编辑模式.一些属性和方法可以用来自定义编辑模式的使用. 当一个单元格处于 ...

  7. Spread for Windows Forms高级主题(5)---数据处理

    2019独角兽企业重金招聘Python工程师标准>>> 使用表单的API处理数据 你可以将数据以有格式或无格式字符串或者数据对象的形式填充到单元格中.将数据填充到单元格的最好方式取决 ...

  8. Spread for Windows Forms高级主题(7)---自定义打印的外观

    2019独角兽企业重金招聘Python工程师标准>>> 表单打印的多个部分都可以进行自定义,大多数的自定义选项存在于PrintInfo对象中.大多数打印选项是在PrintInfo对象 ...

  9. 分享21款漂亮的WordPress高级主题

     WordPress 是最流行的博客系统,各种主题非常多,其中很多的高级主题(Premium Theme)都是要付费购买的,今天本文收集到21款免费的 WordPress 高级主题分享给大家. Bol ...

最新文章

  1. 《自己动手写Docker》书摘之三: Linux UnionFS
  2. dart 语言是jvm_Dart编译技术在服务端的探索和应用
  3. 各个硬件指令集使用简单介绍
  4. 微服务微应用的安全测试_提高微服务安全性的11个方法
  5. shell脚本触发java程序支持传参补跑 +crontab定时器+每天生成日期文件_03
  6. 基于JAVA+SpringBoot+Mybatis+MYSQL的足球联赛管理系统
  7. 关于更新内容次序问题
  8. yield return 和 Func
  9. mysql 双1设置_2020-10-15:mysql的双1设置是什么?
  10. sql注入检测工具 mysql_SQL注入测试
  11. Windows系统常用网络命令详解及命令示例(全)
  12. Spring Boot(二):整合 JPA 及 事务控制
  13. 在计算机中汉字能否排序,你不得不知的Excel表格中汉字多种排序方法
  14. 计算机网络面试常见知识点(含HTTPS和TLS)
  15. 微软的“胡萝卜”会比“大棒”更有效吗
  16. C语言 八进制数转换为四进制
  17. 取球游戏(C++)[堆]
  18. android hal单元测试,用于HAL测试的参数化gtest
  19. 达梦dmrman dmap备份报[-7103]:创建命名管道失败
  20. super extend

热门文章

  1. python 字符串 find_Python 字符串 find() 方法
  2. 热图绘制一个快乐五一
  3. 在 Mac 上创建和移除替身
  4. 什么是创新?如何创新?
  5. js的正则自定义金额输入验证函数
  6. android麦克风监听动画效果,微信小程序实现录音时的麦克风动画效果实例
  7. 信息安全工程师笔记-国产密码算法(国密)概念
  8. Java笔记-CXF增加拦截器与自定义拦截器
  9. Leaflet文档阅读笔记-Quick Start Guide笔记
  10. Qt工作笔记-Qt奇淫技巧把ToolBar改成标题栏