日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

自己动手实现Lua调试器

發(fā)布時(shí)間:2025/6/15 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 自己动手实现Lua调试器 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

這段時(shí)間在qnode項(xiàng)目中新增了一個(gè)叫l(wèi)db的子項(xiàng)目,它的作用是使用C語(yǔ)言實(shí)現(xiàn)了一個(gè)lua調(diào)試器,后面將會(huì)在qnode中嵌入對(duì)調(diào)試lua腳本的支持。

先來(lái)簡(jiǎn)單提一下ldb的用法,在ldb目錄的子目錄test中,有一個(gè)main.c文件,其中使用ldb庫(kù)提供的API實(shí)現(xiàn)對(duì)lua腳本的調(diào)試演示:

#include <stdio h=""> #include "ldb.h"ldb_t *ldb;static int c_break(lua_State *state) {ldb_step_in(state, 1);return 0; }int main() {int i;const char *file = "my.lua";lua_State *L = lua_open();luaL_openlibs(L);lua_register(L, "c_break", c_break);ldb = ldb_new(L);luaL_dofile(L, file);ldb_destroy(ldb);return 0; } </stdio>

在這里,ldb提供了創(chuàng)建和銷(xiāo)毀ldb庫(kù)的API,分別是ldb_new和ldb_destroy。另外提供了一個(gè)API ldb_step_in,main文件中通過(guò)向lua層提供一個(gè)C api c_debug,在該函數(shù)中調(diào)用這個(gè)API,實(shí)現(xiàn)對(duì)lua的調(diào)試。

main函數(shù)將讀取my.lua文件來(lái)執(zhí)行,它的內(nèi)容如下:

function test()local ab = 2034print("test") endlocal t = {out=10} local a = 1014 b = 2024 print("before debug")c_break() test() print("after debug")

來(lái)看看ldb庫(kù)當(dāng)前實(shí)現(xiàn)的調(diào)試功能有哪些:

(ldb) h Lua debugger written by Lichuang(2013) cmd:help(h) : print help infoprint(p) <varname> : print var valuebacktrace(bt) : print backtrace infolist(l) : list file sourcestep(s) : one instruction exactlynext(n) :next linebreak(b) [function|filename:line] : break at function or line in a filedisable(dis) breakpoint : disable a breakpointenable(en) breakpoint : enable a breakpointdelete(del) breakpoint : delete a breakpointinfo(i) :show all break infocontinue(c) : continue execute when hit a break point </varname>

括號(hào)中的都是該命令的縮寫(xiě)。當(dāng)前支持添加/禁止斷點(diǎn),打印變量,查看文件內(nèi)容,查看當(dāng)前調(diào)用棧,step in模式支持逐行執(zhí)行的調(diào)試,以及next模式會(huì)跳過(guò)函數(shù)執(zhí)行等最基本的調(diào)試命令。

介紹完簡(jiǎn)單的使用和功能,下面來(lái)介紹一下lua中為支持調(diào)試功能提供了哪些API,以及通過(guò)這些API如何實(shí)現(xiàn)一個(gè)lua調(diào)試器。

1)lua提供的hook功能
lua為了支持調(diào)試,提供了hook功能,使用者可以根據(jù)需要添加不同的hook處理函數(shù)供條件觸發(fā)時(shí)回調(diào),包括以下幾種:
具體包括以下幾種hook類(lèi)型:

#define LUA_HOOKCALL 0 #define LUA_HOOKRET 1 #define LUA_HOOKLINE 2 #define LUA_HOOKCOUNT 3 #define LUA_HOOKTAILRET 4

這里用的最多的是LUA_HOOKCALL,LUA_HOOKLINE,如果注冊(cè)了這兩個(gè)類(lèi)型的HOOK函數(shù),則會(huì)分別在調(diào)用某函數(shù)和執(zhí)行每一行代碼之后調(diào)用注冊(cè)的HOOK函數(shù)。
使用lua中提供的API,可以如下方式注冊(cè)HOOK函數(shù):

int mask;mask = lua_gethookmask(state);if (enable) {lua_sethook(state, all_hook, mask | LUA_MASKLINE, 0);} else {lua_sethook(state, all_hook, mask & ~LUA_MASKLINE, 0);}

有了HOOK函數(shù),就可以在lua代碼每次執(zhí)行的時(shí)候做一些動(dòng)作了。

除了HOOK函數(shù)之外,lua自身還提供了lua_Debug結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體包括以下成員:

source 函數(shù)的定義位置。如果函數(shù)在字符串內(nèi)被定義(通過(guò)loadstring 函數(shù)),source就是該字符串,如果函數(shù)在文件中被定義,source就是帶“@”前綴的文件名。short_src source的簡(jiǎn)短版本(60個(gè)字符以?xún)?nèi)),對(duì)錯(cuò)誤信息很有用。linedefined source中函數(shù)被定義處的行號(hào)。what 函數(shù)類(lèi)型。如果foo是普通的Lua函數(shù),結(jié)果為“Lua”;如果是C函數(shù),結(jié)果為“C”;如果是Lua的主代碼段,結(jié)果為“main”。name 函數(shù)的名稱(chēng)。namewhat name域的含義。可能的取值為:“global”、“l(fā)ocal”、“method”、“field”,或者空字符串。空字符串意味著Lua無(wú)法找到這個(gè)函數(shù)名。nups 函數(shù)中的Upvalues的個(gè)數(shù)。func 函數(shù)本身。稍后介紹。

可以通過(guò)lua_getinfo得到一些很重要的信息,它的調(diào)用方式是:
lua_getinfo(state, params, ar),第二個(gè)參數(shù)是一個(gè)字符串,支持傳入多個(gè)字母,每個(gè)字母有不同的含義:

'n': name,namewhat 'f': func 'S': source,short_src,what,linedefined 'l': currentline 'u': nup

2) 如何打印變量?
變量分為局部和全局變量,因此搜索某個(gè)變量的時(shí)候是從內(nèi)到外的方式搜索,搜索局部變量時(shí),使用lua提供的API lua_getlocal函數(shù),逐個(gè)搜索,具體可以看ldb.c中的search_local_var函數(shù)。

如果在局部變量中搜索不到,則還得使用lua_getglobal函數(shù)進(jìn)行全局變量的搜索,具體見(jiàn)ldb.c中的search_global_var函數(shù)。

但是以上的過(guò)程僅僅還只能在相應(yīng)的地方查找到同名的變量,在真正需要打印變量值的時(shí)候,還需要根據(jù)變量的類(lèi)型具體來(lái)打印數(shù)據(jù),代碼太多,不在這里列出,見(jiàn)ldb.c中的print_var函數(shù)。

3)如何查看文件的內(nèi)容
查看文件的內(nèi)容相對(duì)簡(jiǎn)單,因?yàn)楫?dāng)lua被HOOK住的時(shí)候,可以通過(guò)lua_getinfo函數(shù)得到當(dāng)前l(fā)ua的一些信息,比如lua文件名,行號(hào),在C實(shí)現(xiàn)的Lua調(diào)試器中,會(huì)維護(hù)一個(gè)已經(jīng)讀取過(guò)的文件列表,如果當(dāng)前所在的文件還沒(méi)有被讀取到內(nèi)存中,那么會(huì)讀取到內(nèi)存中,再根據(jù)所在的行號(hào)就可以得到文件內(nèi)容的信息了。

4)斷點(diǎn)的添加
調(diào)試器的斷點(diǎn)分為兩種,一種是基于文件:行號(hào)形式的,一種則是基于函數(shù)調(diào)用形式的。
C實(shí)現(xiàn)的調(diào)試器中,首先需要定義一個(gè)數(shù)據(jù)結(jié)構(gòu)類(lèi)型,用于表示斷點(diǎn):

typedef struct ldb_breakpoint_t {unsigned int available:1;char *file;char *func;const char *type;int line;unsigned int active:1;int index;int hit; } ldb_breakpoint_t;

這些信息包括斷點(diǎn)所在的文件,行號(hào),函數(shù)名,當(dāng)前是否被激活,被觸發(fā)的計(jì)數(shù),等等。

先來(lái)看第一種形式斷點(diǎn)的實(shí)現(xiàn)。這種形式的斷點(diǎn)相對(duì)簡(jiǎn)單。做法是創(chuàng)建一個(gè)新的斷點(diǎn)數(shù)據(jù)結(jié)構(gòu),保存下文件和行號(hào),在每次HOOK函數(shù)中都去根據(jù)當(dāng)前的文件和行號(hào)信息去查找是否匹配了某個(gè)斷點(diǎn)的信息,當(dāng)然,當(dāng)前的實(shí)現(xiàn)中這個(gè)查找是線(xiàn)性的,可能對(duì)性能有一定影響。

來(lái)看第二種形式斷點(diǎn)的實(shí)現(xiàn)。由于函數(shù)在lua中也是一種類(lèi)型的變量,既然是變量那么就涉及到作用域。比如你在A(yíng)模塊中斷點(diǎn)時(shí),想給B模塊的fun函數(shù)下斷點(diǎn),那么就不能簡(jiǎn)單的寫(xiě)”b func”,而應(yīng)該是”b B.func”。所以在實(shí)現(xiàn)對(duì)函數(shù)進(jìn)行斷點(diǎn)的時(shí)候要注意這一點(diǎn)。另外,當(dāng)給某一個(gè)函數(shù)下斷點(diǎn)時(shí),還需要添加LUA_HOOKCALL類(lèi)型的HOOK,也就是在函數(shù)調(diào)用時(shí)被觸發(fā)。之所以這么做,是因?yàn)樵诓檎覕帱c(diǎn)時(shí),當(dāng)首先使用文件名和行號(hào)都查找不到時(shí),會(huì)判斷一下當(dāng)前這次的HOOK調(diào)用,是不是一個(gè)函數(shù)調(diào)用觸發(fā)的HOOK,如果是的話(huà)再繼續(xù)根據(jù)斷點(diǎn)的函數(shù)名進(jìn)行查找匹配。

5)如何查看當(dāng)前堆棧信息
也就是模擬gdb中的bt指令的功能。lua對(duì)這個(gè)已經(jīng)有支持了,可以使用lua_getstack函數(shù),具體見(jiàn)ldb.c中的dump_stack函數(shù)。

6)step和next指令的實(shí)現(xiàn)
首先來(lái)看一個(gè)子問(wèn)題,如何得到當(dāng)前的函數(shù)堆棧數(shù)量,或者說(shuō)當(dāng)前的調(diào)用層次,通過(guò)反復(fù)調(diào)用前面提到的lua_getstack函數(shù)可以獲取到:

static int get_calldepth(lua_State *state) {int i;lua_Debug ar;for (i = 0; lua_getstack(state, i + 1, &ar ) != 0; i++);return i; }

這兩個(gè)指令的實(shí)現(xiàn)稍微有點(diǎn)難度,所以放在最后一個(gè)講解。step就是逐行執(zhí)行代碼,即使調(diào)用函數(shù)的時(shí)候也會(huì)跟進(jìn)該函數(shù)中,而next指令會(huì)在調(diào)用函數(shù)的時(shí)候不跟進(jìn)函數(shù)的調(diào)用。所以這兩者的區(qū)別在于調(diào)用時(shí)的函數(shù)堆棧,因此這兩個(gè)指令的區(qū)別僅在于遇到函數(shù)的時(shí)候是不是繼續(xù)跟進(jìn)去。所以我的做法是新增一個(gè)變量用于保存當(dāng)前的函數(shù)棧索引,當(dāng)step模式時(shí)將這個(gè)值置為-1,next模式時(shí)只會(huì)保存為當(dāng)前的函數(shù)棧索引,如果某個(gè)指令是調(diào)用一個(gè)函數(shù)時(shí),這時(shí)通過(guò)get_calldepth函數(shù)獲得的函數(shù)棧就會(huì)比之前的大,這樣就可以知道當(dāng)前的這個(gè)指令是不是調(diào)用一個(gè)函數(shù)了,next指令可以在這個(gè)時(shí)候返回不做任何處理,而step指令可以繼續(xù)執(zhí)行下去。

以上就是簡(jiǎn)單的原理性介紹,如果想真正了解lua調(diào)試器的實(shí)現(xiàn),還是具體看看代碼,自己跑一下測(cè)試demo吧。

總結(jié)

以上是生活随笔為你收集整理的自己动手实现Lua调试器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。