0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深入LUA腳本語言

汽車電子技術(shù) ? 來源:IOT物聯(lián)網(wǎng)小鎮(zhèn) ? 作者: 道哥 ? 2023-02-14 13:55 ? 次閱讀

一、前言上篇文章我們聊了gdb的底層調(diào)試機(jī)制,明白了gdb是利用操作系統(tǒng)提供的系統(tǒng)信號(hào)來調(diào)試目標(biāo)程序的。很多朋友私下留言了,看到能幫助到大家,我心里還是很開心的,其實(shí)這也是我繼續(xù)輸出文章的最大動(dòng)力!后面我會(huì)繼續(xù)把自己在項(xiàng)目開發(fā)中的實(shí)戰(zhàn)經(jīng)驗(yàn)進(jìn)行總結(jié)。

圖片

由于gdb的代碼相對(duì)復(fù)雜,沒有辦法從代碼層面仔細(xì)的分析調(diào)試細(xì)節(jié),所以這次我們選擇一個(gè)小巧、開源的Lua腳本語言,深入到最底層的代碼中去探究一下代碼調(diào)試真正是怎么一回事。

不過請放心,雖然深入到代碼最底層,但是理解難度并不大,只要C語言掌握的沒問題,其他就都不是問題。另外,這篇文章重點(diǎn)不是介紹代碼,而是介紹實(shí)現(xiàn)一個(gè)調(diào)試器應(yīng)該如何思考,解決問題的思路是什么。

通過閱讀這篇文章,能有什么收獲?

  1. 如果你使用過Lua語言,那么你能夠從源代碼級(jí)別了解到調(diào)試庫的代碼邏輯。
  2. 如果你對(duì)Lua不了解,可以從設(shè)計(jì)思想、實(shí)現(xiàn)架構(gòu)上學(xué)習(xí)到一門編程語言是如何進(jìn)行調(diào)試程序的。

二、Lua 語言簡介

1. Lua是什么鬼?

喜歡玩游戲的小伙伴可能會(huì)知道,Lua語言在游戲開發(fā)中使用的比較多。它是一個(gè)輕量、小巧的腳本語言,用標(biāo)準(zhǔn)C語言編寫,源碼開放。正因?yàn)檫@幾個(gè)原因,所以我才選擇它作為剖析對(duì)象。

如果對(duì)于Lua語言還是沒有感覺,Python語言總應(yīng)該知道吧?廣告滿天飛,你就把Lua想象為類似Python一樣的腳本語言,只不過體積比Python要輕量的得多。

這里有1張圖可以了解下,2020年12月份的編程語言市場占有率。

圖片

在上圖中看不到Lua的身影,因?yàn)槭袌稣加新侍土?,大概是位?0幾名。但是再看看下面這張圖,從工資的角度再體會(huì)一下Lua的高貴:

圖片

遠(yuǎn)遠(yuǎn)的把C/C++JAVA甩在了身后,是不是有點(diǎn)沖動(dòng)想學(xué)一下Lua語言了?先別激動(dòng),學(xué)習(xí)任何東西,先要想明白可以用在什么地方。如果僅僅是從找工作的角度來,Lua可以不用考慮了,畢竟市場需求量比較小。

2. 為什么選擇Lua語言作為研究對(duì)象?

雖然Lua語言在招聘網(wǎng)站中處于小眾需求,但是這并不妨礙我們利用Lua來深入的學(xué)習(xí)、研究一門編程語言,Lua語言雖小,但是五臟俱全。就像我們?nèi)绻雽W(xué)習(xí)Linux內(nèi)核的設(shè)計(jì)思想,你是愿意從最開始的版本(幾千行代碼)開始呢?還是愿意從當(dāng)前最新的內(nèi)核代碼(2780萬行代碼,66492個(gè)文件)開始呢?

看一下當(dāng)前最新版的Lua代碼體積:

圖片

同樣的思路,如果我們想深入研究一門編程語言,選擇哪一種語言,對(duì)于我們的積極性和學(xué)習(xí)效率是非常重要的。每個(gè)人的職業(yè)生涯都很長,花一些時(shí)間沉下心來研究透一門語言,對(duì)于一個(gè)開發(fā)者來說,還是蠻有成就的,對(duì)于職業(yè)的發(fā)展是非常有好處的,你會(huì)有一覽眾山小的感覺!

再看一下Lua代碼量與Python代碼量的對(duì)比:

圖片

從功能上來說,Lua與Python之間是沒有可比性的,但是我們的目的不是學(xué)習(xí)一個(gè)編程工具,而是研究一門編程語言本身,因此選擇Lua腳本語言進(jìn)行學(xué)習(xí)、研究,沒有錯(cuò)!

言歸正傳。

三、Lua源代碼5.3.5

1. Lua程序是如何執(zhí)行的?

Lua 是一門擴(kuò)展式程序設(shè)計(jì)語言,被設(shè)計(jì)成支持通用過程式編程,并有相關(guān)數(shù)據(jù)描述設(shè)施。同時(shí)對(duì)面向?qū)ο缶幊?、函?shù)式編程和數(shù)據(jù)驅(qū)動(dòng)式編程也提供了良好的支持。它作為一個(gè)強(qiáng)大、輕量的嵌入式腳本語言,可供任何需要的程序使用。

作為一門擴(kuò)展式語言,Lua沒有"main"程序的概念:它只能嵌入一個(gè)宿主程序中工作,該宿主程序被稱為被嵌入程序或者簡稱宿主。宿主程序可以調(diào)用函數(shù)執(zhí)行一小段Lua代碼,可以讀寫Lua變量,可以注冊C函數(shù)讓Lua代碼調(diào)用。依靠C函數(shù),Lua可以共享相同的語法框架來定制編程語言,從而適用不同的領(lǐng)域。

也就是說,我們寫了一個(gè)test.lua程序,是沒有辦法直接運(yùn)行它的。而實(shí)需要一個(gè)“宿主”程序,來加載test.lua文件。

圖片

宿主程序可以是一個(gè)最簡單的C程序,Lua官方提供了一個(gè)宿主程序。

我們也可以自己寫一個(gè),如下:

// 引入Lua頭文件#include#include#include
int main(int argc, char *argv[]){ // 創(chuàng)建一個(gè)Lua虛擬機(jī) lua_State *L = luaL_newstate(); // 打開LUA中的標(biāo)準(zhǔn)庫 luaL_openlibs(L); // 加載 test.lua 程序 if (luaL_loadfile(L, "test.lua") || lua_pcall(L, 0, 0, 0)) { printf("Error: %s \\n", lua_tostring(g_lua_handle.L, -1)); lua_close(g_lua_handle.L); } // 其他代碼}

2. Lua語法

在語法層面,Lua涵蓋的內(nèi)容還是比較全面的,它是一門動(dòng)態(tài)類型語言,基本概念包括:八種基本數(shù)據(jù)類型,表是唯一的數(shù)據(jù)結(jié)構(gòu),環(huán)境與全局變量,元表及元方法,協(xié)程,閉包,錯(cuò)誤處理,垃圾收集。具體的信息可以看一下Lua5.3參考手冊。

這篇文章主要從調(diào)試器這個(gè)角度進(jìn)行分析,因此我不會(huì)在這里詳細(xì)的貼出很多代碼細(xì)節(jié),而只是把與調(diào)試有關(guān)的代碼貼出來進(jìn)行解釋。

我之前在學(xué)習(xí)Lua源碼時(shí)(5.3.5版本),在代碼文件中記錄了很多注釋,可以很好的幫助理解,主要是因?yàn)槲业耐员容^好。

其實(shí)我更建議大家自己去下載源碼學(xué)習(xí),經(jīng)過自己的理解、加工,印象會(huì)更深刻。在之前的工作中,由于項(xiàng)目需要,我對(duì)源碼進(jìn)行了一些優(yōu)化,這部分代碼就不放出來了,添加注釋的源碼是完完全全的Lua5.3.5版本,大概是這個(gè)樣子:

圖片

圖片

如果有小伙伴需要加了注釋的源碼,請?jiān)诠娞?hào)(IOT物聯(lián)網(wǎng)小鎮(zhèn))里留言給我。

四、Lua調(diào)試庫相關(guān)

我們可以停下來稍微想一下,對(duì)一個(gè)程序進(jìn)行調(diào)試,需要考慮的問題有3點(diǎn):

  1. 如何讓程序暫停執(zhí)行?
  2. 如何獲取程序的內(nèi)部信息?
  3. 如果修改程序的內(nèi)部信息?

帶著這些問題,我們來逐個(gè)擊破。

1. 鉤子函數(shù)(Hook):讓程序暫停執(zhí)行

Lua虛擬機(jī)(也可稱之為解釋器)內(nèi)部提供了一個(gè)接口:用戶可以在應(yīng)用程序中設(shè)置一個(gè)鉤子函數(shù)(Hook),虛擬機(jī)在執(zhí)行指令碼的時(shí)候會(huì)檢查用戶是否設(shè)置了鉤子函數(shù),如果設(shè)置了,就調(diào)用這個(gè)鉤子函數(shù)。本質(zhì)上就是設(shè)置一個(gè)回調(diào)函數(shù),因?yàn)槎际怯肅語言來實(shí)現(xiàn)的,虛擬機(jī)中只要把這個(gè)鉤子函數(shù)的地址記住,然后在某些場合回調(diào)這個(gè)函數(shù)就可以了。

圖片

那么,虛擬機(jī)在哪些場合回調(diào)用戶設(shè)置的鉤子函數(shù)呢?

我們在設(shè)置Hook函數(shù)的時(shí)候,可以通過mask參數(shù)來設(shè)置回調(diào)策略,也就是告訴虛擬機(jī):在什么時(shí)候來回調(diào)鉤子函數(shù)。mask參數(shù)可以是下列選項(xiàng)的組合操作:

  1. LUA_MASKCALL:調(diào)用一個(gè)函數(shù)時(shí),就調(diào)用一次鉤子函數(shù)。
  2. LUA_MASKRET:從一個(gè)函數(shù)中返回時(shí),就調(diào)用一次鉤子函數(shù)。
  3. LUA_MASKLINE:執(zhí)行一行指令時(shí),就回調(diào)一次鉤子函數(shù)。
  4. LUA_MASKCOUNT:執(zhí)行指定數(shù)量的指令時(shí),就回調(diào)一次鉤子函數(shù)。

設(shè)置鉤子函數(shù)的基礎(chǔ)API原型如下:

void lua_sethook (lua_State *L, lua_Hook f, int mask, int count);

第二個(gè)參數(shù)f需要指向我們自己定義的鉤子函數(shù),這個(gè)鉤子函數(shù)原型為:

typedef void (*lua_Hook) (lua_State *L, lua_Debug *ar);

我們也可以通過下面即將介紹的調(diào)試庫中的函數(shù)來設(shè)置鉤子函數(shù),效果是一樣的,因?yàn)檎{(diào)試庫函數(shù)的內(nèi)部也是調(diào)用基礎(chǔ)函數(shù)。

debug.sethook ([thread,] hook, mask [, count])

再來看一下虛擬機(jī)中的相關(guān)代碼。當(dāng)執(zhí)行完上一條指令,獲取下一條指令之后,調(diào)用函數(shù) *luaG_traceexec(lua_State L)

void luaG_traceexec (lua_State *L) {  // 獲取mask掩碼  lu_byte mask = L->hookmask;   int counthook = (--L->hookcount == 0 && (mask & LUA_MASKCOUNT));  if (counthook)    resethookcount(L);  else if (!(mask & LUA_MASKLINE))    return; 
if (counthook) luaD_hook(L, LUA_HOOKCOUNT, -1); // 按指令次數(shù)調(diào)用鉤子函數(shù) if (mask & LUA_MASKLINE) { Proto *p = ci_func(ci)->p; int npc = pcRel(ci->u.l.savedpc, p); int newline = getfuncline(p, npc); if (npc == 0 || ci->u.l.savedpc <= L->oldpc || newline != getfuncline(p, pcRel(L->oldpc, p))) luaD_hook(L, LUA_HOOKLINE, newline); // 按行調(diào)用鉤子函數(shù) }}

可以看到,當(dāng)mask掩碼中包含了LUA_MASKLINE時(shí),就調(diào)用函數(shù)luaD_hook(),如下代碼:

voidluaD_hook (lua_State *L, intevent, int line) {  lua_Hook hook = L->hook;  if (hook && L->allowhook) {     // 為鉤子函數(shù)準(zhǔn)備參數(shù),其中包括了各種調(diào)試信息    lua_Debug ar;    ar.event = event;    ar.currentline = line;    ar.i_ci = ci;    // 調(diào)用鉤子函數(shù)    (*hook)(L, &ar);  }}

只要進(jìn)入了用戶設(shè)置的鉤子函數(shù),那么我們就可以在這個(gè)函數(shù)中為所欲為了。

比如:獲取程序內(nèi)部信息,讀取、修改變量的值,查看函數(shù)調(diào)用棧信息等等,這就是下面要講解的內(nèi)容。

2. Lua調(diào)試庫是什么?

首先說一下Lua中的標(biāo)準(zhǔn)庫。所謂的標(biāo)準(zhǔn)庫就是Lua為開發(fā)者提供的一些有用的函數(shù),可以提高開發(fā)效率,當(dāng)然我們可以選擇不使用標(biāo)準(zhǔn)庫,或者只使用部分標(biāo)準(zhǔn)庫,這是可以裁剪的。

圖片

這里我們只介紹一下基礎(chǔ)庫、操作系統(tǒng)庫和調(diào)試庫這3個(gè)家伙。

基礎(chǔ)庫

基礎(chǔ)庫提供了Lua核心函數(shù),如果你不將這個(gè)庫包含在你的程序中,就需要小心檢查程序是否需要自己提供其中一些特性的實(shí)現(xiàn),這個(gè)庫一般都是需要使用的。

操作系統(tǒng)庫

這個(gè)庫提供與操作系統(tǒng)進(jìn)行交互的功能,例如提供了函數(shù):

os.date

os.time

os.execute

os.exit

os.getenv

調(diào)試庫

先看一下庫中提供的幾個(gè)重要的函數(shù):

debug.gethook

debug.sethook

debug.getinfo

debug.getlocal

debug.setlocal

debug.setupvalue

debug.traceback

debug.getregistry

上面已經(jīng)說到,Lua給用戶提供了設(shè)置鉤子的API函數(shù)lua_sethook,用戶可以直接調(diào)用這個(gè)函數(shù),此時(shí)傳入的鉤子函數(shù)的定義格式需要滿足要求。

為了簡化用戶編程,Lua還提供了調(diào)試庫來幫助用戶降低編程難度。調(diào)試庫其實(shí)也就是把基礎(chǔ)API函數(shù)進(jìn)行封裝了一下,我們以設(shè)置鉤子函數(shù)debug.sethook為例:文件ldblib.c中,定義了調(diào)試庫支持的所有函數(shù):

staticintdb_sethook(lua_State *L) {  lua_sethook(L1, func, mask, count);}
static const luaL_Reg dblib[] = { // 其他接口函數(shù)都刪掉了,只保留這一個(gè)來講解 {"sethook", db_sethook}, {NULL, NULL}};
// 這個(gè)函數(shù)用來把調(diào)試庫中的函數(shù)注冊到全局變量表中LUAMOD_API intluaopen_debug(lua_State *L) { luaL_newlib(L, dblib); return 1;}

可以看到,調(diào)試庫的debgu.sethook()函數(shù)最終也是調(diào)用基礎(chǔ)API函數(shù):lua_sethook()。

在后面的調(diào)試器開發(fā)講解中,我就是用debug庫來實(shí)現(xiàn)一個(gè)遠(yuǎn)程調(diào)試器。

3. 獲取程序內(nèi)部信息

在鉤子函數(shù)中,可以通過如下API函數(shù)還獲取程序內(nèi)部的信息了:

int lua_getinfo (lua_State *L, const char *what, lua_Debug *ar);

在這個(gè)API函數(shù)中:

第二個(gè)參數(shù)用來告訴虛擬機(jī)我們想獲取程序的哪些信息

第三個(gè)參數(shù)用來存儲(chǔ)獲取到的信息

結(jié)構(gòu)體lua_Debug比較重要,成員變量如下:

typedef structlua_Debug {  int event;  const char *name;           /* (n) */  const char *namewhat;       /* (n) */  const char *what;           /* (S) */  const char *source;         /* (S) */  int currentline;            /* (l) */  int linedefined;            /* (S) */  int lastlinedefined;        /* (S) */  unsigned char nups;         /* (u) 上值的數(shù)量 */  unsigned char nparams;      /* (u) 參數(shù)的數(shù)量 */  char isvararg;              /* (u) */  char istailcall;            /* (t) */  char short_src[LUA_IDSIZE]; /* (S) */  /* 私有部分 */  其它域} lua_Debug;

  1. source:創(chuàng)建這個(gè)函數(shù)的代碼塊的名字。如果 source 以 '@' 打頭, 指這個(gè)函數(shù)定義在一個(gè)文件中,而 '@' 之后的部分就是文件名。
  2. linedefined: 函數(shù)定義開始處的行號(hào)。
  3. lastlinedefined: 函數(shù)定義結(jié)束處的行號(hào)。
  4. currentline: 給定函數(shù)正在執(zhí)行的那一行。

其他字段可以在參考手冊中查詢。例如:如果想知道函數(shù) f 是在哪一行定義的, 你可以使用下列代碼:

lua_Debug ar;lua_getglobal(L, "f");  /* 取得全局變量 'f' */lua_getinfo(L, ">S", &ar);printf("%d\\n", ar.linedefined);

同樣的,也可以調(diào)用調(diào)試庫debug.getinfo()來達(dá)到同樣的目的。

4. 修改程序內(nèi)部信息

經(jīng)過上面的講解,已經(jīng)看到我們獲取程序信息都是通過Lua提供的API函數(shù),或者是利用調(diào)試庫提供的接口函數(shù)來完成的。那么修改程序內(nèi)部信息也同樣如此。Lua提供了下面這2個(gè)API函數(shù)來修改函數(shù)中的變量:

  1. 修改當(dāng)前活動(dòng)記錄總的局部變量的值:

const char *lua_setlocal (lua_State *L, const lua_Debug *ar, int n);

  1. 設(shè)置閉包上值的值(上值upvalue就是閉包使用了外層的那些變量)

const char *lua_setupvalue (lua_State *L, int funcindex, int n);

同樣的,也可以利用調(diào)試庫中的debug.setlocal和debug.setupvalue來完成同樣的功能。

5. 小結(jié)

到這里,我們就把Lua語言中與調(diào)試有關(guān)的機(jī)制和代碼都理解清楚了,剩下的問題就是如何利用它提供的這些接口,來編寫一個(gè)類似gdb一樣的調(diào)試器。

就好比:Lua已經(jīng)把材料(米、面、菜、肉、佐料)擺在我們的面前了,剩下的就需要我們把這些材料做成一桌美味佳肴。## 五、Lua調(diào)試器開發(fā)

1. 與gdb調(diào)試模型做類比

上一篇文章說過,gdb調(diào)試模型有兩種:本地調(diào)試和遠(yuǎn)程調(diào)試。

本地調(diào)試

圖片

遠(yuǎn)程調(diào)試

圖片

那么,我們也可以按照這個(gè)思路來實(shí)現(xiàn)兩種調(diào)試模型,只要把其中的gdb替換成ldb,gdbserver替換成ldbserver即可。

本地調(diào)試

圖片

遠(yuǎn)程調(diào)試

圖片

這兩種調(diào)試模型本質(zhì)是一樣的,只是調(diào)試程序和被調(diào)試程序是否運(yùn)行在同一臺(tái)電腦上而已。

如果是遠(yuǎn)程調(diào)試,ldbserver調(diào)用接口函數(shù)對(duì)被調(diào)試程序進(jìn)行控制,然后把結(jié)果通過TCP網(wǎng)絡(luò)傳遞給ldb,ldbserver就相當(dāng)于一個(gè)傳話筒。

至于選擇實(shí)現(xiàn)哪一種調(diào)試模型?這個(gè)要根據(jù)實(shí)際場景的需求來決定。我在這里實(shí)現(xiàn)的是遠(yuǎn)程調(diào)試,因?yàn)楸徽{(diào)試程序是需要運(yùn)行在ARM板子(下位機(jī))中的,但是調(diào)試器是需要運(yùn)行在PC電腦上(上位機(jī))的,通過遠(yuǎn)程調(diào)試,只需要把ldbserver和被調(diào)試程序放到下位機(jī)中運(yùn)行,ldb嵌入到上位機(jī)的集成開發(fā)環(huán)境(IDE)中運(yùn)行就可以了。

另外,遠(yuǎn)程調(diào)試模型同樣也可以全部運(yùn)行在同一臺(tái)PC電腦中,這個(gè)時(shí)候ldb與ldbserver之間就是在本機(jī)中進(jìn)行TCP網(wǎng)絡(luò)連接。

這里有2個(gè)內(nèi)容需要補(bǔ)充一下:

  1. TCP鏈接可以直接利用第三方庫luasocket。
  2. ldb與ldbserver之間的通訊協(xié)議可以參照gdb與gdbserver之間的協(xié)議,也可以自定義。我借鑒了HTTP協(xié)議,簡化了很多。

2. ldbserver如何實(shí)現(xiàn)

思考一個(gè)問題:被調(diào)試程序在執(zhí)行時(shí)調(diào)用鉤子函數(shù),在鉤子函數(shù)中我們可以做各種調(diào)試操作,但是在執(zhí)行到鉤子函數(shù)的最后,是需要返回到被調(diào)試程序中的下一行指令碼繼續(xù)執(zhí)行的,我們不能打斷被調(diào)試程序的執(zhí)行序列。

但是,調(diào)試操作又需要通過TCP連接與上位機(jī)進(jìn)行通信協(xié)議的交互,比如:設(shè)置斷點(diǎn)、查看變量的值、查看函數(shù)信息等等。所以,被調(diào)試程序的執(zhí)行與調(diào)試器ldbserver的執(zhí)行是2個(gè)并發(fā)的執(zhí)行序列,可以理解為2個(gè)線程在并發(fā)執(zhí)行。我們需要在這2個(gè)執(zhí)行序列之間進(jìn)行協(xié)調(diào),比如:

  1. ldbserver在等待用戶輸入指令時(shí)(running),被調(diào)試程序應(yīng)該處于暫停狀態(tài)(pending)。
  2. ldbserver接收到用戶指令后(eg: run),自己應(yīng)該暫停執(zhí)行(pending),讓被調(diào)試程序繼續(xù)執(zhí)行(running)。

圖片

上圖中,兩條紅色箭頭表示兩個(gè)執(zhí)行序列。這兩個(gè)執(zhí)行序列并不是同時(shí)在執(zhí)行的,而是交替執(zhí)行,如下圖所示:

圖片

那么怎么樣才能讓這2個(gè)執(zhí)行序列交替執(zhí)行呢?

如果是在C語言中,我們可以通過信號(hào)量、互斥鎖等各種方法實(shí)現(xiàn),但這是在Lua語言中,應(yīng)該利用什么機(jī)制來實(shí)現(xiàn)這個(gè)功能?

柳暗花明又一村!

Lua中提供了協(xié)程機(jī)制!下面這段話是從參考手冊中摘抄過來:

  1. Lua 支持協(xié)程,也叫協(xié)同式多線程。一個(gè)協(xié)程在 Lua 中代表了一段獨(dú)立的執(zhí)行線程。然而,與多線程系統(tǒng)中的線程的區(qū)別在于, 協(xié)程僅在顯式調(diào)用一個(gè)讓出(yield)函數(shù)時(shí)才掛起當(dāng)前的執(zhí)行。
  2. 調(diào)用函數(shù)coroutine.create可創(chuàng)建一個(gè)協(xié)程。
  3. 調(diào)用coroutine.resume函數(shù)執(zhí)行一個(gè)協(xié)程。
  4. 通過調(diào)用coroutine.yield使協(xié)程暫停執(zhí)行,讓出執(zhí)行權(quán)。

我們可以讓ldbserver運(yùn)行在一個(gè)協(xié)程中,被調(diào)試程序運(yùn)行在主程序中。當(dāng)虛擬機(jī)執(zhí)行一條被調(diào)試程序的指令碼之后,調(diào)用鉤子函數(shù),在鉤子函數(shù)中通過coroutine.resume讓協(xié)程運(yùn)行,主程序停止。前面說到,ldbserver運(yùn)行在運(yùn)行在一個(gè)協(xié)程中,此時(shí)就可以在ldbserver中利用阻塞函數(shù)(例如:TCP 中的receive),接收用戶的調(diào)試指令。

假設(shè)用戶發(fā)送來全速執(zhí)行指令(run),ldbserver就調(diào)用coroutine.yield讓自己掛起,此時(shí)被調(diào)試程序所在的主程序就可以繼續(xù)執(zhí)行了。

進(jìn)行到這里,基本上大功告成!剩下的就是一些代碼細(xì)節(jié)問題了。

3. ldb如何實(shí)現(xiàn)

這部分就比較簡單了,從功能上來說包括3部分內(nèi)容:

  1. 與ldbserver之間建立TCP連接。
  2. 讀取調(diào)試人員輸入的指令,發(fā)送給ldbserver。
  3. 接收ldbserver發(fā)來的信息,顯示給調(diào)試人員。

可以在調(diào)試終端中手動(dòng)輸入、顯示調(diào)試信息,也可以把ldb嵌入到一個(gè)可視化的編輯工具中,例如:

local functionprint_commands()    print("setb      -- sets a breakpoin")    print("step                  -- run one line, stepping into function")    print("next                  -- run one line, stepping over function")    print("goto            -- goto line in a function")    // 其他指令end

六、調(diào)試指令舉例

1. break指令的實(shí)現(xiàn)

(1)設(shè)置鉤子函數(shù)

ldbserver通過調(diào)試庫的debug.sethook函數(shù),設(shè)置了一個(gè)鉤子函數(shù),調(diào)用參數(shù)是:

debug.sethook(my_hook, "lcr")

第二個(gè)參數(shù)"lcr"的含義是:

'c': 每當(dāng) Lua 調(diào)用一個(gè)函數(shù)時(shí),調(diào)用鉤子。

'r': 每當(dāng) Lua 從一個(gè)函數(shù)內(nèi)返回時(shí),調(diào)用鉤子。

'l': 每當(dāng) Lua 進(jìn)入新的一行時(shí),調(diào)用鉤子。

也即是說:虛擬機(jī)進(jìn)入一個(gè)函數(shù)、從一個(gè)函數(shù)返回、每執(zhí)行一行代碼,都調(diào)用一次鉤子函數(shù)。注意:這里的一行指定是被調(diào)試程序中的一行Lua代碼,而不是二進(jìn)制文件中的一行指令碼,一行Lua代碼可能被會(huì)編譯生成多行指令碼。

這里還有一點(diǎn)需要注意:鉤子函數(shù)雖然是定義在用戶代碼中,但是它是被虛擬機(jī)調(diào)用的,也就是說鉤子函數(shù)是處于主程序的執(zhí)行序列中。

(2)設(shè)置斷點(diǎn)

ldb向ldbserver發(fā)送設(shè)置斷點(diǎn)的指令:setb test.lua 10,即:在test.lua文件的第10行設(shè)置一個(gè)斷點(diǎn),ldbserver接收到指令后,在內(nèi)存中記錄這個(gè)信息(文件名-行號(hào))。

(3)捕獲斷點(diǎn)

虛擬機(jī)在調(diào)用鉤子函數(shù)時(shí),傳入兩個(gè)參數(shù)(注意:鉤子函數(shù)是被虛擬機(jī)調(diào)用的,所以它是處于主程序的執(zhí)行序列中),

local function my_hook(event, line)

在鉤子函數(shù)中,查找這個(gè)line是否被用戶設(shè)置為斷點(diǎn),如果是那么就通過coroutine.resume讓主程序暫停,讓協(xié)程中的ldbserver執(zhí)行。此時(shí),ldbserver就可以在TCP網(wǎng)絡(luò)上繼續(xù)等待ldb發(fā)來的下一個(gè)調(diào)試指令。

圖片

2. next指令的實(shí)現(xiàn)

next指令與step指令類似,區(qū)別在于當(dāng)下一條指令是一個(gè)函數(shù)調(diào)用時(shí):

step指令: 進(jìn)入到函數(shù)內(nèi)部。

next指令: 不進(jìn)入函數(shù)內(nèi)部,而是直接把這個(gè)函數(shù)執(zhí)行完。

next指令的實(shí)現(xiàn)主要依賴于鉤子函數(shù)的第一個(gè)參數(shù)event,上面在設(shè)置鉤子函數(shù)的時(shí)候,告訴虛擬機(jī)在3種條件下調(diào)用鉤子函數(shù),重新貼一下:

'c': 每當(dāng) Lua 調(diào)用一個(gè)函數(shù)時(shí),調(diào)用鉤子

'r': 每當(dāng) Lua 從一個(gè)函數(shù)內(nèi)返回時(shí),調(diào)用鉤子

'l': 每當(dāng) Lua 進(jìn)入新的一行時(shí),調(diào)用鉤子

在進(jìn)入鉤子函數(shù)之后,event參數(shù)會(huì)告訴我們:為什么會(huì)調(diào)用鉤子函數(shù)。代碼如下:

function my_hook(event, line)    if event == "call" then        // 進(jìn)入了一個(gè)函數(shù)        func_level = func_level + 1    elseif event == "return" then        // 從一個(gè)函數(shù)返回        func_level = func_level - 1    else        // 執(zhí)行完一行代碼    end

所以就可以利用event參數(shù)來記錄進(jìn)入、退出函數(shù)層數(shù),然后在鉤子函數(shù)中判斷:是否需要暫停主程序,把執(zhí)行的機(jī)會(huì)讓給協(xié)程。

3. goto指令的實(shí)現(xiàn)

在調(diào)試過程中,如果我們想跳過當(dāng)前執(zhí)行函數(shù)中的某幾行,可以發(fā)送goto指令,被調(diào)試程序就從當(dāng)前停止的位置直接跳轉(zhuǎn)到goto指令中設(shè)置的那行代碼。

目前goto指令有一個(gè)限制:

因?yàn)長ua虛擬機(jī)中的所有代碼都是以函數(shù)為單位的,通過函數(shù)調(diào)用棧把所有的代碼串接在一起,因此只能goto到當(dāng)前函數(shù)內(nèi)的指定行。

這部分功能Lua源碼中并沒有提供,需要擴(kuò)展調(diào)試庫的功能。核心步驟就是:強(qiáng)制把虛擬機(jī)中的PC指針設(shè)置為指定的那行Lua代碼所對(duì)應(yīng)的第一個(gè)指令碼。

ar->i_ci->u.l.savedpc = cl->p->code + 需要跨過的指令碼

ar變量就是調(diào)試庫為我們準(zhǔn)備的:

const lua_Debug *ar

(如果你能跟著思路看到這里,我心里時(shí)非常非常的感激,能容忍我這么嘮叨這么久。到這里我想表達(dá)的內(nèi)容也差不多結(jié)束了,后面兩個(gè)模塊如果有興趣的話可以稍微了解一下,不是重點(diǎn)。)

七、其他重要的模塊

這部分先空著,如果有小伙伴想要詳細(xì)了解的話,請?jiān)诠娞?hào)(IOT物聯(lián)網(wǎng)小鎮(zhèn))中留言給我,單獨(dú)整理成文檔。比較重要的內(nèi)容包括:

  1. 標(biāo)準(zhǔn)庫的加載過程
  2. 函數(shù)調(diào)用棧
  3. 同時(shí)調(diào)試多個(gè)程序
  4. 如何處理中斷信號(hào)
  5. 如何處理中斷信號(hào)嵌套問題
  6. 如何添加自己的庫
  7. 如何同時(shí)調(diào)試多個(gè)程序
  8. 其他指令的實(shí)現(xiàn)機(jī)制:查看、修改變量,查看函數(shù)調(diào)用棧,多個(gè)被調(diào)試程序的切換等等。

八、調(diào)試操作步驟

關(guān)于實(shí)際操作步驟,用文檔表達(dá)起來比較費(fèi)勁,全部是黑乎乎的終端窗口。計(jì)劃錄一個(gè)60分鐘左右的視頻,把上面提到的內(nèi)容都操作演示一遍,這樣效果會(huì)更好一下。有興趣的話可以在B站搜一下我的ID(道哥分享)。內(nèi)容主要包括:

  1. 在Linux平臺(tái)下:編譯和調(diào)試步驟。
  2. Windows平臺(tái)下:編譯和調(diào)試步驟。
  3. 簡單的圖形調(diào)試界面,就是把ldb嵌入到IDE中。
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4788

    瀏覽量

    68616
  • gdb
    gdb
    +關(guān)注

    關(guān)注

    0

    文章

    60

    瀏覽量

    13303
  • lua腳本
    +關(guān)注

    關(guān)注

    0

    文章

    21

    瀏覽量

    7590
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    腳本語言動(dòng)態(tài)語言是什么

    腳本語言腳本語言又被稱為擴(kuò)建的語言,或者動(dòng)態(tài)語言,是一種編程語言,用來控制軟件應(yīng)用程序,腳本通常
    發(fā)表于 07-02 08:01

    Lua腳本簡單介紹

    Lua簡單介紹Lua[1]是一個(gè)小巧的腳本語言。作者是巴西人。該語言的設(shè)計(jì)目的是為了嵌入應(yīng)用程序中,從而為應(yīng)用程序提供靈活的擴(kuò)展和定制功能。Lua
    發(fā)表于 08-20 06:37

    嘗試設(shè)計(jì)自己的腳本語言

    : Lua for microcontrollers5. 嘗試設(shè)計(jì)自己的腳本語言6. 結(jié)束語 1. 前言 一直以來,因?yàn)閱纹瑱C(jī)的性能和成本的原因,大多數(shù)單片機(jī)開發(fā)者所用的主要編程語言要么是匯編,要么就是C,連C++都很少有人用。
    發(fā)表于 08-20 06:02

    關(guān)于Lua腳本語言

    一、關(guān)于Lua腳本語言Lua 是一個(gè)小巧的腳本語言。是巴西里約熱內(nèi)盧天主教大學(xué)(Pontifical Catholic University of Rio de Janeiro)里的一
    發(fā)表于 08-20 06:23

    什么是腳本語言腳本語言的特點(diǎn)有哪些

    什么是腳本語言?腳本語言的特點(diǎn)有哪些?shell語言和c語言的區(qū)別在哪?編輯腳本語言的優(yōu)缺點(diǎn)有哪些?
    發(fā)表于 10-09 08:53

    適用于Java的嵌入式腳本語言是什么

    /fakescript-java簡介fakescript是一款輕量級(jí)的嵌入式腳本語言,使用Java語言編寫,語法吸取自lua、golang、erlang,基于jflex、cup生成語法樹,編譯成字節(jié)碼解釋執(zhí)行。 C/C++...
    發(fā)表于 12-23 08:17

    【HD-G2UL-EVM開發(fā)板體驗(yàn)】LUA腳本語言移植-使用LUA腳本控制蜂鳴器

    開發(fā)板,方便后面的開發(fā)測試。除了比較熱門的Python,實(shí)際上還有一門優(yōu)秀的腳本語言,LUA也適合移植到嵌入式平臺(tái)中使用。鑒于Python的內(nèi)容網(wǎng)上已經(jīng)夠多了,所以這里不再移植Python,而是LUA
    發(fā)表于 12-18 15:18

    你知道在linux下搭建lua腳本語言的編程環(huán)境?

    lua腳本語言的一種,具體的該腳本的介紹可百度,本文介紹Linux系統(tǒng)下搭建lua編程環(huán)境的步驟,以及在搭建過程中碰到的種種問題。
    發(fā)表于 05-16 16:25 ?3700次閱讀
    你知道在linux下搭建<b class='flag-5'>lua</b><b class='flag-5'>腳本語言</b>的編程環(huán)境?

    基于Lua腳本語言的ESP8266 TCP服務(wù)器資料免費(fèi)下載

      本文檔的主要內(nèi)容詳細(xì)介紹的是基于Lua腳本語言的ESP8266 TCP服務(wù)器資料免費(fèi)下載。
    發(fā)表于 06-21 17:43 ?3次下載
    基于<b class='flag-5'>Lua</b><b class='flag-5'>腳本語言</b>的ESP8266 TCP服務(wù)器資料免費(fèi)下載

    如何使用Lua腳本語言進(jìn)行ESP8266和TCP服務(wù)器多連接

    本文檔的主要內(nèi)容詳細(xì)介紹的是如何使用Lua腳本語言進(jìn)行ESP8266和TCP服務(wù)器多連接。
    發(fā)表于 06-10 17:48 ?2次下載
    如何使用<b class='flag-5'>Lua</b><b class='flag-5'>腳本語言</b>進(jìn)行ESP8266和TCP服務(wù)器多連接

    Lua腳本語言入門教程資料說明

    今天開始自己的Lua語言學(xué)習(xí),Lua腳本語言,是介于應(yīng)用程序和開發(fā)其應(yīng)用程序的底層編程語言之間,,它很方便調(diào)用其它
    發(fā)表于 05-21 18:03 ?2次下載
    <b class='flag-5'>Lua</b><b class='flag-5'>腳本語言</b>入門教程資料說明

    Lua腳本語言入門教程資料免費(fèi)下載

      寫完這篇Lua腳本語言入門,自己就要嘗試去用Lua腳本語言寫esp8266了,,自己現(xiàn)在挺心急的,因?yàn)榕笥咽褂胑sp8266本來說自己幫忙寫好程序的,但是用的單片機(jī)不一樣自己沒有,
    發(fā)表于 05-10 18:13 ?5次下載
    <b class='flag-5'>Lua</b><b class='flag-5'>腳本語言</b>入門教程資料免費(fèi)下載

    單片機(jī)腳本語言移植lua到stm32MDK的步驟

    Lu a 是一個(gè)小巧的腳本語言。作者是巴西人。該語言的設(shè)計(jì)目的是為了嵌入應(yīng)用程序中,從而為應(yīng)用程序提供靈活的擴(kuò)展和定制功能。 Lua腳本能夠非常easy的被C/C++ 代碼調(diào)用,也能夠
    的頭像 發(fā)表于 07-22 16:54 ?5847次閱讀

    【串口屏LUA教程】Lua腳本語言中文教程

    【串口屏LUA教程】Lua腳本語言中文教程
    發(fā)表于 04-29 13:03 ?26次下載

    腳本語言和編程語言的區(qū)別

    腳本語言和編程語言是計(jì)算機(jī)語言的兩個(gè)主要分類。盡管兩者都是用于編寫計(jì)算機(jī)程序的工具,但它們在設(shè)計(jì)和運(yùn)行方式上存在一些顯著的區(qū)別。下面將詳細(xì)探討腳本語言和編程
    的頭像 發(fā)表于 11-22 14:33 ?2976次閱讀