可可西

还原Lua调用栈

Lua数据类型

类型 大类型 类型细分 _tt(类型)
nil #define LUA_TNIL 0

0
布尔 #define LUA_TBOOLEAN 1

int

1

number

#define LUA_TNUMBER 

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] // 行号  注: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(可能为LClosurelua_CFunctionCClosure注:StkIdTValue*类型

例如:上面c++栈上的lua_pcallk即为I:\\pubcode\\LuaTest\\LuaCode\\Test4.luaLClosure(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_tracebackdb_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平衡

 

posted on 2021-01-10 23:04  可可西  阅读(1126)  评论(0编辑  收藏  举报

导航