사용자 도구

사이트 도구


kb:luadebugging

Lua Debugging

루아 디버깅 관련 자잘한 내용들

루아 스택 출력하기

void print_stack(lua_State* L)
{
    int top = lua_gettop(L);
    for (int i=top; i>=1; --i)
    {
        printf("%02d | ", i);
        switch (lua_type(L, i))
        {
        case LUA_TNIL:
            printf("nil      | \n");
            break;
        case LUA_TBOOLEAN:
            printf("boolean  | %d\n", (int)lua_toboolean(L, i));
            break;
        case LUA_TNUMBER:
            printf("number   | %f\n", lua_tonumber(L, i));
            break;
        case LUA_TSTRING:
            printf("string   | %s\n", lua_tostring(L, i));
            break;
        case LUA_TTABLE:
            printf("table    | 0x%08x\n", (int)lua_topointer(L, i));
            break;
        case LUA_TFUNCTION:
            printf("function | 0x%08x\n", (int)lua_topointer(L, i));
            break;
        case LUA_TUSERDATA:
            printf("userdata | 0x%08x\n", (int)lua_topointer(L, i));
            break;
        case LUA_TTHREAD:
            printf("thread   | 0x%08x\n", (int)lua_topointer(L, i));
            break;
        default:
            printf("unknown  | \n");
            break;
        }
    }
}

딱히 무슨 비법 같은 것은 아니다만, 아래와 같은 함수와 조합하면, 루아 스택 관련 버그를 잡는 데 어느 정도 도움이 된다.

function print_all_symbols(t, indent, printed)
    assert(type(t) == "table")
    assert(type(indent) == "number")
    assert(type(printed) == "table")
 
    if printed[t] then return end
 
    printed[t] = true
 
    local prefix = ""
    for i=0,indent do
        prefix = prefix .. "    "
    end
 
    for key, value in pairs(t) do
        print(prefix .. tostring(key) .. "=" .. tostring(value))
        if type(value) == "table" then
            print_all_symbols(value, indent+1, printed)
        end
    end
end
 
print_all_symbols(_G, 0, {})

assert, error, debug.trackback

에러 발생시 콜스택 얻어내기

문자열이나 파일에 있는 스크립트를 실행할 때, doXXX 시리즈를 이용하지 말고, lua_pcall 함수를 이용해야 한다.

//////////////////////////////////////////////////////////////////////////////
/// \brief 버퍼 안에 있는 스크립트를 실행한다.
/// \param script 실행할 스크립트
/// \param chunkname 스크립트의 이름. 에러가 생긴 경우, 디버그용으로
/// chunkname이 출력된다. 그러므로 어느 정도는 의미가 있는 값을 줄 것.
/// \return bool 스크립트를 무사히 실행한 경우에는 false를 반환하고, 무언가
/// 에러가 생긴 경우에는 true를 반환한다.
//////////////////////////////////////////////////////////////////////////////
bool execute(lua_State* L, const string& script, const string& chunkname)
{
    // 먼저 스크립트를 파싱해서 청크를 만든 다음, 스택에 푸쉬
    if (luaL_loadbuffer(L, script.c_str(), script.size(), chunkname.c_str()))
    {
        // 파싱 실패!
        return true;
    }
 
    // 트레이스 함수를 위에서 파싱한 청크 밑에다 끼워넣는다.
    // 트레이스 함수는 전역 테이블에 "_TRACEBACK"이란 이름으로 들어가있다.
    int base = lua_gettop(L);
    lua_getglobal(L, "_TRACEBACK");
    lua_insert(L, base);
 
    // 실행한다.
    return lua_pcall(L, 0, 0, base) != 0;
}

위의 함수를 통해 스크립트를 실행하면 에러 발생시, 스택 최상위에 에러 메시지와 스택 트레이스가 하나의 문자열로 푸쉬된다. 대충 다음과 같은 형식이다.

[string "D:\Cuvit\Stools\SIDE\script\test.lua"]:16: too many players (4 max)
stack traceback
    [C]: in function 'PLAYER_CREATE'
    [string "D:\Cuvit\Stools\SIDE\Script\test.lua"]:16:in main chunk

앞쪽의 string, C 등은 함수가 어디에 존재하는지를 나타낸다. string은 문자열 버퍼로서 존재한다는 의미고, C는 C 쪽에 존재한다는 의미다. 여기에는 없지만, file도 있을 거라는 걸 쉽게 짐작할 ?있다. (문자열 내부에 라인 피드가 들어가 있기 때문에 여러 라인으로 표시된 것이라는 점을 유의하자. 실제 스택에 여러 라인이 들어가 있는 것이 아니다.)

딱히 신경써야할 GUI 같은 것이 없다면 그냥 출력해도 되고, 아니라면 “\n” 문자로 분리해서 출력해도 된다. 분리한 다음에 아래에 있는 파싱 방법을 통해, 파일+라인+메시지 형식으로 만들 수도 있을 것이다.

코루틴(coroutine) 내부에서 문제가 발생했을 때 알아내기

코루틴 내부에서 문법 에러 등이 났을 때, 루아는 이를 화면에 출력해주지 않는다. 보호 모드에서 동작하기 때문이다. 이걸 제대로 출력하기 위해서는…

coroutine.resume(co)

이렇게 할 것이 아니라, 다음과 같이 해야한다.

success, msg = coroutine.resume(co)

success 변수에 들어갈 수 있는 값은 true 또는 false이다. true인 경우는 coroutine이 정상적으로 종료되었거나, 사용자가 coroutine.yield()를 호출했을 경우이다. false인 경우는 문제가 생긴 경우이다.

msg 변수에 들어가는 값의 종류는 세 가지다. 첫째, coroutine.resume()이 정상 종료되면서 사용자가 리턴한 값일 경우이고, 두 번째는 coroutine.yield()를 호출하면서 사용자가 전달한 파라미터 중 하나인 경우이고, 마지막으로 coroutine.resume()이 비정상 종료(문법 에러 등으로)되면서 나온 에러 메시지 문자열인 경우이다. 참고로 첫번째와 두번째 경우, 사용자가 아무 것도 리턴하지 않거나, 전달하지 않으면 msg 변수에는 nil 값이 들어가게 된다.

어쨌든 세번째 경우, msg를 이용하면 에러 메시지를 출력할 수 있다. 즉 success 값을 보고, false인 경우 msg 변수를 받아내어 출력하면 되는 것이다. 이 에러 메시지의 포맷은 LUA 5.0의 경우, 다음과 같다.

[string "CHUNKNAME"]:LINE: ERRORMSG

이것과 함께 에러를 발생시킨 스크립트를 잘 출력하면 꽤 쓸만한 디버그 정보를 얻을 수 있다. (좀 무식하기는 하다.)

void LuaAPI::debugmsg(ostream& os, const string& msg, const string& script)
{
    // 에러 발생 시 루아가 리턴하는 에러 메시지의 포맷은 다음과 같다.
    // [string "CHUNKNAME"]:LINE: ERRORMSG
    // 그러므로 "\"]:" 문자열을 찾은 뒤, 그 다음의 ":" 문자열을 찾고,
    // 그 사이의 문자열을 숫자로 변환하면 라인 번호가 된다.
    size_t line_begin  = msg.find("\"]:", 0);
    size_t line_end    = msg.find(":", line_begin+3);
    string line_string = msg.substr(line_begin+3, line_end - line_begin - 3);
    int    line        = atoi(line_string.c_str());
 
    os << "!!! SCRIPT ERROR OCCURRED !!!!" << endl;
    os << "=== SCRIPT ERROR MSG >>>" << endl << msg << endl;
    os << "=== SCRIPT ERROR OCCURRED LINE >>>" << endl;
    for (int i=__max(1, line-2); i<=line+2; i++)
    {
        if (i == line) os << ">> " << setw(3) << MUtil::itos(i) << "| ";
        else           os << "   " << setw(3) << MUtil::itos(i) << "| ";
        string nth = MUtil::nthline(script, (size_t)i);
        if (nth.size() < 65) os << nth << endl;
        else                 os << nth.substr(0, 65) << " [...]" << endl;
    }
}

스크립트 처리 중 에러 발생시 C 함수 호출하기

루아 라이브러리 소스 중에 $(LUA)\src\lauxlib.c 파일을 연 다음 callalert 함수를 찾아서 보면, 에러가 생겼을 경우, 전역 테이블에 “_ALERT”라는 함수가 있는지 찾아보고, 있으면 이를 호출하고, 없다면 stderr에 에러 메시지를 뿌린다는 것을 알 수 있다.

lauxlib.c

...생략...
static void callalert (lua_State *L, int status) 
{
    if (status != 0) {
        lua_getglobal(L, "_ALERT");
        if (lua_isfunction(L, -1)) {
            lua_insert(L, -2);
            lua_call(L, 1, 0);
        }
        else {  
            /* no _ALERT function; print it on stderr */
            fprintf(stderr, "%s\n", lua_tostring(L, -2));
            lua_pop(L, 2);  /* remove error message and _ALERT */
        }
    }
}
...생략...

이것을 이용하면 에러 발생시에 C 함수를 호출할 수 있다. 즉 “_ALERT”라는 이름으로 C 함수를 전역 테이블에다 등록해주면, 에러 발생시 루아가 이를 호출해주는 것이다.

int myAlert(lua_State* L)
{
    string error_msg = string(lua_tostring(L, -1));
    cerr << error_msg << endl;
    return 0;
}
 
...생략...
 
lua_pushcfunction(L, myAlert);
lua_setglobal(L, "_ALERT");

:!: lua_atpanic 함수를 이용해도 같은 결과를 얻을 수 있다.


kb/luadebugging.txt · 마지막으로 수정됨: 2014/11/06 17:05 (바깥 편집)