사용자 도구

사이트 도구


kb:luadebugging

차이

문서의 선택한 두 판 사이의 차이를 보여줍니다.

차이 보기로 링크

kb:luadebugging [2014/11/06 17:05] (현재)
줄 1: 줄 1:
 +====== Lua Debugging ======
 +루아 디버깅 관련 자잘한 내용들
 +
 +====== 루아 스택 출력하기 ======
 +<code cpp>
 +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;
 +        }
 +    }
 +}
 +</​code>​
 +
 +딱히 무슨 비법 같은 것은 아니다만,​ 아래와 같은 함수와 조합하면,​ 루아 스택 관련 버그를 잡는 데 어느 정도 도움이 된다. ​
 +
 +<code lua>
 +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, {})
 +</​code>​
 +
 +====== assert, error, debug.trackback ======
 +
 +
 +====== 에러 발생시 콜스택 얻어내기 ======
 +문자열이나 파일에 있는 스크립트를 실행할 때, doXXX 시리즈를 이용하지 말고, lua_pcall 함수를 이용해야 한다.
 +<code cpp>
 +//////////////////////////////////////////////////////////////////////////////​
 +/// \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;
 +}
 +</​code>​
 +
 +위의 함수를 통해 스크립트를 실행하면 에러 발생시, 스택 최상위에 에러 메시지와 스택 트레이스가 하나의 문자열로 푸쉬된다. 대충 다음과 같은 형식이다.
 +
 +<​code>​
 +[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
 +</​code>​
 +앞쪽의 string, C 등은 함수가 어디에 존재하는지를 나타낸다. string은 문자열 버퍼로서 존재한다는 의미고, C는 C 쪽에 존재한다는 의미다. 여기에는 없지만, file도 있을 거라는 걸 쉽게 짐작할 ?있다. (문자열 내부에 라인 피드가 들어가 있기 때문에 여러 라인으로 표시된 것이라는 점을 유의하자. 실제 스택에 여러 라인이 들어가 있는 것이 아니다.)
 +
 +딱히 신경써야할 GUI 같은 것이 없다면 그냥 출력해도 되고, 아니라면 "​\n"​ 문자로 분리해서 출력해도 된다. 분리한 다음에 아래에 있는 파싱 방법을 통해, 파일+라인+메시지 형식으로 만들 수도 있을 것이다.
 +
 +
 +====== 코루틴(coroutine) 내부에서 문제가 발생했을 때 알아내기 ======
 +코루틴 내부에서 문법 에러 등이 났을 때, 루아는 이를 화면에 출력해주지 않는다. 보호 모드에서 동작하기 때문이다. 이걸 제대로 출력하기 위해서는...
 +
 +<code lua>
 +coroutine.resume(co)
 +</​code>​
 +
 +이렇게 할 것이 아니라, 다음과 같이 해야한다.
 +
 +<code cpp>
 +success, msg = coroutine.resume(co)
 +</​code>​
 +
 +success 변수에 들어갈 수 있는 값은 true 또는 false이다. true인 경우는 coroutine이 정상적으로 종료되었거나,​ 사용자가 coroutine.yield()를 호출했을 경우이다. false인 경우는 문제가 생긴 경우이다.
 +
 +msg 변수에 들어가는 값의 종류는 세 가지다. 첫째, coroutine.resume()이 정상 종료되면서 사용자가 리턴한 값일 경우이고,​ 두 번째는 coroutine.yield()를 호출하면서 사용자가 전달한 파라미터 중 하나인 경우이고,​ 마지막으로 coroutine.resume()이 비정상 종료(문법 에러 등으로)되면서 나온 에러 메시지 문자열인 경우이다. 참고로 첫번째와 두번째 경우, 사용자가 아무 것도 리턴하지 않거나, 전달하지 않으면 msg 변수에는 nil 값이 들어가게 된다.
 +
 +어쨌든 세번째 경우, msg를 이용하면 에러 메시지를 출력할 수 있다. 즉 success 값을 보고, false인 경우 msg 변수를 받아내어 출력하면 되는 것이다. 이 에러 메시지의 포맷은 LUA 5.0의 경우, 다음과 같다.
 +<​code>​
 +[string "​CHUNKNAME"​]:​LINE:​ ERRORMSG
 +</​code>​
 +
 +이것과 함께 에러를 발생시킨 스크립트를 잘 출력하면 꽤 쓸만한 디버그 정보를 얻을 수 있다. (좀 무식하기는 하다.)
 +
 +<code cpp>
 +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;
 +    }
 +}
 +</​code>​
 +
 +====== 스크립트 처리 중 에러 발생시 C 함수 호출하기 ======
 +루아 라이브러리 소스 중에 $(LUA)\src\lauxlib.c 파일을 연 다음 callalert 함수를 찾아서 보면, 에러가 생겼을 경우, 전역 테이블에 "​_ALERT"​라는 함수가 있는지 찾아보고,​ 있으면 이를 호출하고,​ 없다면 stderr에 에러 메시지를 뿌린다는 것을 알 수 있다. ​
 +
 +**lauxlib.c**
 +<code cpp>
 +...생략...
 +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 */
 +        }
 +    }
 +}
 +...생략...
 +</​code>​
 +
 +이것을 이용하면 에러 발생시에 C 함수를 호출할 수 있다. 즉 "​_ALERT"​라는 이름으로 C 함수를 전역 테이블에다 등록해주면,​ 에러 발생시 루아가 이를 호출해주는 것이다.
 +
 +<code cpp>
 +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"​);​
 +</​code>​
 +
 +:!: lua_atpanic 함수를 이용해도 같은 결과를 얻을 수 있다.
 +----
 +  * see also [[Lua]]
  
kb/luadebugging.txt · 마지막으로 수정됨: 2014/11/06 17:05 (바깥 편집)