Skip to content

Commit

Permalink
Add custom logger for berry errors, enable emergency mode when a berr…
Browse files Browse the repository at this point in the history
…y error occurs during initialization

Reimplement be_dumpexcept in BerryBind, make it collect the error + stacktrace as a std::string.
Add an BerryErrorException class and throw it when execute_string fails with a berry error.
Handle BerryErrorException in Core, so that emergency mode is engaged when a berry error occurs.
  • Loading branch information
alufers committed Apr 12, 2024
1 parent d2a3de7 commit ea0e834
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 59 deletions.
199 changes: 162 additions & 37 deletions src/core/main/app/BerryBind.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#include "BerryBind.h"
#include <stdio.h>
#include <memory>
#include "EuphLogger.h"
#include "be_debug.h"

using namespace berry;

berry::moduleMap berry::moduleLambdas;


VmState::VmState(bvm* vm) : vm(vm) {}

int berryStdoutBSD(void* ctx, const char* buf, int len) {
Expand All @@ -24,9 +26,8 @@ static char stdoutLineBuffer[256] = {0};

VmState::VmState() {
#ifdef __linux__
stdoutRedirect = fopencookie(this, "w", (cookie_io_functions_t) {
.write = berryStdoutLinux
});
stdoutRedirect = fopencookie(
this, "w", (cookie_io_functions_t){.write = berryStdoutLinux});
#else
stdoutRedirect = funopen(this, NULL, &berryStdoutBSD, NULL, NULL);
#endif
Expand Down Expand Up @@ -98,52 +99,176 @@ int VmState::call(bvm* vm) {
return (*function)(l);
}

std::pair<int, std::string> VmState::debug_get_lineinfo() {
int line = -1;
std::string file;
void VmState::execute_string(const std::string& data,
const std::string& tag = "script") {
// create an array
if (be_loadbuffer(vm, tag.c_str(), data.c_str(), data.size()) == 0) {
if (be_pcall(vm, 0) != 0) {

be_save_stacktrace(vm);
bcallsnapshot* cf;
bcallsnapshot* base = (bcallsnapshot*)be_stack_base(&vm->tracestack);
bcallsnapshot* top = (bcallsnapshot*)be_stack_top(&vm->tracestack);
auto errorMessage = dumpException();

cf = top;
// decrease callstack until we find nearest closure
while (!var_isclosure(&cf->func) && cf >= base) {
cf--;
bell::bellGlobalLogger->error(tag, 0, TAG, "Berry error: %s",
errorMessage.c_str());
if (stdoutCallback != nullptr) {
stdoutCallback(errorMessage.c_str(), errorMessage.size());
stdoutCallback("\n", 1);
}
throw BerryErrorException(errorMessage);

}
} else {
auto errorMessage = dumpException();
bell::bellGlobalLogger->error(tag, 0, TAG, "Berry error: %s",
errorMessage.c_str());
if (stdoutCallback != nullptr) {
stdoutCallback(errorMessage.c_str(), errorMessage.size());
stdoutCallback("\n", 1);
}
throw BerryErrorException(errorMessage);
}
cf--;
be_pop(vm, 1);
}

// if we found a closure, get its line info and source name
if (var_isclosure(&cf->func)) {
bclosure* cl = (bclosure*)var_toobj(&cf->func);
auto proto = cl->proto;
blineinfo* it = (blineinfo*)proto->lineinfo;
/**
* @brief Native function pushed to the berry stack that a value as a string.
*
* @param vm
* @param esc
* @return int
*/
static int _dvfunc(bvm* vm) {
const char* s = be_tostring(vm, 1);
std::string* exceptionMsg = (std::string*)be_tocomptr(vm, 2);
*exceptionMsg += s;
be_return_nil(vm);
}

if (it) {
line = it->linenumber;
file = std::string(be_str2cstr(cl->proto->source));
}
int VmState::dumpValue(int index, std::string& exceptionMsg) {
int res, top = be_top(vm) + 1;
index = be_absindex(vm, index);
be_pushntvfunction(vm, _dvfunc);
be_pushvalue(vm, index);
be_pushcomptr(vm, std::addressof(exceptionMsg));
res = be_pcall(vm, 2); /* using index to store result */
be_remove(vm, top); /* remove '_dumpvalue' function */
be_remove(vm, top); /* remove the value */
be_remove(vm, top); /* remove the exception message pointer */
if (res == BE_EXCEPTION) {
be_dumpexcept(vm);
}
return res;
}

return std::make_pair(line, file);
std::string VmState::dumpException() {
std::string exceptionMsg;
do {

/* print exception value */
if (dumpValue(-2, exceptionMsg))
break;

exceptionMsg += ": ";
/* print exception argument */
if (dumpValue(-1, exceptionMsg))
break;
exceptionMsg += "\n";
/* print stack traceback */
exceptionMsg += traceStack();

} while (0);
if (exceptionMsg.ends_with("\n")) {
exceptionMsg = exceptionMsg.substr(0, exceptionMsg.size() - 1);
}
be_pop(vm, 2); /* pop the exception value & argument */
return exceptionMsg;
}

bool VmState::execute_string(const std::string& data,
const std::string& tag = "script") {
// create an array
if (be_loadbuffer(vm, tag.c_str(), data.c_str(), data.size()) == 0) {
if (be_pcall(vm, 0) != 0) {
be_dumpexcept(vm);
return false;
// Copied from be_debug.c
static inline void repair_stack(bvm* vm) {
bcallsnapshot* cf;
bcallsnapshot* base =
static_cast<bcallsnapshot*>(be_stack_base(&vm->tracestack));
bcallsnapshot* top =
static_cast<bcallsnapshot*>(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;
}
}
}

static inline std::string sourceinfo(bproto* proto, binstruction* ip) {

std::string result;
#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)
;
snprintf(buf, sizeof(buf), ":%d:", it->linenumber);
#if BE_DEBUG_SOURCE_FILE
result += be_str2cstr(proto->source);
#endif
result += buf;
} else {
be_dumpexcept(vm);
return false;
result += "<unknown source>:";
}
be_pop(vm, 1);
#else
(void)proto;
(void)ip;
result += "<unknown source>:";
#endif

return result;
}

std::string VmState::traceStack() {
std::string result;
EUPH_LOG(error, TAG, "traceStack()");
if (be_stack_count(&vm->tracestack)) {
EUPH_LOG(error, TAG, " ...OK");
repair_stack(vm);
bcallsnapshot* cf;
bcallsnapshot* base =
static_cast<bcallsnapshot*>(be_stack_base(&vm->tracestack));
bcallsnapshot* top =
static_cast<bcallsnapshot*>(be_stack_top(&vm->tracestack));
result += "stack traceback:\n";
for (cf = top; cf >= base; --cf) {
if (cf <= top - 10 && cf >= base + 10) {
if (cf == top - 10)
result += "\t...\n";
continue;
}
if (var_isclosure(&cf->func)) {
bclosure* cl = static_cast<bclosure*>(var_toobj(&cf->func));
result += "\t";
result += sourceinfo(cl->proto, cf->ip);
result += " in function `";
result += be_str2cstr(cl->proto->name);
result += "'\n";

return true;
} else {
result += "\t<native>: in native function\n";
}
}
}
return result;
}

void VmState::lambda(std::function<int(VmState&)>* function,
Expand Down
20 changes: 14 additions & 6 deletions src/core/main/app/Core.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "Core.h"
#include <memory>
#include "BerryBind.h"
#include "CoreEvents.h"
#include "EmergencyMode.h"
#include "EventBus.h"
#include "OTAHandler.h"
#include "URLParser.h"
Expand Down Expand Up @@ -115,17 +117,23 @@ void Core::initialize() {
return this->http->getServer()->makeJsonResponse(state.dump());
});

// Load system packages
this->pkgLoader->loadWithHook("system");
this->pkgLoader->loadWithHook("plugin");
try {

// Load system packages
this->pkgLoader->loadWithHook("system");
this->pkgLoader->loadWithHook("plugin");

#ifdef ESP_PLATFORM
this->pkgLoader->loadWithHook("plugin_esp32");
this->pkgLoader->loadWithHook("platform_esp32");
this->pkgLoader->loadWithHook("plugin_esp32");
this->pkgLoader->loadWithHook("platform_esp32");
#else
this->pkgLoader->loadWithHook("platform_desktop");
this->pkgLoader->loadWithHook("platform_desktop");
#endif

} catch (berry::BerryErrorException& e) {
this->ctx->emergencyMode->trip(EmergencyModeReason::BERRY_INIT_ERROR, e.what());
}

// Initialize HTTP
this->http->initialize();

Expand Down
21 changes: 17 additions & 4 deletions src/core/main/app/EmergencyMode.cpp
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
#include "EmergencyMode.h"
#include <shared_mutex>
#include <string>
#include "EuphLogger.h"

using namespace euph;

EmergencyMode::EmergencyMode() {}
EmergencyMode::EmergencyMode() {
EUPH_LOG(info, "EmergencyMode",
"std::atomic<EmergencyModeReason>::is_lock_free() = %d",
std::atomic<EmergencyModeReason>{}.is_lock_free());
}

bool EmergencyMode::isActive() const {
return this->reason != EmergencyModeReason::NOT_ACTIVE;
}

void EmergencyMode::trip(EmergencyModeReason reason) {
void EmergencyMode::trip(EmergencyModeReason reason,
const std::string& message) {
EUPH_LOG(error, "EmergencyMode", "===============================");
EUPH_LOG(error, "EmergencyMode", "Tripped emergency mode with reason: %s",
getReasonString(reason).c_str());
EUPH_LOG(error, "EmergencyMode", "===============================");
this->reason = reason;
std::unique_lock<std::shared_mutex> lock(this->messageMutex);
this->message = message;
}

bool EmergencyMode::tryServe(struct mg_connection* conn) {
if (!this->isActive()) {
return false;
}

std::shared_lock<std::shared_mutex> lock(this->messageMutex);
// Serve the emergency mode page
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Connection: close\r\n\r\n"
"Emergency mode!");
"Emergency mode!<br>\n"
"Reason: %s<br>\n"
"Message: <br><pre>%s</pre>",
getReasonString(this->reason).c_str(), this->message.c_str());

return true;
}
Expand Down
3 changes: 2 additions & 1 deletion src/core/main/app/PackageLoader.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "PackageLoader.h"
#include "BerryBind.h"

using namespace euph;

Expand Down Expand Up @@ -68,7 +69,7 @@ void PackageLoader::loadWithHook(const std::string& hook) {
std::stringstream initCodeBuffer;
initCodeBuffer << initCodeFile.rdbuf();

ctx->vm->execute_string(initCodeBuffer.str(), pkg.name);
ctx->vm->execute_string(initCodeBuffer.str(), initPath);
}
}
}
Loading

0 comments on commit ea0e834

Please sign in to comment.