/******************************************************************** ** Copyright (c) 2018-2020 Guan Wenliang ** This file is part of the Berry default interpreter. ** skiars@qq.com, https://github.com/Skiars/berry ** See Copyright Notice in the LICENSE file or at ** https://github.com/Skiars/berry/blob/master/LICENSE ********************************************************************/ #include "be_debug.h" #include "be_func.h" #include "be_decoder.h" #include "be_string.h" #include "be_class.h" #include "be_vm.h" #include "be_vector.h" #include "be_strlib.h" #include "be_exec.h" #include "be_mem.h" #include "be_sys.h" #include #include #if BE_USE_DEBUG_HOOK && !BE_DEBUG_RUNTIME_INFO #error This macro BE_USE_DEBUG_HOOK requires BE_DEBUG_RUNTIME_INFO != 0 #endif #ifndef INST_BUF_SIZE #define INST_BUF_SIZE 96 #endif #define logbuf(...) snprintf(__lbuf, sizeof(__lbuf), __VA_ARGS__) #define logfmt(...) \ do { \ char __lbuf[INST_BUF_SIZE]; \ logbuf(__VA_ARGS__); \ be_writestring(__lbuf); \ } while (0) #if BE_USE_DEBUG_MODULE static const char* opc2str(bopcode op) { static const char* const opc_tab[] = { #define OPCODE(opc) #opc #include "be_opcodes.h" #undef OPCODE }; return op < array_count(opc_tab) ? opc_tab[op] : "ERROP"; } void be_print_inst(binstruction ins, int pc, void* fout) { char __lbuf[INST_BUF_SIZE]; bopcode op = IGET_OP(ins); logbuf(" %.4X ", pc); if (fout) { be_fwrite(fout, __lbuf, strlen(__lbuf)); } else { be_writestring(__lbuf); } switch (op) { case OP_ADD: case OP_SUB: case OP_MUL: case OP_DIV: case OP_MOD: case OP_LT: case OP_LE: case OP_EQ: case OP_NE: case OP_GT: case OP_GE: case OP_CONNECT: case OP_GETMBR: case OP_SETMBR: case OP_GETMET: case OP_GETIDX: case OP_SETIDX: case OP_AND: case OP_OR: case OP_XOR: case OP_SHL: case OP_SHR: logbuf("%s\tR%d\t%c%d\t%c%d", opc2str(op), IGET_RA(ins), isKB(ins) ? 'K' : 'R', IGET_RKB(ins) & KR_MASK, isKC(ins) ? 'K' : 'R', IGET_RKC(ins) & KR_MASK); break; case OP_GETNGBL: case OP_SETNGBL: logbuf("%s\tR%d\t%c%d", opc2str(op), IGET_RA(ins), isKB(ins) ? 'K' : 'R', IGET_RKB(ins) & KR_MASK); break; case OP_GETGBL: case OP_SETGBL: logbuf("%s\tR%d\tG%d", opc2str(op), IGET_RA(ins), IGET_Bx(ins)); break; case OP_MOVE: case OP_SETSUPER: case OP_NEG: case OP_FLIP: case OP_IMPORT: logbuf("%s\tR%d\t%c%d", opc2str(op), IGET_RA(ins), isKB(ins) ? 'K' : 'R', IGET_RKB(ins) & KR_MASK); break; case OP_JMP: logbuf("%s\t\t#%.4X", opc2str(op), IGET_sBx(ins) + pc + 1); break; case OP_JMPT: case OP_JMPF: logbuf("%s\tR%d\t#%.4X", opc2str(op), IGET_RA(ins), IGET_sBx(ins) + pc + 1); break; case OP_LDINT: logbuf("%s\tR%d\t%d", opc2str(op), IGET_RA(ins), IGET_sBx(ins)); break; case OP_LDBOOL: logbuf("%s\tR%d\t%d\t%d", opc2str(op), IGET_RA(ins), IGET_RKB(ins), IGET_RKC(ins)); break; case OP_RET: if (IGET_RA(ins)) { logbuf("%s\t%d\t%c%d", opc2str(op), IGET_RA(ins), isKB(ins) ? 'K' : 'R', IGET_RKB(ins) & KR_MASK); } else { logbuf("%s\t%d", opc2str(op), IGET_RA(ins)); /* RET 0 does not take an additional parameter */ } break; case OP_GETUPV: case OP_SETUPV: logbuf("%s\tR%d\tU%d", opc2str(op), IGET_RA(ins), IGET_Bx(ins)); break; case OP_LDCONST: logbuf("%s\tR%d\tK%d", opc2str(op), IGET_RA(ins), IGET_Bx(ins)); break; case OP_CALL: logbuf("%s\tR%d\t%d", opc2str(op), IGET_RA(ins), IGET_RKB(ins)); break; case OP_CLOSURE: logbuf("%s\tR%d\tP%d", opc2str(op), IGET_RA(ins), IGET_Bx(ins)); break; case OP_CLASS: logbuf("%s\tK%d", opc2str(op), IGET_Bx(ins)); break; case OP_CLOSE: case OP_LDNIL: logbuf("%s\tR%d", opc2str(op), IGET_RA(ins)); break; case OP_RAISE: logbuf("%s\t%d\t%c%d\t%c%d", opc2str(op), IGET_RA(ins), isKB(ins) ? 'K' : 'R', IGET_RKB(ins) & KR_MASK, isKC(ins) ? 'K' : 'R', IGET_RKC(ins) & KR_MASK); break; case OP_EXBLK: if (IGET_RA(ins)) { logbuf("%s\t%d\t%d", opc2str(op), IGET_RA(ins), IGET_Bx(ins)); } else { logbuf("%s\t%d\t#%.4X", opc2str(op), IGET_RA(ins), IGET_sBx(ins) + pc + 1); } break; case OP_CATCH: logbuf("%s\tR%d\t%d\t%d", opc2str(op), IGET_RA(ins), IGET_RKB(ins), IGET_RKC(ins)); break; default: logbuf("%s", opc2str(op)); break; } logbuf("%s\n", __lbuf); if (fout) { be_fwrite(fout, __lbuf, strlen(__lbuf)); } else { be_writestring(__lbuf); } } #endif #if BE_USE_DEBUG_MODULE void be_dumpclosure(bclosure *cl) { int pc; bproto *proto = cl->proto; binstruction *code = proto->code; #if BE_DEBUG_RUNTIME_INFO blineinfo *lineinfo = proto->lineinfo; #endif logfmt("source '%s', ", str(proto->source)); logfmt("function '%s':\n", str(proto->name)); #if BE_DEBUG_RUNTIME_INFO if (lineinfo) { /* first line */ logfmt("; line %d\n", lineinfo->linenumber); } #endif for (pc = 0; pc < proto->codesize; pc++) { #if BE_DEBUG_RUNTIME_INFO if (lineinfo && pc > lineinfo->endpc) { logfmt("; line %d\n", (++lineinfo)->linenumber); } #endif be_print_inst(*code++, pc, NULL); } } #endif static void sourceinfo(bproto *proto, binstruction *ip) { #if BE_DEBUG_RUNTIME_INFO char buf[24]; be_assert(proto != NULL); if (proto->lineinfo && proto->nlineinfo) { blineinfo *it = proto->lineinfo; blineinfo *end = it + proto->nlineinfo; int pc = cast_int(ip - proto->code - 1); /* now vm->ip has been increased */ for (; it < end && pc > it->endpc; ++it); sprintf(buf, ":%d:", it->linenumber); be_writestring(str(proto->source)); be_writestring(buf); } else { be_writestring(":"); } #else (void)proto; (void)ip; be_writestring(":"); #endif } static void tracestack(bvm *vm) { bcallsnapshot *cf; bcallsnapshot *base = be_stack_base(&vm->tracestack); bcallsnapshot *top = be_stack_top(&vm->tracestack); be_writestring("stack traceback:\n"); for (cf = top; cf >= base; --cf) { if (cf <= top - 10 && cf >= base + 10) { if (cf == top - 10) be_writestring("\t...\n"); continue; } if (var_isclosure(&cf->func)) { bclosure *cl = var_toobj(&cf->func); be_writestring("\t"); sourceinfo(cl->proto, cf->ip); be_writestring(" in function `"); be_writestring(str(cl->proto->name)); be_writestring("`\n"); } else { be_writestring("\t: in native function\n"); } } } static void repair_stack(bvm *vm) { bcallsnapshot *cf; bcallsnapshot *base = be_stack_base(&vm->tracestack); bcallsnapshot *top = be_stack_top(&vm->tracestack); /* Because the native function does not push `ip` to the * stack, the ip on the native function frame corresponds * to the previous Berry closure. */ for (cf = top; cf >= base; --cf) { if (!var_isclosure(&cf->func)) { /* the last native function stack frame has the `ip` of * the previous Berry frame */ binstruction *ip = cf->ip; /* skip native function stack frames */ for (; cf >= base && !var_isclosure(&cf->func); --cf); /* fixed `ip` of Berry closure frame near native function frame */ if (cf >= base) cf->ip = ip; } } } void be_tracestack(bvm *vm) { if (be_stack_count(&vm->tracestack)) { repair_stack(vm); tracestack(vm); } } #if BE_USE_DEBUG_HOOK static void hook_callnative(bvm *vm, int mask) { bhookinfo info; int top = be_top(vm); bcallframe *cf = vm->cf; bclosure *cl = var_toobj(cf->func); struct bhookblock *hb = var_toobj(&vm->hook); be_stack_require(vm, BE_STACK_FREE_MIN + 2); info.type = mask; info.line = cf->lineinfo->linenumber; info.source = str(cl->proto->source); info.func_name = str(cl->proto->name); info.data = hb->data; hb->hook(vm, &info); vm->top += 2; be_pop(vm, be_top(vm) - top); } static int hook_pushargs(bvm *vm, int mask) { bcallframe *cf = vm->cf; if (mask == BE_HOOK_LINE) { be_pushstring(vm, "line"); be_pushint(vm, cf->lineinfo->linenumber); return 2; } if (mask == BE_HOOK_CALL) { bclosure *cl = var_toobj(cf->func); be_pushstring(vm, "call"); var_setstr(vm->top, cl->proto->name); vm->top++; return 2; } if (mask == BE_HOOK_RET) { be_pushstring(vm, "return"); return 1; } return 0; } static void hook_call(bvm *vm, int mask) { int argc; vm->top[2] = vm->hook; be_stack_require(vm, 5); vm->top += 3; argc = hook_pushargs(vm, mask); be_call(vm, argc); be_pop(vm, 3 + argc); } void be_callhook(bvm *vm, int mask) { if (vm->hookmask & mask) { int hookmask = vm->hookmask; vm->hookmask = 0; if (var_istype(&vm->hook, BE_COMPTR)) { hook_callnative(vm, mask); } else { hook_call(vm, mask); } vm->hookmask = (bbyte)hookmask; } } static bbyte parse_hookmask(const char *mask) { int c, res = 0; if (mask) { while ((c = *mask++) != '\0') { switch (c) { case 'l': res |= BE_HOOK_LINE; break; case 'c': res |= BE_HOOK_CALL; break; case 'r': res |= BE_HOOK_RET; break; default: break; } } } return (bbyte)res; } BERRY_API void be_sethook(bvm *vm, const char *mask) { vm->hookmask = parse_hookmask(mask); if (vm->hookmask && var_istype(&vm->hook, BE_COMPTR)) /* free native hook */ be_free(vm, var_toobj(&vm->hook), sizeof(struct bhookblock)); if (vm->hookmask) { vm->hook = *be_indexof(vm, -1); } else if (!var_istype(&vm->hook, BE_COMPTR)) { var_setnil(&vm->hook); } } BERRY_API void be_setntvhook(bvm *vm, bntvhook hook, void *data, int mask) { struct bhookblock *hb; if (mask) { if (!var_istype(&vm->hook, BE_COMPTR)) { var_setobj(&vm->hook, BE_COMPTR, be_malloc(vm, sizeof(struct bhookblock))); } hb = var_toobj(&vm->hook); be_assert(hb != NULL); hb->hook = hook; hb->data = data; } else if (!var_istype(&vm->hook, BE_COMPTR)) { var_setnil(&vm->hook); } vm->hookmask = (bbyte)mask; } #endif #if BE_DEBUG_VAR_INFO static binstruction* callstack_fixip(bvm *vm, int level) { bcallframe *top = (bcallframe*)be_stack_top(&vm->callstack); bcallframe *cf = top - level + 2; for (; cf <= top && cf->status & PRIM_FUNC; ++cf); return cf <= top ? cf->ip : vm->ip; } bbool be_debug_varname(bvm *vm, int level, int index) { int depth = be_stack_count(&vm->callstack); if (level > 0 && level <= depth) { bcallframe *cf = be_vector_at(&vm->callstack, depth - level); if ((cf->status & PRIM_FUNC) == 0) { bproto *proto = cast(bclosure*, var_toobj(cf->func))->proto; int pc = (int)(callstack_fixip(vm, level) - proto->code); bstring *name = be_func_varname(proto, index, pc); if (name) { bvalue *reg = be_incrtop(vm); var_setstr(reg, name); return btrue; } } } return bfalse; } bbool be_debug_upvname(bvm *vm, int level, int index) { int depth = be_stack_count(&vm->callstack); if (level > 0 && level <= depth) { bcallframe *cf = be_vector_at(&vm->callstack, depth - level); if ((cf->status & PRIM_FUNC) == 0) { bproto *proto = cast(bclosure*, var_toobj(cf->func))->proto; if (index >= 0 && index < proto->nupvals) { bvalue *reg = be_incrtop(vm); var_setstr(reg, proto->upvals[index].name); return btrue; } } } return bfalse; } #endif