第 20 章 除錯器 (ocamldebug)

本章描述 OCaml 原始碼層級的重播除錯器 ocamldebug

Unix:  除錯器在提供 BSD Socket 的 Unix 系統上可用。
Windows:  除錯器在 OCaml 的 Cygwin 移植版下可用,但在原生的 Win32 移植版下不可用。

1 編譯以進行除錯

在使用除錯器之前,程式必須使用 -g 選項進行編譯和連結:程式中包含的所有 .cmo.cma 檔案都應使用 ocamlc -g 建立,並且必須使用 ocamlc -g 連結在一起。

使用 -g 編譯不會對程式的執行時間造成任何影響:物件檔案和位元組碼可執行檔案會較大且產生時間較長,但可執行檔案的執行速度與未使用 -g 編譯時完全相同。

2 呼叫

2.1 啟動除錯器

OCaml 除錯器透過執行程式 ocamldebug 並將位元組碼可執行檔案的名稱作為第一個引數來呼叫

        ocamldebug [options] program [arguments]

程式 之後的引數是選用的,並作為命令列引數傳遞給要除錯的程式。(另請參閱 set arguments 命令。)

以下命令列選項會被識別

-c 計數
將同時存在的檢查點最大數量設定為 計數
-cd 目錄
從工作目錄 目錄 而不是當前目錄執行除錯器程式。(另請參閱 cd 命令。)
-emacs
告知除錯器它是在 Emacs 下執行的。(關於如何在 Emacs 下執行除錯器的資訊,請參閱第 ‍20.10 節。)
-I 目錄
目錄 新增至搜尋來源檔案和編譯檔案的目錄清單。(另請參閱 directory 命令。)
-s Socket
使用 Socket 與除錯程式通訊。有關 Socket 的格式,請參閱 set socket 命令的說明(第 ‍20.8.8 節)。
-version
列印版本字串並結束。
-vnum
列印簡短的版本號碼並結束。
-help--help
顯示簡短的使用方式摘要並結束。

2.2 初始化檔案

在啟動時,除錯器會先從初始化檔案讀取命令,然後才將控制權交給使用者。如果存在,預設檔案是當前目錄中的 .ocamldebug,否則就是使用者主目錄中的 .ocamldebug

2.3 結束除錯器

quit 命令會結束除錯器。您也可以透過輸入檔案結尾字元 (通常是 ctrl-D) 來結束除錯器。

輸入中斷字元 (通常是 ctrl-C) 不會結束除錯器,而是會終止任何正在進行中的除錯器命令的動作,並返回除錯器命令層級。

3 命令

除錯器命令是一行輸入。它以命令名稱開頭,後面接著取決於此名稱的引數。範例

        run
        goto 1000
        set arguments arg1 arg2

只要沒有歧義,就可以截斷命令名稱。例如,go 1000 會被理解為 goto 1000,因為沒有其他命令的名稱以 go 開頭。對於最常用的命令,允許有歧義的縮寫。例如,r 代表 run,即使有其他命令以 r 開頭。您可以使用 help 命令測試縮寫的有效性。

如果先前的命令成功,則空白行 (僅輸入 RET) 會重複該命令。

3.1 取得協助

OCaml 除錯器有一個簡單的線上說明系統,其中提供了每個命令和變數的簡短描述。

help
列印命令清單。
help 命令
提供有關 命令 的協助。
help set 變數, help show 變數
提供有關 變數 的協助。可以使用 help set 取得所有除錯器變數的清單。
help info 主題
提供有關 主題 的協助。使用 help info 取得已知主題的清單。

3.2 存取除錯器狀態

set 變數
將除錯器變數 變數 設定為值
show 變數
列印除錯器變數 變數 的值。
info 主題
提供有關給定主題的資訊。例如,info breakpoints 將列印所有中斷點的清單。

4 執行程式

4.1 事件

事件是原始碼中「有趣」的位置,對應於「有趣」子運算式的評估開始或結束。事件是單步執行的單位 (單步執行會跳到程式執行中遇到的下一個或上一個事件)。此外,只能在事件上設定中斷點。因此,事件在傳統語言的除錯器中扮演行號的角色。

在程式執行期間,每遇到一個事件,計數器就會遞增。此計數器的值稱為「目前時間」。由於有反向執行,因此可以來回跳轉到執行的任何時間。

以下是除錯器事件 (寫成 ⋈) 在原始碼中的位置

例外:函數應用後跟隨函數返回會由編譯器替換為跳轉 (尾部呼叫最佳化)。在這種情況下,函數應用之後不會放置事件。

4.2 啟動除錯程式

除錯器僅在需要時才開始執行除錯程式。這允許在執行開始之前設定中斷點或指派除錯器變數。啟動執行有多種方式

run
執行程式,直到命中中斷點或程式終止。
goto 0
載入程式並在第一個事件停止。
goto 時間
載入程式並將其執行到給定的時間。當您已經大致知道問題發生的時間時,此方法很有用。也可用於在時間 0 尚未計算出函數值時設定中斷點 (請參閱第 ‍20.5 節)。

程式的執行會受到除錯器啟動時接收到的某些資訊影響,例如程式的命令列引數及其工作目錄。除錯器提供命令來指定此資訊(set argumentscd)。這些命令必須在程式執行開始之前使用。如果您在程式啟動後嘗試變更引數或工作目錄,除錯器將會終止程式(在要求確認後)。

4.3 執行程式

以下命令從目前時間開始向前或向後執行程式。執行將在命令指定時或遇到中斷點時停止。

run
從目前時間向前執行程式。在下一個中斷點或程式終止時停止。
reverse
從目前時間向後執行程式。主要用於回到目前時間之前遇到的最後一個中斷點。
step [count]
執行程式並在下一個事件停止。使用引數時,執行 count 次。如果 count 為 0,則執行直到程式終止或命中中斷點。
backstep [count]
向後執行程式並在先前的事件停止。使用引數時,執行 count 次。
next [count]
執行程式並在下一個事件停止,跳過函式呼叫。使用引數時,執行 count 次。
previous [count]
向後執行程式並在先前的事件停止,跳過函式呼叫。使用引數時,執行 count 次。
finish
執行程式直到目前的函式返回。
start
向後執行程式並在目前函式呼叫之前的第一個事件停止。

4.4 時間旅行

您可以使用 goto 命令直接跳轉到給定的時間,而不會在中斷點停止。

當您在程式中移動時,除錯器會維護您停止時的連續時間歷史記錄。last 命令可用於重新瀏覽這些時間:每個 last 命令都會在歷史記錄中向後移動一步。這主要用於還原諸如 stepnext 之類的命令。

goto 時間
跳轉到給定的時間。
last [count]
回到執行歷史記錄中記錄的最新時間。使用引數時,執行 count 次。
set history size
設定執行歷史記錄的大小。

4.5 終止程式

kill
終止正在執行的程式。如果您希望在不離開除錯器的情況下重新編譯程式,此命令主要很有用。

5 中斷點

中斷點會導致程式在到達程式中的特定點時停止。可以使用 break 命令以多種方式設定中斷點。設定中斷點時會為其指派數字,以供進一步參考。設定中斷點最方便的方法是通過 Emacs 介面(請參閱第 ‍20.10節)。

break
在程式執行中的目前位置設定中斷點。目前位置必須在事件上(即,既不在程式的開始,也不在程式的結束)。
break function
function 的開頭設定中斷點。這僅在已計算出識別符號 function 的功能值並將其指派給識別符號時才起作用。因此,此命令不能在程式執行的一開始使用,因為所有識別符號仍未定義;使用 goto time 來推進執行直到功能值可用。
break @ [module] line
在模組 module 中(如果未給定 module,則在目前模組中),在行 line 的第一個事件處設定中斷點。
break @ [module] line column
在模組 module 中(如果未給定 module,則在目前模組中),在最接近行 line、列 column 的事件處設定中斷點。
break @ [module] # character
在模組 module 中,在最接近字元編號 character 的事件處設定中斷點。
break frag:pc, break pc
在程式碼位址 frag:pc 處設定中斷點。整數 frag 是程式碼片段的識別符號,這是一組已同時載入的模組,無論是最初還是使用 Dynlink 模組。整數 pc 是此程式碼片段內的指令計數器。如果省略 frag,則預設為 0,這是最初載入的程式的程式碼片段。
delete [breakpoint-numbers]
刪除指定的中斷點。如果沒有引數,則會刪除所有中斷點(在要求確認後)。
info breakpoints
列印所有中斷點的清單。

6 呼叫堆疊

每次程式執行函式應用時,它都會將應用程式的位置(返回位址)儲存在稱為堆疊框架的資料區塊中。框架還包含呼叫函式的局部變數。所有框架都配置在稱為呼叫堆疊的記憶體區域中。backtrace(或 bt)命令會顯示呼叫堆疊的部分內容。

在任何時候,除錯器都會「選取」其中一個堆疊框架;多個除錯器命令會隱式地參考所選取的框架。特別是,每當您向除錯器要求局部變數的值時,該值會在所選取的框架中找到。命令 frameupdown 會選擇您感興趣的任何框架。

當程式停止時,除錯器會自動選取目前正在執行的框架,並簡要描述它,如同 frame 命令所做的那樣。

frame
描述目前選取的堆疊框架。
frame frame-number
按數字選取堆疊框架並加以描述。程式停止時目前正在執行的框架編號為 0;它的呼叫者編號為 1;依此類推,直到呼叫堆疊的頂端。
backtrace [count], bt [count]
列印呼叫堆疊。這對於查看導致目前正在執行的框架的函式呼叫順序很有用。使用正引數時,僅列印最內層的 count 個框架。使用負引數時,僅列印最外層的 -count 個框架。
up [count]
選取並顯示剛好在所選框架「上方」的堆疊框架,也就是呼叫所選框架的框架。引數表示要向上移動多少個框架。
down [count]
選取並顯示剛好在所選框架「下方」的堆疊框架,也就是所選框架所呼叫的框架。引數表示要向下移動多少個框架。

7 檢查變數值

除錯器可以列印簡單表示式的目前值。表示式可以包含程式變數:可以存取所選程式點範圍內的所有識別符號。

可以列印的表示式是 OCaml 表示式的子集,如以下文法所述

simple-expr::= lowercase-ident
  { capitalized-ident. } lowercase-ident
 *
 $integer
 simple-expr.lowercase-ident
 simple-expr.(integer)
 simple-expr.[integer]
 !simple-expr
 (simple-expr)

前兩種情況是指值識別符號,無論是否由定義它的結構路徑限定。* 是指剛計算出的結果(通常是函式應用程式的值),並且僅當選取的事件是「之後」事件(通常是函式應用程式)時才有效。$ integer 是指先前列印的值。其餘四種形式選取表示式的一部分:分別是記錄欄位、陣列元素、字串元素和參考的目前內容。

print variables
列印給定變數的值。print 可以縮寫為 p
display variables
print 相同,但將列印的深度限制為 1。適用於瀏覽大型資料結構而不完整列印它們。display 可以縮寫為 d

當列印一個複雜的表達式時,會自動為其值指派一個形式為 $整數 的名稱。如果值的部分由於超過最大列印深度而無法列印,也會指派這樣的名稱。之後可以使用命令 p $整數d $整數 來列印已命名的值。已命名的值僅在程式停止時有效。一旦程式恢復執行,這些值就會被遺忘。

set print_depth d
限制值的列印深度最多為 d
set print_length l
限制值的列印最多為 l 個節點。

8 控制除錯器

8.1 設定程式名稱和參數

set program 檔案
將程式名稱設定為 檔案
set arguments 參數
參數 作為程式的命令列參數。

會使用 shell 將參數傳遞給除錯的程式。因此您可以在參數中使用萬用字元、shell 變數和檔案重新導向。為了除錯從標準輸入讀取的程式,建議從檔案重新導向其輸入(使用 set arguments < 輸入檔),否則程式的輸入和除錯器的輸入無法正確分離,且當程式反向執行時,輸入無法正確重播。

8.2 程式的載入方式

loadingmode 變數控制程式的執行方式。

set loadingmode direct
程式由除錯器直接執行。這是預設模式。
set loadingmode runtime
除錯器在程式上執行 OCaml 執行期 ocamlrun。很少有用;此外,它會阻止對以「自訂執行期」模式編譯的程式進行除錯。
set loadingmode manual
使用者在除錯器要求時手動啟動程式。允許遠端除錯(請參閱章節 ‍20.8.8)。

8.3 檔案的搜尋路徑

除錯器會在目錄清單(即搜尋路徑)中搜尋原始碼檔案和已編譯的介面檔案。搜尋路徑最初包含目前的目錄 . 和標準程式庫目錄。directory 命令會將目錄新增至路徑。

每當修改搜尋路徑時,除錯器都會清除它可能已快取的任何檔案資訊。

directory 目錄名稱
將指定的目錄新增至搜尋路徑。這些目錄會新增到最前面,因此會優先搜尋。
directory 目錄名稱 for 模組名稱
directory 目錄名稱 相同,但指定的目錄僅在尋找已封裝到 模組名稱 中的模組的原始碼檔案時才會搜尋。
directory
重設搜尋路徑。這需要確認。

8.4 工作目錄

每次在除錯器中啟動程式時,它都會從除錯器的目前工作目錄繼承其工作目錄。此工作目錄最初是它從其父處理程序(通常是 shell)繼承的任何目錄,但您可以使用 cd 命令或 -cd 命令列選項在除錯器中指定新的工作目錄。

cd 目錄
ocamldebug 的工作目錄設定為 目錄
pwd
列印 ocamldebug 的工作目錄。

8.5 開啟和關閉反向執行

在某些情況下,您可能需要關閉反向執行。這樣可以加快程式執行速度,而且有時對互動式程式也很有用。

通常,除錯器會不時取得程式狀態的檢查點。也就是說,它會複製程式的目前狀態(使用 Unix 系統呼叫 fork)。如果變數 checkpoints 設定為 off,則除錯器將不會取得任何檢查點。

set checkpoints on/off
選取除錯器是否要取得檢查點。

8.6 除錯器關於 fork 的行為

當程式發出 fork 呼叫時,除錯器可以跟隨子處理程序或父處理程序。預設情況下,除錯器會跟隨父處理程序。變數 follow_fork_mode 控制此行為

set follow_fork_mode child/parent
選取在呼叫 fork 時要跟隨子處理程序還是父處理程序。

8.7 在載入新程式碼時停止執行

除錯器與 Dynlink 模組相容。但是,當外部模組尚未載入時,無法在其程式碼中設定中斷點。為了方便在動態載入的程式碼中設定中斷點,除錯器會在每次載入新模組時停止程式。可以使用 break_on_load 變數停用此行為

set break_on_load on/off
選取是否要在載入新程式碼後停止。

8.8 除錯器和程式之間的通訊

除錯器會透過 Unix socket 與要除錯的程式通訊。您可能需要變更 socket 名稱,例如如果您需要在一部電腦上執行除錯器,而在另一部電腦上執行程式。

set socket socket
使用 socket 與程式通訊。socket 可以是檔案名稱,或是網際網路連接埠規格 主機:連接埠,其中 主機 是主機名稱或點分表示法的網際網路位址,而 連接埠 是主機上的連接埠號碼。

在除錯程式端,socket 名稱會透過 CAML_DEBUG_SOCKET 環境變數傳遞。

8.9 微調除錯器

有數個變數可用於微調除錯器。會提供合理的預設值,通常您不必變更這些值。

set processcount 計數
將檢查點的最大數目設定為 計數。較多的檢查點有助於回溯更久的時間,但會使用更多記憶體並建立更多 Unix 處理程序。

由於取得檢查點的成本相當高,因此不能太頻繁地執行。另一方面,如果更頻繁地取得檢查點,則反向執行速度會更快。特別是,如果在目前時間之前已取得許多檢查點,則反向單步執行會更靈敏。為了微調檢查點策略,除錯器不會以相同的頻率取得長位移(例如 run)和短位移(例如 step)的檢查點。兩個變數 bigstepsmallstep 包含在每種情況下兩個檢查點之間的事件數。

set bigstep 計數
設定長位移的兩個檢查點之間的事件數。
set smallstep 計數
設定短位移的兩個檢查點之間的事件數。

以下命令會顯示有關檢查點和事件的資訊

info checkpoints
列印檢查點清單。
info events [模組]
列印指定模組中的事件清單(預設為目前的模組)。

8.10 使用者定義的列印器

如同在頂層系統中(章節 ‍14.2),使用者可以註冊用於列印某些類型值的函數。由於技術原因,除錯器無法呼叫駐留在要除錯程式中的列印函數。因此,列印函數的程式碼必須在除錯器中明確載入。

load_printer "檔案名稱"
在除錯器中載入指定的 .cmo.cma 物件檔。該檔案會載入到一個環境中,該環境僅包含 OCaml 標準程式庫,以及先前使用 load_printer 載入的物件檔案所提供的定義。如果此檔案相依於其他尚未載入的物件檔案,則除錯器如果能夠在搜尋路徑中找到這些檔案,則會自動載入這些檔案。已載入的檔案無法直接存取要除錯程式的模組。
install_printer 列印器名稱
將名為 列印器名稱 的函數(值路徑)註冊為類型與函數引數類型相符的物件的列印器。也就是說,當除錯器有此類物件要列印時,將會呼叫 列印器名稱。列印函數 列印器名稱 必須使用 Format 程式庫模組產生其輸出,否則其輸出將無法在頂層迴圈列印的值中正確定位。

值路徑 列印器名稱 必須參考使用 load_printer 載入的物件檔案所定義的函數之一。它不能參考要除錯程式的函數。

remove_printer 列印器名稱
從值列印器表格中移除指定的函數。

9 其他命令

list [模組] [起始] [結束]
列出模組 模組 的來源,從行號 起始 到行號 結束。預設情況下,會顯示目前模組的 20 行,從目前位置之前 10 行開始。
source 檔案名稱
從指令碼 檔案名稱 讀取除錯器命令。

10 在 Emacs 下執行除錯器

使用除錯器最友善的方式是在 Emacs 下執行它,搭配透過 MELPA 以及 https://github.com/ocaml/caml-mode 提供的 OCaml 模式。

OCaml 除錯器在 Emacs 環境下透過命令 M-x camldebug 啟動,並以要除錯的可執行檔名稱 progname 作為參數。與除錯器的通訊會在名為 *camldebug-progname* 的 Emacs 緩衝區中進行。Shell 模式的編輯和歷史記錄功能可用於與除錯器互動。

此外,Emacs 會顯示包含目前事件(程式執行中的目前位置)的原始碼檔案,並醒目提示事件的位置。此顯示會與除錯器的動作同步更新。

*camldebug-progname* 緩衝區中,可以使用以下最常見的除錯器命令的綁定:

C-c C-s
(命令 step):程式向前執行一步。
C-c C-k
(命令 backstep):程式向後執行一步。
C-c C-n
(命令 next):程式向前執行一步,跳過函式呼叫。
滑鼠中鍵
(命令 display):顯示具名數值。滑鼠游標下的 $n(支援大型資料結構的遞增瀏覽)。
C-c C-p
(命令 print):印出游標位置的識別符號數值。
C-c C-d
(命令 display):顯示游標位置的識別符號數值。
C-c C-r
(命令 run):程式向前執行到下一個中斷點。
C-c C-v
(命令 reverse):程式向後執行到最近的中斷點。
C-c C-l
(命令 last):在命令歷史記錄中後退一步。
C-c C-t
(命令 backtrace):顯示函式呼叫的回溯追蹤。
C-c C-f
(命令 finish):向前執行直到目前函式返回。
C-c <
(命令 up):選擇目前框架下方的堆疊框架。
C-c >
(命令 down):選擇目前框架上方的堆疊框架。

在 OCaml 編輯模式的所有緩衝區中,也可以使用以下除錯器命令:

C-x C-a C-b
(命令 break):在最靠近游標的事件處設定中斷點。
C-x C-a C-p
(命令 print):印出游標位置的識別符號數值。
C-x C-a C-d
(命令 display):顯示游標位置的識別符號數值。