Scite Debug |
|
scite-debug
完全用 SciTE Lua 編寫,與標準 5.1 相容。唯一絕對不可或缺的 C 程式碼是產生器,它會擷取互動式指令列除錯器,例如 GDB,讓 scite-debug
可以將指令寫入其中。輸出導向至指定的一般函式,使用適當的主執行緒技術;在 Windows 上,我將訊息傳遞至偵錯器執行緒的 wndproc,而在 GTK 上使用非同步輸出機制。在兩個平台上,我們以整行方式擷取偵錯器輸出,意即在除錯會發出提示訊息的互動式程式時,會有限制。然而,在 Windows 端,我們會為偵錯的程式要求一個獨立的控制台視窗,用以區分程式的輸入和輸出,並對 Windows 控制台應用程式除錯。
程式完全有可能沒有可除錯符號。遇到這種情況時,表示我們僅對除錯程式載入的共用函式庫感興趣。因此,我們通常會在這些函式庫設定中斷點,但 GDB 必須接受這種做法。如果 debug.target 開頭為 '[n]',則 GDB 會載入一個任意的小符號檔案 (由 stubby.so|.dll 提供)。此時,它很樂意將任何未解析的中斷點要求視為暫存。(請注意,您需要 GDB 的 vs 6 版本;如果您使用的是 MinGW,請下載較新的 GDB,如果您仍在執行 vs 5 的話。)
可使用 debug.environment
屬性為除錯目標設定環境變數。這個變數是一個以分號分隔的 VAR=VAL 配對清單。
一般的受控偵錯器會話需要設定中斷點,並開始執行程式,並使用 檢視 | 參數
指定的參數。在執行程式之前設定的中斷點會寫入組態檔案;在 GDB 的情況下,它稱為 'prompt.cmd'。這個檔案也是一次按照我們喜好設定偵錯器的機會。例如,對於 GDB,我們以換行設定提示,關閉輸出分頁等等。
在中斷點上停止需要一種相符的模式,這種模式可以相符偵錯器輸出並萃取檔案和列。GDB 有 Emacs 使用的特有模式 (-f),它會以這種格式在每個中斷處列印出完整路徑:(26)(26)<路徑>:<列>
,其中 (26) 是十進制 26 表示的 ASCII 字元。這提供了一個很明顯的目標模式。其他偵錯器並不適合自動驅動,因此我們必須想辦法解決。
一旦進入「中斷」模式,我們當然可以直接設定和取消中斷點。
可以從 Output 視窗輸入任何偵錯器指令。例如,您可以使用「watch var」在 GDB 中設定變數監控。
一個常見的任務是評估表達式。Alt-I(檢查)將報告游標處的表達式的值。scite-debug
夠智能,知道a.b
和p->c->x
是完整的表達式,但如果選取一段表達式,它會採用之。提示工具評估使用相同的启发式和相同的偵錯器指令,但會將輸出重新導向到提示工具而不是 SciTE 輸出視窗。任何指令的輸出都可以重新導向到任意函式,這為更圖形的介面開啟了有趣的機會。(不過,這必須再等一會兒。)
對於 C/C++,scite-debug
會注意到一個評估為指標的表達式,並會嘗試除去那個指標的引用。也就是說,如果 GDB 回報的值為$11 = (A *)0xFFF23EE
,scite-debug
將會嘗試評估*$11
並收集結果。
C++ 偵錯的另一個有用功能是某些標準模式的自動簡化,特別是std::string
會提取 char 指標值。這些規則會遞迴應用,所以包含std::string
值的結構將會以更人性化的形式呈現。請注意,這些簡化極度仰賴使用的精確實現!目前只支援 g++,所以模式相當穩定。這是適合使用者自訂的地方。
scite-debug
通常知道如何詮釋堆疊追蹤;在需要的層級上按兩下,會將你帶到那個框架並將你放到對應的原始碼行。堆疊追蹤可以用Ctrl+Alt+S
明確顯示,如果可能,當錯誤發生時會自動顯示。Alt+U
和Alt+D
對應於向上的和向下的層級。
這項有用(可能也是獨特的)功能是透過在 GDB 內的某個行程中執行 clidebug Lua 偵錯器來實作的。clidebug
現在有一個「GDB 模式」,模仿 GDB 的指令集和輸出。因此不論我們是在 GDB 或clidebug
中中斷,luagdb
都會將我們帶到原始碼中的那個位置。
可能出現兩種情況;第一種情況是主程式本身就是 Lua(或其親戚),並理解 -e 和 -l 命令列選項,這是預設值。第二種情況是主程式是一個已內嵌 Lua 的程式。debug.target 變數必須顯示主程式和 Lua 指令碼名稱。一些範例可以明確說明這一點
# the host is Lua on the path, and has no debugging symbols. debug.target=[n]:gdb;lua;mytest.lua # the host is SciTE - the [h] indicates that it isn't Lua debug.target=[n]:gdb;SciTE[h];/home/steve/{{scite-debug}}/extman.lua # a debug version of Lua. debug.target=:gdb;/home/steve/lua-5.1.3/src/lua;/home/steve/tests/testlfs.lua
如果主程式不是 Lua,你必須在要偵錯的指令碼中自己初始化 clidebug。在這個片段中,將路徑替換為適當的位置。
local path = ';/home/steve/{{scite-debug}}/lua_clidebugger/?.' package.path = package.path .. path .. 'lua' package.cpath = package.cpath .. path .. 'so' WIN=false GDB=true require "debugger" io.stdout:setvbuf("no") pause('debug')
要偵錯 Windows 上的 GUI 程式,例如 SciTE,需要有特別的建置,至少當你想要偵錯 Lua 指令碼時需要。通常 GUI 程式(「子系統:Windows」)沒有標準輸入和輸出,但它們可以重新建置。以 SciTE 為例,我用「-lgdi32 -lcommctl32」取代「-mwindows」;產生的程式會附帶一個難看的黑色主控台,但這個主控台會被偵錯器隱藏。
(另一種只對有 GUI 的 Lua 應用程式除錯的方法為 remDebug
,這也獲得 scite-debug
的支援。)
luagdb
使我們能夠從 Lua 程式單步執行 C 的延伸模組。要執行此動作,必須在「呼叫」除錯事件中找出我們正在進入的特定 C 函式的位址。dbgl
這個小延伸會查詢 Lua 內部資訊並傳回這個位址。它還提供一個方便的函式,叫做 debug_break()
,用來設定中斷點。(這個技巧需要一個對中斷點沒有問題的 GDB 版本。)clidebug
會以特殊的字首印出這個位址,並呼叫 dbgl.debug_break()
,這個函式會讓 GDB 運作。luagdb
會取出這個位址,並對這個位址發出 GDB 『訊息行數』查詢,以查看是否能找到適用於這個位址的任何除錯訊息。如果有,它會在這個函式的開始處設定一個暫時的中斷點。無論是否如此,它都會發出『繼續』命令,讓我們繼續執行。我們會快取這個結果,這樣就不必一直呼叫『訊息行數』,但是當程式從 Lua 單步執行 C/C++ 時,我們仍會設定暫時的中斷點。這個動作可確保我們總是能跨過 C 函式。
一旦進入 C/C++,只要繼續執行(Alt+R),您就能繼續在 Lua 中單步執行。
這些仍是兩個分開的除錯工作階段,所以有些限制。在執行的程式中設定中斷點可能會造成一些問題,至少在 Windows 中是這樣。沒有整合呼叫堆疊。
dbgl.c
必須找出 Lua C 函式的對應位址
static int c_addr (lua_State *L) { char buff[40]; CallInfo *ci; Closure* cl = NULL; for (ci = L->ci - 1; ci > L->base_ci; ci--) { if (! f_isLua(ci)) { // C function! cl = clvalue(ci->func); break; } } if (cl == NULL) { lua_pushnil(L); } else { void *fun = cl->c.f; sprintf(buff,"0x%X",fun); lua_pushstring(L,buff); } return 1; }
這需要對 Lua 內部運作有一些認識,而且需要非公開 API 的一部份 lstate.h
。(所以重新建構 dbgl.c
時,您需要提供包含絕對完整 Lua 標頭集的路徑。)我們會往下檢視閉包的堆疊,找出我們呼叫的 C 函式。