第 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 事件
事件是原始碼中「有趣」的位置,對應於「有趣」子運算式的評估開始或結束。事件是單步執行的單位 (單步執行會跳到程式執行中遇到的下一個或上一個事件)。此外,只能在事件上設定中斷點。因此,事件在傳統語言的除錯器中扮演行號的角色。
在程式執行期間,每遇到一個事件,計數器就會遞增。此計數器的值稱為「目前時間」。由於有反向執行,因此可以來回跳轉到執行的任何時間。
以下是除錯器事件 (寫成 ⋈) 在原始碼中的位置
- 在函數應用之後
(f arg)⋈
- 在進入函數時
fun x y z -> ⋈ ...
- 在模式比對定義 (函數、match…with 建構、try…with 建構) 的每個情況下
function pat1 -> ⋈ expr1
| ...
| patN -> ⋈ exprN
- 在序列的子運算式之間
expr1; ⋈ expr2; ⋈ ...; ⋈ exprN
- 在條件運算式的兩個分支中
if cond then ⋈ expr1 else ⋈ expr2
- 在迴圈的每次迭代開始時
while cond do ⋈ body done
for i = a to b do ⋈ body done
例外:函數應用後跟隨函數返回會由編譯器替換為跳轉 (尾部呼叫最佳化)。在這種情況下,函數應用之後不會放置事件。
4.2 啟動除錯程式
除錯器僅在需要時才開始執行除錯程式。這允許在執行開始之前設定中斷點或指派除錯器變數。啟動執行有多種方式
-
run
- 執行程式,直到命中中斷點或程式終止。
- goto 0
- 載入程式並在第一個事件停止。
- goto 時間
- 載入程式並將其執行到給定的時間。當您已經大致知道問題發生的時間時,此方法很有用。也可用於在時間 0 尚未計算出函數值時設定中斷點 (請參閱第 20.5 節)。
程式的執行會受到除錯器啟動時接收到的某些資訊影響,例如程式的命令列引數及其工作目錄。除錯器提供命令來指定此資訊(set arguments 和 cd)。這些命令必須在程式執行開始之前使用。如果您在程式啟動後嘗試變更引數或工作目錄,除錯器將會終止程式(在要求確認後)。
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 命令都會在歷史記錄中向後移動一步。這主要用於還原諸如 step 和 next 之類的命令。
-
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)命令會顯示呼叫堆疊的部分內容。
在任何時候,除錯器都會「選取」其中一個堆疊框架;多個除錯器命令會隱式地參考所選取的框架。特別是,每當您向除錯器要求局部變數的值時,該值會在所選取的框架中找到。命令 frame、up 和 down 會選擇您感興趣的任何框架。
當程式停止時,除錯器會自動選取目前正在執行的框架,並簡要描述它,如同 frame 命令所做的那樣。
-
frame
- 描述目前選取的堆疊框架。
- frame frame-number
- 按數字選取堆疊框架並加以描述。程式停止時目前正在執行的框架編號為 0;它的呼叫者編號為 1;依此類推,直到呼叫堆疊的頂端。
- backtrace [count], bt [count]
- 列印呼叫堆疊。這對於查看導致目前正在執行的框架的函式呼叫順序很有用。使用正引數時,僅列印最內層的 count 個框架。使用負引數時,僅列印最外層的 -count 個框架。
- up [count]
- 選取並顯示剛好在所選框架「上方」的堆疊框架,也就是呼叫所選框架的框架。引數表示要向上移動多少個框架。
- down [count]
- 選取並顯示剛好在所選框架「下方」的堆疊框架,也就是所選框架所呼叫的框架。引數表示要向下移動多少個框架。
7 檢查變數值
除錯器可以列印簡單表示式的目前值。表示式可以包含程式變數:可以存取所選程式點範圍內的所有識別符號。
可以列印的表示式是 OCaml 表示式的子集,如以下文法所述
前兩種情況是指值識別符號,無論是否由定義它的結構路徑限定。* 是指剛計算出的結果(通常是函式應用程式的值),並且僅當選取的事件是「之後」事件(通常是函式應用程式)時才有效。$ 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)的檢查點。兩個變數 bigstep 和 smallstep 包含在每種情況下兩個檢查點之間的事件數。
-
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):顯示游標位置的識別符號數值。
版權 © 2024 法國國家資訊與自動化研究院