内容

名称

Devel::Peek - XS 程序员的数据调试工具

语法

        use Devel::Peek;
        Dump( $a );
        Dump( $a, 5 );
        Dump( @a );
        Dump( %h );
        DumpArray( 5, $a, $b, ... );
	mstat "Point 5";

        use Devel::Peek ':opd=st';

说明

Devel::Peek 包含允许从 Perl 脚本操作原始 Perl 数据类型的函数。XS 编程人员使用它来检查他们从 C 发送到 Perl 的数据是否看起来像他们认为的那样。诀窍在于知道原始数据类型在到达 Perl 时应该是什么样子。本文档提供了一些提示和建议来描述好坏原始数据。

这份文档很可能对普通读者来说没什么用。读者应该了解 perlguts 前几节中的内容。

Devel::Peek 提供了一个 Dump() 函数,它可以转储原始 Perl 数据类型,以及一个 mstat("marker") 函数来报告内存使用情况(如果 perl 是使用相应选项编译的)。DeadCode() 函数提供有关冻结到非活动 CV 中的数据的统计信息。Devel::Peek 还提供了 SvREFCNT(),它可以查询 SV 上的引用计数。本文将采用被动且安全的方法进行数据调试,因此它只描述 Dump() 函数。

所有输出都发送到 STDERR。

Dump() 函数接受一个或两个参数:要转储的内容,以及递归和数组元素的可选限制(默认为 4)。第一个参数在 rvalue 标量上下文中求值,但 @array 和 %hash 除外,它们转储数组或哈希本身。因此,Dump @arrayDump $foo 都可以正常工作。而 Dump pos 将在 rvalue 上下文中调用 pos,而 Dump ${\pos} 将在 lvalue 上下文中调用它。

函数 DumpArray() 允许转储多个值(当您需要分析函数的返回值时很有用)。

全局变量 $Devel::Peek::pv_limit 可以设置为限制在各种字符串值中打印的字符数。将其设置为 0 表示没有限制。

如果 use Devel::Peek 指令带有 :opd=FLAGS 参数,则会开启对操作码调度的调试。FLAGS 应该是 stP 的组合(请参阅 perlrun 中的 -D 标志)。

:opd:opd=st 的快捷方式。

运行时调试

CvGV($cv) 返回与子例程引用 $cv 关联的一个全局变量。

debug_flags() 返回 $^D 的字符串表示形式(类似于 -D 标志允许的内容)。当使用数字参数调用时,将 $^D 设置为相应的值。当使用 "flags-flags" 形式的参数调用时,设置/取消 $^D 的位,这些位对应于 - 前后字母。(返回值是修改前的 $^D)。

如果当前操作码分派器是调试分派器,runops_debug() 返回 true。当使用参数调用时,根据参数切换到调试或非调试分派器(仅对新进入的子项等有效)。(返回的值是修改前的分派器。)

内存占用调试

当使用内存占用调试支持编译 Perl(Perl 的 malloc() 的默认设置)时,Devel::Peek 提供对该 API 的访问。

使用 mstat() 函数向终端发出内存状态统计信息。有关 mstat() 输出格式的详细信息,请参阅 perldebguts 中的"使用 $ENV{PERL_DEBUG_MSTATS}"

另外三个函数允许从 Perl 访问此统计信息。首先,使用 mstats_fillhash(%hash) 将 mstat() 输出中包含的信息获取到 %hash 中。此哈希的字段为

minbucket nbuckets sbrk_good sbrk_slack sbrked_remains sbrks
start_slack topbucket topbucket_ev topbucket_odd total total_chain
total_sbrk totfree

另外两个字段 freeused 包含数组引用,提供每个存储桶的空闲和已用块计数。另外两个字段 mem_sizeavailable_size 包含数组引用,提供每个存储桶中已分配大小和可用大小的信息。同样,有关详细信息,请参阅 perldebguts 中的"使用 $ENV{PERL_DEBUG_MSTATS}"

请记住,仅使用前几个“奇数”存储桶,因此未使用的“奇数”存储桶的大小信息可能没有意义。

中的信息

mem_size available_size minbucket nbuckets

是特定 Perl 版本的属性,不依赖于当前进程。如果您未向函数 mstats_fillhash()、fill_mstats()、mstats2hash() 提供可选参数,则字段 mem_sizeavailable_size 中的信息不会更新。

fill_mstats($buf) 是一个更便宜的调用(从速度和内存方面来说),它以机器可读的形式将统计信息收集到 $buf 中。稍后,您可能需要调用 mstats2hash($buf, %hash) 以使用此信息填充 %hash。

所有三个 API fill_mstats($buf)mstats_fillhash(%hash)mstats2hash($buf, %hash) 都设计为在同一 $buf 和/或 %hash 上第二次使用时不分配内存。

因此,如果您想按周期收集内存信息,您可以调用

$#buf = 999;
fill_mstats($_) for @buf;
mstats_fillhash(%report, 1);		# Static info too

foreach (@buf) {
  # Do something...
  fill_mstats $_;			# Collect statistic
}
foreach (@buf) {
  mstats2hash($_, %report);		# Preserve static info
  # Do something with %report
}

示例

以下示例并未尝试展示所有内容,因为那将是一项艰巨的任务,而且坦率地说,我们不希望此手册页成为 Perl 的内部文档。这些示例确实演示了原始 Perl 数据类型的一些基础知识,并且应该足以让大多数决心坚定的人入门。没有指导线或安全网,也没有开辟的道路,因此请做好从现在开始独自旅行的准备,并且如果可能的话,不要陷入流沙(这对业务不利)。

哦,最后一点建议:带上perlguts。我们希望在您返回时看到它被翻得破破烂烂。

一个简单的标量字符串

让我们从查看一个保存字符串的简单标量开始。

use Devel::Peek;
$a = 42; $a = "hello";
Dump $a;

输出

SV = PVIV(0xbc288) at 0xbe9a8
  REFCNT = 1
  FLAGS = (POK,pPOK)
  IV = 42
  PV = 0xb2048 "hello"\0
  CUR = 5
  LEN = 8

这表示$a是一个 SV,一个标量。标量类型是一个 PVIV,它能够保存一个整数 (IV) 和/或一个字符串 (PV) 值。标量的头部分配在地址 0xbe9a8,而主体在 0xbc288。它的引用计数为 1。它设置了POK标志,这意味着它当前的 PV 字段有效。由于设置了 POK,我们查看 PV 项以了解标量中有什么。末尾的 \0 指示此 PV 正确地以 NUL 结尾。请注意,IV 字段仍然包含其旧数值,但由于 FLAGS 没有设置 IOK,因此我们必须忽略 IV 项。CUR 指示 PV 中的字符数。LEN 指示为 PV 分配的字节数(至少比 CUR 多一个,因为 LEN 包含一个用于字符串结束标记的额外字节,然后通常向上舍入到某个高效分配单元)。

一个简单的标量数字

如果标量包含一个数字,则原始 SV 将更精简。

use Devel::Peek;
$a = 42;
Dump $a;

输出

SV = IV(0xbc818) at 0xbe9a8
  REFCNT = 1
  FLAGS = (IOK,pIOK)
  IV = 42

这表示$a是一个 SV,一个标量。标量是一个 IV,一个数字。它的引用计数为 1。它设置了IOK标志,这意味着它当前正被评估为一个数字。由于设置了 IOK,我们查看 IV 项以了解标量中有什么。

一个带有额外引用的简单标量

如果前一个示例中的标量有额外的引用

use Devel::Peek;
$a = 42;
$b = \$a;
Dump $a;

输出

SV = IV(0xbe860) at 0xbe9a8
  REFCNT = 2
  FLAGS = (IOK,pIOK)
  IV = 42

请注意,此示例与前一个示例的区别仅在于其引用计数。将此与下一个示例进行比较,其中我们转储$b而不是$a

对一个简单标量的引用

这显示了引用一个简单标量时的引用是什么样子。

use Devel::Peek;
$a = 42;
$b = \$a;
Dump $b;

输出

SV = IV(0xf041c) at 0xbe9a0
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0xbab08
  SV = IV(0xbe860) at 0xbe9a8
    REFCNT = 2
    FLAGS = (IOK,pIOK)
    IV = 42

从顶部开始,这表示$b是一个 SV。标量是一个 IV,它能够保存一个整数或引用值。它设置了ROK标志,这意味着它是一个引用(而不是一个整数或字符串)。请注意,Dump 遵循引用并向我们展示$b引用的内容。我们看到与我们在前一个示例中找到的相同的$a

请注意,RV的值与我们在将 $b 字符串化时看到的数字一致。IV() 中的地址是X***结构的地址,该结构保存SV的当前状态。此地址可能会在 SV 的生命周期内更改。

数组引用

以下显示数组引用的样子。

use Devel::Peek;
$a = [42];
Dump $a;

输出

SV = IV(0xc85998) at 0xc859a8
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0xc70de8
  SV = PVAV(0xc71e10) at 0xc70de8
    REFCNT = 1
    FLAGS = ()
    ARRAY = 0xc7e820
    FILL = 0
    MAX = 0
    FLAGS = (REAL)
    Elt No. 0
    SV = IV(0xc70f88) at 0xc70f98
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 42

这表示$a是一个引用(ROK),指向另一个 SV,该 SV 是一个 PVAV,即一个数组。该数组有一个元素,即元素零,它是一个另一个 SV。上面的FILL字段表示数组中的最后一个元素,类似于$#$a

如果$a指向一个包含两个元素的数组,那么我们将看到以下内容。

use Devel::Peek 'Dump';
$a = [42,24];
Dump $a;

输出

SV = IV(0x158c998) at 0x158c9a8
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x1577de8
  SV = PVAV(0x1578e10) at 0x1577de8
    REFCNT = 1
    FLAGS = ()
    ARRAY = 0x1585820
    FILL = 1
    MAX = 1
    FLAGS = (REAL)
    Elt No. 0
    SV = IV(0x1577f88) at 0x1577f98
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 42
    Elt No. 1
    SV = IV(0x158be88) at 0x158be98
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 24

请注意,Dump不会报告数组中的所有元素,只会报告前几个元素(取决于它在报告树中已经深入到了什么程度)。

哈希引用

以下显示哈希引用的原始形式。

use Devel::Peek;
$a = {hello=>42};
Dump $a;

输出

SV = IV(0x55cb50b50fb0) at 0x55cb50b50fc0
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0x55cb50b2b758
  SV = PVHV(0x55cb50b319c0) at 0x55cb50b2b758
    REFCNT = 1
    FLAGS = (SHAREKEYS)
    ARRAY = 0x55cb50b941a0  (0:7, 1:1)
    hash quality = 100.0%
    KEYS = 1
    FILL = 1
    MAX = 7
    Elt "hello" HASH = 0x3128ece4
    SV = IV(0x55cb50b464f8) at 0x55cb50b46508
      REFCNT = 1
      FLAGS = (IOK,pIOK)
      IV = 42

这显示$a是一个指向 SV 的引用。该 SV 是一个 PVHV,即一个哈希。

哈希的“质量”定义为访问每个元素一次所需的比较总数,相对于随机哈希所需的预期数量。该值可以超过 100%。

比较总数等于每个存储桶中条目数的平方和。对于将<n>个键哈希到<k>个存储桶的随机哈希,预期值为

n + n(n-1)/2k

转储大数组或哈希

默认情况下,Dump()函数最多转储来自顶级数组或哈希的 4 个元素。可以通过向函数提供第二个参数来增加此数字。

use Devel::Peek;
$a = [10,11,12,13,14];
Dump $a;

请注意,在上面的代码中,Dump()只打印元素 10 到 13。以下代码将打印所有元素。

use Devel::Peek 'Dump';
$a = [10,11,12,13,14];
Dump $a, 5;

包含 C 指针的 SV 引用

当然,作为一名 XS 程序员,这正是你真正需要知道的。当一个 XSUB 返回一个指向 C 结构的指针时,该指针将存储在一个 SV 中,并且对该 SV 的引用将放在 XSUB 栈上。因此,使用类似于 T_PTROBJ 映射的 XSUB 的输出可能如下所示

SV = IV(0xf381c) at 0xc859a8
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0xb8ad8
  SV = PVMG(0xbb3c8) at 0xc859a0
    REFCNT = 1
    FLAGS = (OBJECT,IOK,pIOK)
    IV = 729160
    NV = 0
    PV = 0
    STASH = 0xc1d10       "CookBookB::Opaque"

这表明我们有一个 SV 是一个引用,它指向另一个 SV。在这种情况下,第二个 SV 是一个 PVMG,一个受祝福的标量。因为它受祝福,所以设置了 OBJECT 标志。请注意,持有 C 指针的 SV 也设置了 IOK 标志。STASH 设置为这个 SV 被祝福的包名。

使用类似于 T_PTRREF 映射的 XSUB 的输出(它不会祝福对象)可能看起来像这样

SV = IV(0xf381c) at 0xc859a8
  REFCNT = 1
  FLAGS = (ROK)
  RV = 0xb8ad8
  SV = PVMG(0xbb3c8) at 0xc859a0
    REFCNT = 1
    FLAGS = (IOK,pIOK)
    IV = 729160
    NV = 0
    PV = 0

对子例程的引用

看起来像这样

SV = IV(0x24d2dd8) at 0x24d2de8
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x24e79d8
  SV = PVCV(0x24e5798) at 0x24e79d8
    REFCNT = 2
    FLAGS = ()
    COMP_STASH = 0x22c9c50	"main"
    START = 0x22eed60 ===> 0
    ROOT = 0x22ee490
    GVGV::GV = 0x22de9d8	"MY" :: "top_targets"
    FILE = "(eval 5)"
    DEPTH = 0
    FLAGS = 0x0
    OUTSIDE_SEQ = 93
    PADLIST = 0x22e9ed8
    PADNAME = 0x22e9ec0(0x22eed00) PAD = 0x22e9ea8(0x22eecd0)
    OUTSIDE = 0x22c9fb0 (MAIN)

这表明

EXPORTS

默认情况下是 DumpmstatDeadCodeDumpArrayDumpWithOPDumpProgfill_mstatsmstats_fillhashmstats2hash。此外还有 SvREFCNTSvREFCNT_incSvREFCNT_dec

BUGS

已知读者会跳过 perlguts 的重要部分,这会给所有人带来很大的挫败感。

AUTHOR

Ilya Zakharevich [email protected]

版权所有 (c) 1995-98 Ilya Zakharevich。保留所有权利。此程序是免费软件;您可以在与 Perl 本身相同的条款下重新分发或修改它。

此软件的作者不对此产品的适用性、可靠性、可编辑性、可编辑性或可用性做出任何声明,也不应对因使用它而造成的任何损害承担责任。如果您能使用它,您很幸运,如果不能,我不应该承担责任。请随时准备好您的备份磁带副本。

另请参见

perlguts,以及 perlguts,再次。