crash vm trace
This commit is contained in:
@@ -28,6 +28,7 @@ static int run_test_suite(size_t heap_size);
|
||||
|
||||
cell_rt *root_cell = NULL;
|
||||
static char *shop_path = NULL;
|
||||
volatile JSContext *g_crash_ctx = NULL;
|
||||
static char *core_path = NULL;
|
||||
static int native_mode = 0;
|
||||
static int warn_mode = 1;
|
||||
@@ -408,6 +409,13 @@ static void signal_handler(int sig)
|
||||
#endif
|
||||
if (!str) return;
|
||||
|
||||
/* Reset handler to default so a double-fault terminates immediately */
|
||||
signal(sig, SIG_DFL);
|
||||
|
||||
/* Try to print the JS stack (best-effort, signal-safe) */
|
||||
if (g_crash_ctx)
|
||||
JS_CrashPrintStack((JSContext *)g_crash_ctx);
|
||||
|
||||
exit_handler();
|
||||
}
|
||||
|
||||
@@ -689,7 +697,9 @@ int cell_init(int argc, char **argv)
|
||||
JS_DeleteGCRef(ctx, &args_ref);
|
||||
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
|
||||
|
||||
g_crash_ctx = ctx;
|
||||
JSValue result = JS_RunMachBin(ctx, (const uint8_t *)bin_data, bin_size, hidden_env);
|
||||
g_crash_ctx = NULL;
|
||||
JS_DeleteGCRef(ctx, &env_ref);
|
||||
free(bin_data);
|
||||
|
||||
|
||||
@@ -76,6 +76,10 @@ typedef struct cell_rt {
|
||||
cell_hook trace_hook;
|
||||
} cell_rt;
|
||||
|
||||
/* Set by actor_turn/CLI before entering the VM, cleared after.
|
||||
Read by signal_handler to print JS stack on crash. */
|
||||
extern volatile JSContext *g_crash_ctx;
|
||||
|
||||
cell_rt *create_actor(void *wota);
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||
void actor_disrupt(cell_rt *actor);
|
||||
|
||||
@@ -1054,6 +1054,11 @@ MachCode *mach_compile_mcode(struct cJSON *mcode_json);
|
||||
Does NOT clear reg_current_frame — caller is responsible if needed. */
|
||||
JSValue JS_GetStack (JSContext *ctx);
|
||||
|
||||
/* Crash-safe stack printer: walks JS frames and writes to stderr.
|
||||
Uses only write() (async-signal-safe). No GC allocations.
|
||||
Safe to call from signal handlers. */
|
||||
void JS_CrashPrintStack(JSContext *ctx);
|
||||
|
||||
#undef js_unlikely
|
||||
#undef inline
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#define BLOB_IMPLEMENTATION
|
||||
#include "quickjs-internal.h"
|
||||
#include <unistd.h>
|
||||
|
||||
// #define DUMP_BUDDY
|
||||
#ifdef DUMP_BUDDY
|
||||
@@ -11640,10 +11641,10 @@ JSValue JS_GetStack(JSContext *ctx) {
|
||||
JSGCRef item_ref;
|
||||
JS_PushGCRef(ctx, &item_ref);
|
||||
item_ref.val = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "fn",
|
||||
JS_NewString(ctx, frames[i].fn ? frames[i].fn : "<anonymous>"));
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "file",
|
||||
JS_NewString(ctx, frames[i].file ? frames[i].file : "<unknown>"));
|
||||
JSValue fn_str = JS_NewString(ctx, frames[i].fn ? frames[i].fn : "<anonymous>");
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "fn", fn_str);
|
||||
JSValue file_str = JS_NewString(ctx, frames[i].file ? frames[i].file : "<unknown>");
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "file", file_str);
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "line", JS_NewInt32(ctx, frames[i].line));
|
||||
JS_SetPropertyStr(ctx, item_ref.val, "col", JS_NewInt32(ctx, frames[i].col));
|
||||
JS_SetPropertyNumber(ctx, arr_ref.val, i, item_ref.val);
|
||||
@@ -11653,3 +11654,65 @@ JSValue JS_GetStack(JSContext *ctx) {
|
||||
JS_PopGCRef(ctx, &arr_ref);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Crash-safe stack printer: walks JS frames and writes to stderr
|
||||
using only write() (async-signal-safe). No GC allocations.
|
||||
Best-effort: if the heap is corrupted, we may double-fault. */
|
||||
void JS_CrashPrintStack(JSContext *ctx) {
|
||||
if (!ctx) return;
|
||||
if (JS_IsNull(ctx->reg_current_frame)) return;
|
||||
|
||||
static const char hdr[] = "\n--- JS Stack at crash ---\n";
|
||||
write(STDERR_FILENO, hdr, sizeof(hdr) - 1);
|
||||
|
||||
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);
|
||||
uint32_t cur_pc = ctx->current_register_pc;
|
||||
int is_first = 1;
|
||||
int depth = 0;
|
||||
|
||||
while (frame && depth < 32) {
|
||||
if (!JS_IsFunction(frame->function)) break;
|
||||
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
|
||||
|
||||
if (fn->kind == JS_FUNC_KIND_REGISTER && JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code) {
|
||||
JSCodeRegister *code = JS_VALUE_GET_CODE(fn->u.cell.code)->u.reg.code;
|
||||
uint32_t pc = is_first ? cur_pc : (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
|
||||
|
||||
const char *name = code->name_cstr ? code->name_cstr : "<anonymous>";
|
||||
const char *file = code->filename_cstr ? code->filename_cstr : "<unknown>";
|
||||
int line = 0;
|
||||
if (code->line_table && pc < code->instr_count)
|
||||
line = code->line_table[pc].line;
|
||||
|
||||
/* Format: " at name (file:line)\n" using only write() */
|
||||
char buf[512];
|
||||
int pos = 0;
|
||||
buf[pos++] = ' '; buf[pos++] = ' '; buf[pos++] = ' '; buf[pos++] = ' ';
|
||||
buf[pos++] = 'a'; buf[pos++] = 't'; buf[pos++] = ' ';
|
||||
for (const char *p = name; *p && pos < 200; p++) buf[pos++] = *p;
|
||||
buf[pos++] = ' '; buf[pos++] = '(';
|
||||
for (const char *p = file; *p && pos < 440; p++) buf[pos++] = *p;
|
||||
buf[pos++] = ':';
|
||||
/* itoa for line number */
|
||||
if (line == 0) {
|
||||
buf[pos++] = '0';
|
||||
} else {
|
||||
char digits[12];
|
||||
int d = 0;
|
||||
int n = line;
|
||||
while (n > 0) { digits[d++] = '0' + (n % 10); n /= 10; }
|
||||
for (int i = d - 1; i >= 0; i--) buf[pos++] = digits[i];
|
||||
}
|
||||
buf[pos++] = ')'; buf[pos++] = '\n';
|
||||
write(STDERR_FILENO, buf, pos);
|
||||
}
|
||||
|
||||
if (JS_IsNull(frame->caller)) break;
|
||||
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
|
||||
is_first = 0;
|
||||
depth++;
|
||||
}
|
||||
|
||||
static const char ftr[] = "--- End JS Stack ---\n";
|
||||
write(STDERR_FILENO, ftr, sizeof(ftr) - 1);
|
||||
}
|
||||
|
||||
@@ -716,6 +716,7 @@ void actor_turn(cell_rt *actor)
|
||||
|
||||
if (actor->vm_suspended) {
|
||||
/* RESUME path: continue suspended turn under kill timer only */
|
||||
g_crash_ctx = actor->context;
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
JS_SetPauseFlag(actor->context, 0);
|
||||
actor->turn_start_ns = cell_ns();
|
||||
@@ -783,6 +784,8 @@ void actor_turn(cell_rt *actor)
|
||||
pthread_cond_signal(&engine.timer_cond);
|
||||
pthread_mutex_unlock(&engine.lock);
|
||||
|
||||
g_crash_ctx = actor->context;
|
||||
|
||||
if (l.type == LETTER_BLOB) {
|
||||
size_t size = blob_length(l.blob_data) / 8;
|
||||
JSValue arg = js_new_blob_stoned_copy(actor->context,
|
||||
@@ -816,6 +819,7 @@ void actor_turn(cell_rt *actor)
|
||||
actor->slow_strikes = 0; /* completed within fast timer */
|
||||
|
||||
ENDTURN:
|
||||
g_crash_ctx = NULL;
|
||||
/* Invalidate any outstanding pause/kill timers for this turn */
|
||||
atomic_fetch_add_explicit(&actor->turn_gen, 1, memory_order_relaxed);
|
||||
actor->state = ACTOR_IDLE;
|
||||
@@ -842,6 +846,7 @@ ENDTURN:
|
||||
return;
|
||||
|
||||
ENDTURN_SLOW:
|
||||
g_crash_ctx = NULL;
|
||||
#ifdef ACTOR_TRACE
|
||||
fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n",
|
||||
actor->name ? actor->name : actor->id);
|
||||
|
||||
Reference in New Issue
Block a user