mirror of
https://github.com/arendst/Tasmota.git
synced 2025-04-25 23:37:16 +00:00
412 lines
12 KiB
C
412 lines
12 KiB
C
/********************************************************************
|
|
** 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 <stdio.h>
|
|
#include <string.h>
|
|
|
|
#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("<unknown source>:");
|
|
}
|
|
#else
|
|
(void)proto; (void)ip;
|
|
be_writestring("<unknown source>:");
|
|
#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<native>: 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
|