| /* |
| ** Debugging and introspection. |
| ** Copyright (C) 2005-2015 Mike Pall. See Copyright Notice in luajit.h |
| */ |
| |
| #define lj_debug_c |
| #define LUA_CORE |
| |
| #include "lj_obj.h" |
| #include "lj_err.h" |
| #include "lj_debug.h" |
| #include "lj_str.h" |
| #include "lj_tab.h" |
| #include "lj_state.h" |
| #include "lj_frame.h" |
| #include "lj_bc.h" |
| #include "lj_vm.h" |
| #if LJ_HASJIT |
| #include "lj_jit.h" |
| #endif |
| |
| /* -- Frames -------------------------------------------------------------- */ |
| |
| /* Get frame corresponding to a level. */ |
| cTValue *lj_debug_frame(lua_State *L, int level, int *size) |
| { |
| cTValue *frame, *nextframe, *bot = tvref(L->stack); |
| /* Traverse frames backwards. */ |
| for (nextframe = frame = L->base-1; frame > bot; ) { |
| if (frame_gc(frame) == obj2gco(L)) |
| level++; /* Skip dummy frames. See lj_meta_call(). */ |
| if (level-- == 0) { |
| *size = (int)(nextframe - frame); |
| return frame; /* Level found. */ |
| } |
| nextframe = frame; |
| if (frame_islua(frame)) { |
| frame = frame_prevl(frame); |
| } else { |
| if (frame_isvarg(frame)) |
| level++; /* Skip vararg pseudo-frame. */ |
| frame = frame_prevd(frame); |
| } |
| } |
| *size = level; |
| return NULL; /* Level not found. */ |
| } |
| |
| /* Invalid bytecode position. */ |
| #define NO_BCPOS (~(BCPos)0) |
| |
| /* Return bytecode position for function/frame or NO_BCPOS. */ |
| static BCPos debug_framepc(lua_State *L, GCfunc *fn, cTValue *nextframe) |
| { |
| const BCIns *ins; |
| GCproto *pt; |
| BCPos pos; |
| lua_assert(fn->c.gct == ~LJ_TFUNC || fn->c.gct == ~LJ_TTHREAD); |
| if (!isluafunc(fn)) { /* Cannot derive a PC for non-Lua functions. */ |
| return NO_BCPOS; |
| } else if (nextframe == NULL) { /* Lua function on top. */ |
| void *cf = cframe_raw(L->cframe); |
| if (cf == NULL || (char *)cframe_pc(cf) == (char *)cframe_L(cf)) |
| return NO_BCPOS; |
| ins = cframe_pc(cf); /* Only happens during error/hook handling. */ |
| } else { |
| if (frame_islua(nextframe)) { |
| ins = frame_pc(nextframe); |
| } else if (frame_iscont(nextframe)) { |
| ins = frame_contpc(nextframe); |
| } else { |
| /* Lua function below errfunc/gc/hook: find cframe to get the PC. */ |
| void *cf = cframe_raw(L->cframe); |
| TValue *f = L->base-1; |
| for (;;) { |
| if (cf == NULL) |
| return NO_BCPOS; |
| while (cframe_nres(cf) < 0) { |
| if (f >= restorestack(L, -cframe_nres(cf))) |
| break; |
| cf = cframe_raw(cframe_prev(cf)); |
| if (cf == NULL) |
| return NO_BCPOS; |
| } |
| if (f < nextframe) |
| break; |
| if (frame_islua(f)) { |
| f = frame_prevl(f); |
| } else { |
| if (frame_isc(f) || (LJ_HASFFI && frame_iscont(f) && |
| (f-1)->u32.lo == LJ_CONT_FFI_CALLBACK)) |
| cf = cframe_raw(cframe_prev(cf)); |
| f = frame_prevd(f); |
| } |
| } |
| ins = cframe_pc(cf); |
| } |
| } |
| pt = funcproto(fn); |
| pos = proto_bcpos(pt, ins) - 1; |
| #if LJ_HASJIT |
| if (pos > pt->sizebc) { /* Undo the effects of lj_trace_exit for JLOOP. */ |
| GCtrace *T = (GCtrace *)((char *)(ins-1) - offsetof(GCtrace, startins)); |
| lua_assert(bc_isret(bc_op(ins[-1]))); |
| pos = proto_bcpos(pt, mref(T->startpc, const BCIns)); |
| } |
| #endif |
| return pos; |
| } |
| |
| /* -- Line numbers -------------------------------------------------------- */ |
| |
| /* Get line number for a bytecode position. */ |
| BCLine LJ_FASTCALL lj_debug_line(GCproto *pt, BCPos pc) |
| { |
| const void *lineinfo = proto_lineinfo(pt); |
| if (pc <= pt->sizebc && lineinfo) { |
| BCLine first = pt->firstline; |
| if (pc == pt->sizebc) return first + pt->numline; |
| if (pc-- == 0) return first; |
| if (pt->numline < 256) |
| return first + (BCLine)((const uint8_t *)lineinfo)[pc]; |
| else if (pt->numline < 65536) |
| return first + (BCLine)((const uint16_t *)lineinfo)[pc]; |
| else |
| return first + (BCLine)((const uint32_t *)lineinfo)[pc]; |
| } |
| return 0; |
| } |
| |
| /* Get line number for function/frame. */ |
| static BCLine debug_frameline(lua_State *L, GCfunc *fn, cTValue *nextframe) |
| { |
| BCPos pc = debug_framepc(L, fn, nextframe); |
| if (pc != NO_BCPOS) { |
| GCproto *pt = funcproto(fn); |
| lua_assert(pc <= pt->sizebc); |
| return lj_debug_line(pt, pc); |
| } |
| return -1; |
| } |
| |
| /* -- Variable names ------------------------------------------------------ */ |
| |
| /* Read ULEB128 value. */ |
| static uint32_t debug_read_uleb128(const uint8_t **pp) |
| { |
| const uint8_t *p = *pp; |
| uint32_t v = *p++; |
| if (LJ_UNLIKELY(v >= 0x80)) { |
| int sh = 0; |
| v &= 0x7f; |
| do { v |= ((*p & 0x7f) << (sh += 7)); } while (*p++ >= 0x80); |
| } |
| *pp = p; |
| return v; |
| } |
| |
| /* Get name of a local variable from slot number and PC. */ |
| static const char *debug_varname(const GCproto *pt, BCPos pc, BCReg slot) |
| { |
| const uint8_t *p = proto_varinfo(pt); |
| if (p) { |
| BCPos lastpc = 0; |
| for (;;) { |
| const char *name = (const char *)p; |
| uint32_t vn = *p++; |
| BCPos startpc, endpc; |
| if (vn < VARNAME__MAX) { |
| if (vn == VARNAME_END) break; /* End of varinfo. */ |
| } else { |
| while (*p++) ; /* Skip over variable name string. */ |
| } |
| lastpc = startpc = lastpc + debug_read_uleb128(&p); |
| if (startpc > pc) break; |
| endpc = startpc + debug_read_uleb128(&p); |
| if (pc < endpc && slot-- == 0) { |
| if (vn < VARNAME__MAX) { |
| #define VARNAMESTR(name, str) str "\0" |
| name = VARNAMEDEF(VARNAMESTR); |
| #undef VARNAMESTR |
| if (--vn) while (*name++ || --vn) ; |
| } |
| return name; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| /* Get name of local variable from 1-based slot number and function/frame. */ |
| static TValue *debug_localname(lua_State *L, const lua_Debug *ar, |
| const char **name, BCReg slot1) |
| { |
| uint32_t offset = (uint32_t)ar->i_ci & 0xffff; |
| uint32_t size = (uint32_t)ar->i_ci >> 16; |
| TValue *frame = tvref(L->stack) + offset; |
| TValue *nextframe = size ? frame + size : NULL; |
| GCfunc *fn = frame_func(frame); |
| BCPos pc = debug_framepc(L, fn, nextframe); |
| if (!nextframe) nextframe = L->top; |
| if ((int)slot1 < 0) { /* Negative slot number is for varargs. */ |
| if (pc != NO_BCPOS) { |
| GCproto *pt = funcproto(fn); |
| if ((pt->flags & PROTO_VARARG)) { |
| slot1 = pt->numparams + (BCReg)(-(int)slot1); |
| if (frame_isvarg(frame)) { /* Vararg frame has been set up? (pc!=0) */ |
| nextframe = frame; |
| frame = frame_prevd(frame); |
| } |
| if (frame + slot1 < nextframe) { |
| *name = "(*vararg)"; |
| return frame+slot1; |
| } |
| } |
| } |
| return NULL; |
| } |
| if (pc != NO_BCPOS && |
| (*name = debug_varname(funcproto(fn), pc, slot1-1)) != NULL) |
| ; |
| else if (slot1 > 0 && frame + slot1 < nextframe) |
| *name = "(*temporary)"; |
| return frame+slot1; |
| } |
| |
| /* Get name of upvalue. */ |
| const char *lj_debug_uvname(GCproto *pt, uint32_t idx) |
| { |
| const uint8_t *p = proto_uvinfo(pt); |
| lua_assert(idx < pt->sizeuv); |
| if (!p) return ""; |
| if (idx) while (*p++ || --idx) ; |
| return (const char *)p; |
| } |
| |
| /* Get name and value of upvalue. */ |
| const char *lj_debug_uvnamev(cTValue *o, uint32_t idx, TValue **tvp) |
| { |
| if (tvisfunc(o)) { |
| GCfunc *fn = funcV(o); |
| if (isluafunc(fn)) { |
| GCproto *pt = funcproto(fn); |
| if (idx < pt->sizeuv) { |
| *tvp = uvval(&gcref(fn->l.uvptr[idx])->uv); |
| return lj_debug_uvname(pt, idx); |
| } |
| } else { |
| if (idx < fn->c.nupvalues) { |
| *tvp = &fn->c.upvalue[idx]; |
| return ""; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| /* Deduce name of an object from slot number and PC. */ |
| const char *lj_debug_slotname(GCproto *pt, const BCIns *ip, BCReg slot, |
| const char **name) |
| { |
| const char *lname; |
| restart: |
| lname = debug_varname(pt, proto_bcpos(pt, ip), slot); |
| if (lname != NULL) { *name = lname; return "local"; } |
| while (--ip > proto_bc(pt)) { |
| BCIns ins = *ip; |
| BCOp op = bc_op(ins); |
| BCReg ra = bc_a(ins); |
| if (bcmode_a(op) == BCMbase) { |
| if (slot >= ra && (op != BC_KNIL || slot <= bc_d(ins))) |
| return NULL; |
| } else if (bcmode_a(op) == BCMdst && ra == slot) { |
| switch (bc_op(ins)) { |
| case BC_MOV: |
| if (ra == slot) { slot = bc_d(ins); goto restart; } |
| break; |
| case BC_GGET: |
| *name = strdata(gco2str(proto_kgc(pt, ~(ptrdiff_t)bc_d(ins)))); |
| return "global"; |
| case BC_TGETS: |
| *name = strdata(gco2str(proto_kgc(pt, ~(ptrdiff_t)bc_c(ins)))); |
| if (ip > proto_bc(pt)) { |
| BCIns insp = ip[-1]; |
| if (bc_op(insp) == BC_MOV && bc_a(insp) == ra+1 && |
| bc_d(insp) == bc_b(ins)) |
| return "method"; |
| } |
| return "field"; |
| case BC_UGET: |
| *name = lj_debug_uvname(pt, bc_d(ins)); |
| return "upvalue"; |
| default: |
| return NULL; |
| } |
| } |
| } |
| return NULL; |
| } |
| |
| /* Deduce function name from caller of a frame. */ |
| const char *lj_debug_funcname(lua_State *L, TValue *frame, const char **name) |
| { |
| TValue *pframe; |
| GCfunc *fn; |
| BCPos pc; |
| if (frame <= tvref(L->stack)) |
| return NULL; |
| if (frame_isvarg(frame)) |
| frame = frame_prevd(frame); |
| pframe = frame_prev(frame); |
| fn = frame_func(pframe); |
| pc = debug_framepc(L, fn, frame); |
| if (pc != NO_BCPOS) { |
| GCproto *pt = funcproto(fn); |
| const BCIns *ip = &proto_bc(pt)[check_exp(pc < pt->sizebc, pc)]; |
| MMS mm = bcmode_mm(bc_op(*ip)); |
| if (mm == MM_call) { |
| BCReg slot = bc_a(*ip); |
| if (bc_op(*ip) == BC_ITERC) slot -= 3; |
| return lj_debug_slotname(pt, ip, slot, name); |
| } else if (mm != MM__MAX) { |
| *name = strdata(mmname_str(G(L), mm)); |
| return "metamethod"; |
| } |
| } |
| return NULL; |
| } |
| |
| /* -- Source code locations ----------------------------------------------- */ |
| |
| /* Generate shortened source name. */ |
| void lj_debug_shortname(char *out, GCstr *str) |
| { |
| const char *src = strdata(str); |
| if (*src == '=') { |
| strncpy(out, src+1, LUA_IDSIZE); /* Remove first char. */ |
| out[LUA_IDSIZE-1] = '\0'; /* Ensures null termination. */ |
| } else if (*src == '@') { /* Output "source", or "...source". */ |
| size_t len = str->len-1; |
| src++; /* Skip the `@' */ |
| if (len >= LUA_IDSIZE) { |
| src += len-(LUA_IDSIZE-4); /* Get last part of file name. */ |
| *out++ = '.'; *out++ = '.'; *out++ = '.'; |
| } |
| strcpy(out, src); |
| } else { /* Output [string "string"]. */ |
| size_t len; /* Length, up to first control char. */ |
| for (len = 0; len < LUA_IDSIZE-12; len++) |
| if (((const unsigned char *)src)[len] < ' ') break; |
| strcpy(out, "[string \""); out += 9; |
| if (src[len] != '\0') { /* Must truncate? */ |
| if (len > LUA_IDSIZE-15) len = LUA_IDSIZE-15; |
| strncpy(out, src, len); out += len; |
| strcpy(out, "..."); out += 3; |
| } else { |
| strcpy(out, src); out += len; |
| } |
| strcpy(out, "\"]"); |
| } |
| } |
| |
| /* Add current location of a frame to error message. */ |
| void lj_debug_addloc(lua_State *L, const char *msg, |
| cTValue *frame, cTValue *nextframe) |
| { |
| if (frame) { |
| GCfunc *fn = frame_func(frame); |
| if (isluafunc(fn)) { |
| BCLine line = debug_frameline(L, fn, nextframe); |
| if (line >= 0) { |
| char buf[LUA_IDSIZE]; |
| lj_debug_shortname(buf, proto_chunkname(funcproto(fn))); |
| lj_str_pushf(L, "%s:%d: %s", buf, line, msg); |
| return; |
| } |
| } |
| } |
| lj_str_pushf(L, "%s", msg); |
| } |
| |
| /* Push location string for a bytecode position to Lua stack. */ |
| void lj_debug_pushloc(lua_State *L, GCproto *pt, BCPos pc) |
| { |
| GCstr *name = proto_chunkname(pt); |
| const char *s = strdata(name); |
| MSize i, len = name->len; |
| BCLine line = lj_debug_line(pt, pc); |
| if (*s == '@') { |
| s++; len--; |
| for (i = len; i > 0; i--) |
| if (s[i] == '/' || s[i] == '\\') { |
| s += i+1; |
| break; |
| } |
| lj_str_pushf(L, "%s:%d", s, line); |
| } else if (len > 40) { |
| lj_str_pushf(L, "%p:%d", pt, line); |
| } else if (*s == '=') { |
| lj_str_pushf(L, "%s:%d", s+1, line); |
| } else { |
| lj_str_pushf(L, "\"%s\":%d", s, line); |
| } |
| } |
| |
| /* -- Public debug API ---------------------------------------------------- */ |
| |
| /* lua_getupvalue() and lua_setupvalue() are in lj_api.c. */ |
| |
| LUA_API const char *lua_getlocal(lua_State *L, const lua_Debug *ar, int n) |
| { |
| const char *name = NULL; |
| if (ar) { |
| TValue *o = debug_localname(L, ar, &name, (BCReg)n); |
| if (name) { |
| copyTV(L, L->top, o); |
| incr_top(L); |
| } |
| } else if (tvisfunc(L->top-1) && isluafunc(funcV(L->top-1))) { |
| name = debug_varname(funcproto(funcV(L->top-1)), 0, (BCReg)n-1); |
| } |
| return name; |
| } |
| |
| LUA_API const char *lua_setlocal(lua_State *L, const lua_Debug *ar, int n) |
| { |
| const char *name = NULL; |
| TValue *o = debug_localname(L, ar, &name, (BCReg)n); |
| if (name) |
| copyTV(L, o, L->top-1); |
| L->top--; |
| return name; |
| } |
| |
| int lj_debug_getinfo(lua_State *L, const char *what, lj_Debug *ar, int ext) |
| { |
| int opt_f = 0, opt_L = 0; |
| TValue *frame = NULL; |
| TValue *nextframe = NULL; |
| GCfunc *fn; |
| if (*what == '>') { |
| TValue *func = L->top - 1; |
| api_check(L, tvisfunc(func)); |
| fn = funcV(func); |
| L->top--; |
| what++; |
| } else { |
| uint32_t offset = (uint32_t)ar->i_ci & 0xffff; |
| uint32_t size = (uint32_t)ar->i_ci >> 16; |
| lua_assert(offset != 0); |
| frame = tvref(L->stack) + offset; |
| if (size) nextframe = frame + size; |
| lua_assert(frame <= tvref(L->maxstack) && |
| (!nextframe || nextframe <= tvref(L->maxstack))); |
| fn = frame_func(frame); |
| lua_assert(fn->c.gct == ~LJ_TFUNC); |
| } |
| for (; *what; what++) { |
| if (*what == 'S') { |
| if (isluafunc(fn)) { |
| GCproto *pt = funcproto(fn); |
| BCLine firstline = pt->firstline; |
| GCstr *name = proto_chunkname(pt); |
| ar->source = strdata(name); |
| lj_debug_shortname(ar->short_src, name); |
| ar->linedefined = (int)firstline; |
| ar->lastlinedefined = (int)(firstline + pt->numline); |
| ar->what = (firstline || !pt->numline) ? "Lua" : "main"; |
| } else { |
| ar->source = "=[C]"; |
| ar->short_src[0] = '['; |
| ar->short_src[1] = 'C'; |
| ar->short_src[2] = ']'; |
| ar->short_src[3] = '\0'; |
| ar->linedefined = -1; |
| ar->lastlinedefined = -1; |
| ar->what = "C"; |
| } |
| } else if (*what == 'l') { |
| ar->currentline = frame ? debug_frameline(L, fn, nextframe) : -1; |
| } else if (*what == 'u') { |
| ar->nups = fn->c.nupvalues; |
| if (ext) { |
| if (isluafunc(fn)) { |
| GCproto *pt = funcproto(fn); |
| ar->nparams = pt->numparams; |
| ar->isvararg = !!(pt->flags & PROTO_VARARG); |
| } else { |
| ar->nparams = 0; |
| ar->isvararg = 1; |
| } |
| } |
| } else if (*what == 'n') { |
| ar->namewhat = frame ? lj_debug_funcname(L, frame, &ar->name) : NULL; |
| if (ar->namewhat == NULL) { |
| ar->namewhat = ""; |
| ar->name = NULL; |
| } |
| } else if (*what == 'f') { |
| opt_f = 1; |
| } else if (*what == 'L') { |
| opt_L = 1; |
| } else { |
| return 0; /* Bad option. */ |
| } |
| } |
| if (opt_f) { |
| setfuncV(L, L->top, fn); |
| incr_top(L); |
| } |
| if (opt_L) { |
| if (isluafunc(fn)) { |
| GCtab *t = lj_tab_new(L, 0, 0); |
| GCproto *pt = funcproto(fn); |
| const void *lineinfo = proto_lineinfo(pt); |
| if (lineinfo) { |
| BCLine first = pt->firstline; |
| int sz = pt->numline < 256 ? 1 : pt->numline < 65536 ? 2 : 4; |
| MSize i, szl = pt->sizebc-1; |
| for (i = 0; i < szl; i++) { |
| BCLine line = first + |
| (sz == 1 ? (BCLine)((const uint8_t *)lineinfo)[i] : |
| sz == 2 ? (BCLine)((const uint16_t *)lineinfo)[i] : |
| (BCLine)((const uint32_t *)lineinfo)[i]); |
| setboolV(lj_tab_setint(L, t, line), 1); |
| } |
| } |
| settabV(L, L->top, t); |
| } else { |
| setnilV(L->top); |
| } |
| incr_top(L); |
| } |
| return 1; /* Ok. */ |
| } |
| |
| LUA_API int lua_getinfo(lua_State *L, const char *what, lua_Debug *ar) |
| { |
| return lj_debug_getinfo(L, what, (lj_Debug *)ar, 0); |
| } |
| |
| LUA_API int lua_getstack(lua_State *L, int level, lua_Debug *ar) |
| { |
| int size; |
| cTValue *frame = lj_debug_frame(L, level, &size); |
| if (frame) { |
| ar->i_ci = (size << 16) + (int)(frame - tvref(L->stack)); |
| return 1; |
| } else { |
| ar->i_ci = level - size; |
| return 0; |
| } |
| } |
| |
| /* Number of frames for the leading and trailing part of a traceback. */ |
| #define TRACEBACK_LEVELS1 12 |
| #define TRACEBACK_LEVELS2 10 |
| |
| LUALIB_API void luaL_traceback (lua_State *L, lua_State *L1, const char *msg, |
| int level) |
| { |
| int top = (int)(L->top - L->base); |
| int lim = TRACEBACK_LEVELS1; |
| lua_Debug ar; |
| if (msg) lua_pushfstring(L, "%s\n", msg); |
| lua_pushliteral(L, "stack traceback:"); |
| while (lua_getstack(L1, level++, &ar)) { |
| GCfunc *fn; |
| if (level > lim) { |
| if (!lua_getstack(L1, level + TRACEBACK_LEVELS2, &ar)) { |
| level--; |
| } else { |
| lua_pushliteral(L, "\n\t..."); |
| lua_getstack(L1, -10, &ar); |
| level = ar.i_ci - TRACEBACK_LEVELS2; |
| } |
| lim = 2147483647; |
| continue; |
| } |
| lua_getinfo(L1, "Snlf", &ar); |
| fn = funcV(L1->top-1); L1->top--; |
| if (isffunc(fn) && !*ar.namewhat) |
| lua_pushfstring(L, "\n\t[builtin#%d]:", fn->c.ffid); |
| else |
| lua_pushfstring(L, "\n\t%s:", ar.short_src); |
| if (ar.currentline > 0) |
| lua_pushfstring(L, "%d:", ar.currentline); |
| if (*ar.namewhat) { |
| lua_pushfstring(L, " in function " LUA_QS, ar.name); |
| } else { |
| if (*ar.what == 'm') { |
| lua_pushliteral(L, " in main chunk"); |
| } else if (*ar.what == 'C') { |
| lua_pushfstring(L, " at %p", fn->c.f); |
| } else { |
| lua_pushfstring(L, " in function <%s:%d>", |
| ar.short_src, ar.linedefined); |
| } |
| } |
| if ((int)(L->top - L->base) - top >= 15) |
| lua_concat(L, (int)(L->top - L->base) - top); |
| } |
| lua_concat(L, (int)(L->top - L->base) - top); |
| } |
| |