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 @array
可以運作,Dump $foo
也可以。而 Dump pos
會在 rvalue 內容中呼叫 pos
,而 Dump ${\pos}
會在 lvalue 內容中呼叫它。
函數 DumpArray()
允許傾印多個值(在需要分析函數回傳值時很有用)。
全域變數 $Devel::Peek::pv_limit 可以設定為限制在各種字串值中印出的字元數。設定為 0 表示沒有限制。
如果 use Devel::Peek
指令有一個 :opd=FLAGS
引數,這會開啟 opcode 調度的除錯。FLAGS
應該是 s
、t
和 P
的組合(請參閱 -D perlrun 中的旗標)。
:opd
是 :opd=st
的捷徑。
CvGV($cv)
回傳與子常式參考 $cv 相關聯的一個 glob。
debug_flags() 回傳一個 $^D
的字串表示(類似於 -D 旗標允許的)。當使用數字引數呼叫時,會將 $^D 設定為對應的值。當使用 "flags-flags"
形式的引數呼叫時,會設定/取消 $^D
的位元,對應於 -
前/後的字母。(回傳值是修改前的 $^D
。)
runops_debug() 如果目前的 opcode dispatcher 是除錯用的,則回傳 true。當使用引數呼叫時,會根據引數切換到除錯或非除錯 dispatcher(僅對新輸入的子常式等有效)。(回傳值是修改前的 dispatcher。)
當 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
另外兩個欄位 free
、used
包含陣列參考,可提供每個區塊的可用和已使用的區塊數。另外兩個欄位 mem_size
、available_size
包含陣列參考,可提供每個區塊中已配置大小和可用大小的資訊。同樣地,請參閱 "perldebguts 中的「使用 $ENV{PERL_DEBUG_MSTATS}」" 以取得詳細資訊。
請記住,只會使用前幾個「奇數」區塊,因此未使用的「奇數」區塊大小資訊可能沒有意義。
中的資訊
mem_size available_size minbucket nbuckets
是特定 perl 建置的屬性,與目前的處理程序無關。如果您未提供 mstats_fillhash()、fill_mstats()、mstats2hash() 函式的選用引數,則 mem_size
、available_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,一個陣列。這個陣列有一個元素,元素 0,它是一個另一個 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;
當然,這是 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)
這表示
子常式不是 XSUB(因為 START
和 ROOT
非零,而且未列出 XSUB
,因此為空值);
它編譯於套件 main
中;
名稱為 MY::top_targets
;
在程式中的第 5 個 eval 內部;
它目前未執行(因為 DEPTH
為 0);
它沒有原型(缺少 PROTOTYPE
欄位)。
預設為 Dump
、mstat
、DeadCode
、DumpArray
、DumpWithOP
和 DumpProg
、fill_mstats
、mstats_fillhash
、mstats2hash
。此外,還提供 SvREFCNT
、SvREFCNT_inc
和 SvREFCNT_dec
。
已知讀者會跳過 perlguts 的重要部分,造成大家的極度沮喪。
Ilya Zakharevich ilya@math.ohio-state.edu
版權所有 (c) 1995-98 Ilya Zakharevich。保留所有權利。此程式為免費軟體;您可以在與 Perl 相同的條款下重新散布或修改它。
此軟體的作者對於此產品的適用性、可靠性、可編輯性、可編輯性或可用性不作任何主張,且不應對因使用此產品而造成的任何損害負責。如果您能使用它,您很幸運,如果不能,我不應負責。請隨時準備好備份磁帶的副本。