LUA调试环境
- 运行模式
- 断点确认
- 运行控制
- 基本准备工作
- 控制
- 调试信息获取
今天在已有基础上继续
做一个功能,可以考虑通用与专用需求。我现在做的这个功能,只是先给自己用,所以很多地方可以特殊处理,能满足自用即可,最主要的是时间上紧迫,项目交付在即,不能花大量时间去研究别人或第三方的细节。那就拿来主要功能,改造下集成到系统之中。
运行模式
有编程N年的经历,自然知道IDE的模式,虽然术语不准确,但自己明白含义。
运行这块,在自用的需求情况下,可简化成全速运行、调试运行,后者再划分成运行到断点、单步运行。至于运行到某一行,好象需求不大,也就不考虑。而单步运行,直接阉割成运行到下一调用。
直接定义一下
enum NDebugPauseType {
dptNextBreakpoint = 1, // 下一断点
dptNextCall = 2, // 下一调用
dptNone = -1 // 无需暂停
};
这里调用的原因在于,使用环境是C++与LUA的集成调试,至于在LUA内部的运行,暂可以不管,只关心二者之间的交互。这些交互都是通过各种函数调用实现的,可认为是调用组控制。
所以,断点也应该定义设置在各函数调用处,纯LUA的流程处先不处理。
这样做的原因,也是在于没有下功夫去研究LUA的流程控制,即在LUA内部跑的时候,还没有想办法去暂停等控制运行操作。
断点确认
在上面调用级控制前提下,确认断点就比较方便了。
bool __fastcall TCbwSynEditor::IsBreakpointLine(TDrLUA * lua) {
lua->RefreshDebugInfo();
DebugLuaObject = lua;
UnicodeString fileName = lua->CurrentFileName;
int lineNumber = lua->CurrentLine;
bool needPauseFlag = false;
if(dptNextBreakpoint == DebugPauseType) {
TBreakPoints * destItems = NULL;
CBW_ITERATOR(vector<TBreakPoints *>, FBreakpoints) {
if(ExtractFileName((*it)->fileName) == ExtractFileName(fileName))
destItems = *it;
}
if(destItems)
CBW_ITERATOR(vector<int>, destItems->lineNumbers)
if(*it == lineNumber)
needPauseFlag = true;
}
if(dptNextCall == DebugPauseType)
needPauseFlag = true;
return needPauseFlag;
}
其中,获取LUA的调试信息,直接调用LUA的调试信息lua_getinfo即可。
运行控制
发现满足断点条件后,当然是需要控制处理。说控制有点过份了,因为就是暂停处理。
基本准备工作
采用Windows内核事件对象就OK。实现几个函数供业务调用:
void __fastcall TCbwSynEditor::DebugEvent_Init() {
hDebugEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
}
void __fastcall TCbwSynEditor::DebugEvent_Deinit() {
CloseHandle(hDebugEvent);
}
void __fastcall TCbwSynEditor::DebugEvent_Wait() {
IsDebugPaused = true;
ResetEvent(hDebugEvent);
WaitForSingleObject(hDebugEvent, INFINITE);
}
void __fastcall TCbwSynEditor::DebugEvent_Next() {
SetEvent(hDebugEvent);
}
控制
所谓控制也就是在几个适宜环节条件下,调用上述方法
简单地说,在每次调试运行之前,初始化DebugEvent_Init。完成后,清理DebugEvent_Deinit。发现满足条件,自动等待DebugEvent_Wait。用户干预,比如按F9运行到下一断点,F8运行到下一调用,都是DebugEvent_Next。
唯一需要注意的一点就是,因为调试会可能导致长时间等待,所以不能放在主线程。用一个简单的多线程即可。
class TDebugThread : public TThread {
private:
TDrLUA * FLuaObject;
UnicodeString FFunctionName;
void __fastcall Execute();
public:
__fastcall TDebugThread(TForm * ownerForm, TDrLUA * lua, UnicodeString functionName);
};
__fastcall TDebugThread::TDebugThread(TDrLUA * lua, UnicodeString functionName) : TThread(true) {
FreeOnTerminate = true;
FLuaObject = lua;
FFunctionName = functionName;
Suspended = false;
}
void __fastcall TDebugThread::Execute() {
THelper::Debug::AddLog("Execute::");
FLuaObject->IsLogging_Script = true;
TScriptManager::GetInstance()->RunFunction(FLuaObject, FFunctionName);
}
再加上界面按钮,运行后,可以达到预期的断点暂停、继续、中止效果。
调试信息获取
作为调试工作,上面做的都是铺垫,最终还是需要在暂停的时候,获取相应的调试信息。
这个没法绕过,老老实实地看LUA文档。
试了几次,发现LUA脚本可以输出调试信息,采用debug.xxx即可。那就先实现一个LUA函数,以供C++调用获取。这样,在断点暂停的时候,调用该函数则得到相应的调试信息。类似于
[C]: in function 'Log'
D:\ExenObj\Exe\XE5\Debug\Logic_233.logic:285: in function 'test'
D:\ExenObj\Exe\XE5\Debug\Logic_233.logic:258: in function 'test1'
VarInFun2
(*temporary) = 37035
(*temporary) = 24690
VarInFun3
param = this is a test
param2 = and second value
x = 12345
y = 24690
z = 304798050
VarInFun4
剩下的事,就是解析这些信息,然后展示出来。图快,就直接用了个TTreeView进行显示,刚好还可以把函数调用关系展示出来。
现在工作完成了80%。还需要做的事就是易用处理,包括且不限于点击某变量时,直接定位到源文件相应行、对于特殊类型变量,如图像、XML文档等,以更直观方式进行展现。
明天完成。