还原Lua调用栈
Lua数据类型
类型 | 大类型 | 类型细分 | _tt(类型) |
nil | #define LUA_TNIL 0 |
空 |
0 |
布尔 | #define LUA_TBOOLEAN 1 |
int |
1 |
number |
#define LUA_TNUMBER 3 |
double |
3 |
int |
19(0x13) |
||
字符串 |
#define LUA_TSTRING 4 |
短字符串 |
68(0x44) |
长字符串 |
84(0x54) | ||
function |
#define LUA_TFUNCTION 6 |
lua_CFunction /* typedef int (*lua_CFunction) (lua_State *L); */ |
22(0x16) |
CClosure |
102(0x66) | ||
LClosure |
70(0x46) |
||
表 | #define LUA_TTABLE 5 |
Table |
69(0x45) |
user data |
#define LUA_TLIGHTUSERDATA 2 |
void* /* 轻量级用户数据(light userdata)*/ |
2 |
#define LUA_TUSERDATA 7 |
Udata /* 完全用户数据(full userdata)*/ |
71(0x47) | |
Lua栈 |
#define LUA_TTHREAD 8 |
Lua_State |
72(0x48) |
CallInfo结构
Lua_State结构
C++中还原lua调用栈
Lua代码:
1 -- 2 -- This is a test lua 3 -- 4 function TestFunc(a) 5 return square(a); 6 end 7 8 TestFunc(5);
C++代码:
注:luaL_dofile函数实际上是执行了luaL_loadfile来加载lua文件,加载成功之后会编译该代码块为一个Lua闭包放置在栈顶,然后继续调用lua_pcall来执行该Lua闭包,最后把该Lua闭包弹出栈。
当前c++调用栈:
LuaTest.exe!Square(lua_State * L=0000022f395eb018) Line 57 LuaTest.exe!luaD_precall(lua_State * L=0000022f395eb018, lua_TValue * func=0x0000022f395fbc00, int nresults=-1) Line 434 LuaTest.exe!luaV_execute(lua_State * L=0000022f395eb018) Line 1149 LuaTest.exe!luaD_call(lua_State * L=0000022f395eb018, lua_TValue * func=0x0000022f395fbbd0, int nResults=-1) Line 500 LuaTest.exe!luaD_callnoyield(lua_State * L=0000022f395eb018, lua_TValue * func=0x0000022f395fbbd0, int nResults=-1) Line 510 LuaTest.exe!f_call(lua_State * L=0000022f395eb018, void* ud=0x00000061ff4ff518) Line 943 ; LuaTest.exe!luaD_rawrunprotected(lua_State * L=0000022f395eb018, void(*)(lua_State *, void *)f=0x00007ff6cd3f00e0, void * ud=0x00000061ff4ff518) Line 145 LuaTest.exe!luaD_pcall(lua_State * L=0000022f395eb018, void(*)(lua_State *, void *)func=0x00007ff6cd3f00e0, void * ud=0x00000061ff4ff518, __int64 old_top=64, __int64 ef=0) Line 729 LuaTest.exe!lua_pcallk(lua_State * L=0000022f395eb018, int nargs=0, int nresults=-1, int errfunc=0, __int64 ctx=0, int(*)(lua_State *, int, __int64) k=0x0000000000000000) Line 968 //执行加载的Test4.lua文件对应的Lua闭包 LuaTest.exe!CGameLuaHandler::WriteHello() Line 95 LuaTest.exe!CGameLogic::Run() Line 44 LuaTest.exe!main() Line 18
lua调用栈解析:
# | 表达式 | 结果 | 类型 |
Frame 1 | L->ci->func->tt_ | 22 // lua_CFunction类型 | int |
L->ci->func->value_.gc | 0x00007ff6cd3f0050 {LuaTest.exe!Square(lua_State *)} | GCObject * | |
(L->ci->func+1)->tt_ | 19 // 为传入的第一个参数的类型为int | int | |
(int*)(&(L->ci->func+1)->value_) | 0x0000024417590650 {5} // 为传入的第一个参数的值为5 | int * | |
Frame 2 | L->ci->previous->func->tt_ | 70 // LClosure | int |
(char*)((LClosure*)L->ci->previous->func->value_.gc)->p->source + sizeof(TString) | 0x0000024417589628 "@I:\\pubcode\\LuaTest\\LuaCode\\Test4.lua" | char * | |
L->ci->previous->u.l.savedpc - ((LClosure*) L->ci->previous->func->value_.gc)->p->code | 3 | __int64 | |
((LClosure*) L->ci->previous->func->value_.gc)->p->lineinfo [ 3 -1] | 5 // 行号 注:lua5.4要调用luaG_getfuncline函数 | int | |
Frame 3 | L->ci->previous->previous->func->tt_ | 70 // LClosure | int |
(char*)((LClosure*)L->ci->previous->previous->func->value_.gc)->p->source + sizeof(TString) | 0x0000024417589628 "@I:\\pubcode\\LuaTest\\LuaCode\\Test4.lua" | char * | |
L->ci->previous->previous->u.l.savedpc - ((LClosure*) L->ci->previous->previous->func->value_.gc)->p->code | 5 | __int64 | |
((LClosure*) L->ci->previous->func->value_.gc)->p->lineinfo [ 5 -1] | 8 // 行号 注:lua5.4要调用luaG_getfuncline函数 | int | |
Frame 4 | L->ci->previous->previous->previous->func->tt_ | 0 // nil | int |
注:若当前Frame为CClosure,可通过((CClosure*)L->ci->func->value_.gc)->f来获取其指针,其类型形如:int(*)(lua_State *)
使用c++函数来还原,实现如下:
#include <cstring> #include <string> #include <vector> extern "C" { #include "lua.h" #include "lua.hpp" #include "lualib.h" #include "lauxlib.h" #include "luaconf.h" #include "lobject.h" #include "lstate.h" } const TValue luaO_nilobject_ = { NILCONSTANT }; /* corresponding test */ #define isvalid(o) ((o) != luaO_nilobject) /* value at a non-valid index */ #define NONVALIDVALUE cast(TValue *, luaO_nilobject) /* test for pseudo index */ #define ispseudo(i) ((i) <= LUA_REGISTRYINDEX) static TValue* index2addr(lua_State* L, int idx) { CallInfo* ci = L->ci; if (idx > 0) { TValue* o = ci->func + idx; api_check(L, idx <= ci->top - (ci->func + 1), "unacceptable index"); if (o >= L->top) return NONVALIDVALUE; else return o; } else if (!ispseudo(idx)) { /* negative index */ api_check(L, idx != 0 && -idx <= L->top - (ci->func + 1), "invalid index"); return L->top + idx; } else if (idx == LUA_REGISTRYINDEX) return &G(L)->l_registry; else { /* upvalues */ idx = LUA_REGISTRYINDEX - idx; api_check(L, idx <= MAXUPVAL + 1, "upvalue index too large"); if (ttislcf(ci->func)) /* light C function? */ return NONVALIDVALUE; /* it has no upvalues */ else { CClosure* func = clCvalue(ci->func); return (idx <= func->nupvalues) ? &func->upvalue[idx - 1] : NONVALIDVALUE; } } } void GetLuaTValue(lua_State* L, StkId o, int t, int ad, int count, char* szOutput) { int i = count + ad + 1; switch (t) { case LUA_TSTRING: sprintf_s(szOutput, 255, "%d[%d]: '%s'", i, ad, lua_tostring(L, i)); break; case LUA_TBOOLEAN: sprintf_s(szOutput, 255, "%d[%d]: %s", i, ad, lua_toboolean(L, i) ? "true" : "false"); break; case LUA_TNUMBER: if (ttisinteger(o)) sprintf_s(szOutput, 255, "%d[%d]: int %d", i, ad, (int)lua_tointeger(L, i)); else sprintf_s(szOutput, 255, "%d[%d]: double %lf", i, ad, lua_tonumber(L, i)); break; case LUA_TFUNCTION: if (ttislcf(o)) { sprintf_s(szOutput, 255, "%d[%d]: c 0x%p", i, ad, fvalue(o)); // lua_CFunction } else if (ttisCclosure(o)) { sprintf_s(szOutput, 255, "%d[%d]: c closure 0x%p FucAdr:0x%p", i, ad, clCvalue(o), clCvalue(o)->f); // CClosure } else if (ttisLclosure(o)) { Proto* pinfo = clLvalue(o)->p; sprintf_s(szOutput, 255, "%d[%d]: lua closure 0x%p %s[%d,%d]", i, ad, clLvalue(o), getstr(pinfo->source), pinfo->linedefined, pinfo->lastlinedefined); } break; case LUA_TTABLE: sprintf_s(szOutput, 255, "%d[%d]: table:0x%p", i, ad, hvalue(o)); break; case LUA_TLIGHTUSERDATA: sprintf_s(szOutput, 255, "%d[%d]: light userdata:0x%p", i, ad, pvalue(o)); break; case LUA_TUSERDATA: sprintf_s(szOutput, 255, "%d[%d]: full userdata:0x%p", i, ad, uvalue(o)); break; case LUA_TTHREAD: sprintf_s(szOutput, 255, "%d[%d]: thread:0x%p", i, ad, thvalue(o)); break; default: sprintf_s(szOutput, 255, "%d[%d]: %s", i, ad, lua_typename(L, t)); break; } } const char* VSDumpLuaStateTValue(lua_State* L, int n) { static char s_szBuffer[256] = { 0 }; memset(s_szBuffer, 0, 256); int ad = -1; int count = lua_gettop(L); int i = count; while (i) { if ((n < 0 ? ad : i) == n) { StkId o = index2addr(L, i); int t = (isvalid(o) ? ttnov(o) : LUA_TNONE); GetLuaTValue(L, o, t, ad, count, s_szBuffer); return s_szBuffer; } i--; ad--; } return "Lua Frame not found!"; } std::vector<std::string> VSDumpLuaState(lua_State* L) { std::vector<std::string> Msg; char szBuffer[256] = { 0 }; memset(szBuffer, 0, 256); int ad = -1; int count = lua_gettop(L); int i = count; sprintf_s(szBuffer, 255, "---------------- Lua State Dump ----------------"); Msg.push_back(szBuffer); while (i) { StkId o = index2addr(L, i); int t = (isvalid(o) ? ttnov(o) : LUA_TNONE); GetLuaTValue(L, o, t, ad, count, szBuffer); Msg.push_back(szBuffer); i--; ad--; } sprintf_s(szBuffer, 255, "---------------------------------------------"); Msg.push_back(szBuffer); return Msg; } std::vector<std::string> VSDumpLuaCallStack(lua_State* L) { std::vector<std::string> Msg; char szBuffer[256] = { 0 }; memset(szBuffer, 0, 256); sprintf_s(szBuffer, 255, "---------------- Lua Call Stack Dump ----------------"); Msg.push_back(szBuffer); CallInfo* curCi = L->ci; while (curCi != NULL) { StkId func = curCi->func; if (ttislcf(func)) { sprintf_s(szBuffer, 255, "++ CallStack: c 0x%p", fvalue(func)); // lua_CFunction } else if (ttisCclosure(func)) { sprintf_s(szBuffer, 255, "++ CallStack: c closure 0x%p FucAdr:0x%p", clCvalue(func), clCvalue(func)->f); // CClosure } else if (ttisLclosure(func)) { Proto* pinfo = clLvalue(func)->p;
#if 503 == LUA_VERSION_NUM
int linenum = pinfo->lineinfo[(unsigned __int64)(curCi->u.l.savedpc) - (unsigned __int64)(pinfo->code) - 1];
#else
int linenum = luaG_getfuncline(pinfo, pcRel(curCi->u.l.savedpc, pinfo)); // lua 5.4
#endif
sprintf_s(szBuffer, 255, "++ CallStack: lua closure 0x%p %s:%d", clLvalue(func), getstr(pinfo->source), linenum); } else { int funct = (isvalid(func) ? ttnov(func) : LUA_TNONE); sprintf_s(szBuffer, 255, "++ CallStack: %s", lua_typename(L, funct)); } Msg.push_back(szBuffer); StkId locals = ((L->top < curCi->top) ? L->top : curCi->top) - 1; int ad = -1; int count = (int)(locals - func); while (locals > func) { StkId o = locals; int t = (isvalid(o) ? ttnov(o) : LUA_TNONE); GetLuaTValue(L, o, t, ad, count, szBuffer); Msg.push_back(szBuffer); --locals; --ad; } if (curCi == &(L->base_ci)) { break; } curCi = curCi->previous; } sprintf_s(szBuffer, 255, "---------------------------------------------"); Msg.push_back(szBuffer); return Msg; }
在C++代码中(在业务逻辑或lua虚拟机里面)调用LuaCall(为以下4个函数),会导致luaD_call函数的调用,其参数StkId func为当前要执行的function(可能为LClosure、lua_CFunction或CClosure) 注:StkId即TValue*类型
例如:上面c++栈上的lua_pcallk即为I:\\pubcode\\LuaTest\\LuaCode\\Test4.lua的LClosure(Lua闭包)
/***************** lua.h *****************/ LUA_API void (lua_callk) (lua_State *L, int nargs, int nresults, lua_KContext ctx, lua_KFunction k); #define lua_call(L,n,r) lua_callk(L, (n), (r), 0, NULL) LUA_API int (lua_pcallk) (lua_State *L, int nargs, int nresults, int errfunc, lua_KContext ctx, lua_KFunction k); #define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL)
调用关系如下:
lua_pcall() / lua_pcallk() / lua_call() / lua_callk |- ... ... |-luaD_call(lua_State *L, StkId func, int nResults) |-if (!luaD_precall(L, func, nResults)) /* 返回0表示为LClosure */ 注:① 通过next_ci宏创建新的CallInfo栈帧 ② lua_CFunction或CClosure在该函数中直接执行 ③ LClosure会在该函数中参数不够时进行nil补全,为送入VM执行LClosure做好准备 |- luaV_execute(L); /* 虚拟机执行LClosure */ |-... ... |-newframe: /* jump tag */ |-... ... |-for (;;) |-... ... |-vmcase(OP_CALL) /* call指令 */ |-if (luaD_precall(L, ra, nresults)) |---... ... |-else |---... ... |---goto newframe; |-vmcase(OP_TAILCALL) /* tailcall指令 */ |-if (luaD_precall(L, ra, nresults)) |---... ... |-else |---... ... |---goto newframe; |-... ...
Lua中还原lua调用栈
lua中的使用debug.traceback函数打印当前的lua调用栈
Lua代码:
1 -- 2 -- This is a test lua 3 -- 4 function TestFunc(a) 5 SubFunc(a); 6 end 7 8 function SubFunc(a) 9 local b = a + 100 10 print(debug.traceback()); 11 end 12 13 TestFunc(5);
输出结果如下:
stack traceback: I:\pubcode\LuaTest\LuaCode/test4.lua:10: in function 'SubFunc' I:\pubcode\LuaTest\LuaCode/test4.lua:5: in function 'TestFunc' I:\pubcode\LuaTest\LuaCode/test4.lua:13: in main chunk [C]: in ?
它在c++中的绑定函数为db_traceback,db_traceback最后调用luaL_traceback函数来实现lua栈的打印
LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, int level) { lua_Debug ar; int top = lua_gettop(L); int last = lastlevel(L1); int n1 = (last - level > LEVELS1 + LEVELS2) ? LEVELS1 : -1; if (msg) lua_pushfstring(L, "%s\n", msg); luaL_checkstack(L, 10, NULL); lua_pushliteral(L, "stack traceback:"); while (lua_getstack(L1, level++, &ar)) { if (n1-- == 0) { /* too many levels? */ lua_pushliteral(L, "\n\t..."); /* add a '...' */ level = last - LEVELS2 + 1; /* and skip to last ones */ } else { lua_getinfo(L1, "Slnt", &ar); lua_pushfstring(L, "\n\t%s:", ar.short_src); if (ar.currentline > 0) lua_pushfstring(L, "%d:", ar.currentline); lua_pushliteral(L, " in "); pushfuncname(L, &ar); if (ar.istailcall) lua_pushliteral(L, "\n\t(...tail calls...)"); lua_concat(L, lua_gettop(L) - top); } } lua_concat(L, lua_gettop(L) - top); }
示例代码如下:
int n = lua_gettop(L); luaL_traceback(L, L, "", 0); const char* luaCallStack = lua_tostring(L, -1); /* luaCallStack结果如下: stack traceback: [C]: in function 'square' I:\pubcode\LuaTest\LuaCode\test3.lua:18: in local 'LuaCall' I:\pubcode\LuaTest\LuaCode\test3.lua:21: in main chunk */ lua_settop(L, n); // 保持stack平衡