Skip to content

Commit 011170a

Browse files
Mike PallBuristan
authored andcommitted
Run VM events and finalizers in separate state.
Reported by Sergey Kaplun. (cherry picked from commit 5c64775) If `lj_state_checkstack()` in the `lj_vmevent_prepare()` raises the stack overflow error, `J->cur.startpc` is uninitialized during the recording, and its further inspection during trace blacklisting leads to the crash. To prevent any further inconsistencies in the JIT state due to the VM event handlers, all these handlers run in the separate `lua_State` (named V), which is stored in the `global_State`. Sergey Kaplun: * added the description and the test for the problem Part of tarantool/tarantool#12134
1 parent bdd5908 commit 011170a

File tree

8 files changed

+123
-67
lines changed

8 files changed

+123
-67
lines changed

src/lj_gc.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ static void gc_mark_start(global_State *g)
9797
setgcrefnull(g->gc.weak);
9898
gc_markobj(g, mainthread(g));
9999
gc_markobj(g, tabref(mainthread(g)->env));
100+
gc_markobj(g, vmthread(g));
100101
gc_marktv(g, &g->registrytv);
101102
gc_mark_gcroot(g);
102103
g->gc.state = GCSpropagate;
@@ -501,24 +502,25 @@ static void gc_call_finalizer(global_State *g, lua_State *L,
501502
uint8_t oldh = hook_save(g);
502503
GCSize oldt = g->gc.threshold;
503504
int errcode;
505+
lua_State *VL = vmthread(g);
504506
TValue *top;
505507
lj_trace_abort(g);
506508
hook_entergc(g); /* Disable hooks and new traces during __gc. */
507509
if (LJ_HASPROFILE && (oldh & HOOK_PROFILE)) lj_dispatch_update(g);
508510
g->gc.threshold = LJ_MAX_MEM; /* Prevent GC steps. */
509-
top = L->top;
510-
copyTV(L, top++, mo);
511+
top = VL->top;
512+
copyTV(VL, top++, mo);
511513
if (LJ_FR2) setnilV(top++);
512-
setgcV(L, top, o, ~o->gch.gct);
513-
L->top = top+1;
514-
errcode = lj_vm_pcall(L, top, 1+0, -1); /* Stack: |mo|o| -> | */
514+
setgcV(VL, top, o, ~o->gch.gct);
515+
VL->top = top+1;
516+
errcode = lj_vm_pcall(VL, top, 1+0, -1); /* Stack: |mo|o| -> | */
517+
setgcref(g->cur_L, obj2gco(L));
515518
hook_restore(g, oldh);
516519
if (LJ_HASPROFILE && (oldh & HOOK_PROFILE)) lj_dispatch_update(g);
517520
g->gc.threshold = oldt; /* Restore GC threshold. */
518521
if (errcode) {
519-
ptrdiff_t errobj = savestack(L, L->top-1); /* Stack may be resized. */
520-
lj_vmevent_send(L, ERRFIN,
521-
copyTV(L, L->top++, restorestack(L, errobj));
522+
lj_vmevent_send(g, ERRFIN,
523+
copyTV(V, V->top++, L->top-1);
522524
);
523525
L->top--;
524526
}

src/lj_obj.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ typedef struct global_State {
672672
GCupval uvhead; /* Head of double-linked list of all open upvalues. */
673673
volatile int32_t vmstate; /* VM state or current JIT code trace number. */
674674
int32_t hookcstart; /* Start count for instruction hook counter. */
675+
GCRef vmthref; /* Link to VM thread. */
675676
lua_Hook hookf; /* Hook function. */
676677
lua_CFunction wrapf; /* Wrapper for C function calls. */
677678
lua_CFunction panic; /* Called as a last resort for errors. */
@@ -688,6 +689,7 @@ typedef struct global_State {
688689
} global_State;
689690

690691
#define mainthread(g) (&gcref(g->mainthref)->th)
692+
#define vmthread(g) (&gcref(g->vmthref)->th)
691693
#define niltv(L) \
692694
check_exp(tvisnil(&G(L)->nilnode.val), &G(L)->nilnode.val)
693695
#define niltvg(g) \

src/lj_parse.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1595,8 +1595,8 @@ static GCproto *fs_finish(LexState *ls, BCLine line)
15951595
fs_fixup_line(fs, pt, (void *)((char *)pt + ofsli), numline);
15961596
fs_fixup_var(ls, pt, (uint8_t *)((char *)pt + ofsdbg), ofsvar);
15971597

1598-
lj_vmevent_send(L, BC,
1599-
setprotoV(L, L->top++, pt);
1598+
lj_vmevent_send(G(L), BC,
1599+
setprotoV(V, V->top++, pt);
16001600
);
16011601

16021602
/* Add a new prototype to the profiler. */

src/lj_state.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ static TValue *cpluaopen(lua_State *L, lua_CFunction dummy, void *ud)
208208
#endif
209209
lj_trace_initstate(g);
210210
lj_err_verify();
211+
setgcref(g->vmthref, obj2gco(lj_state_new(L)));
211212
return NULL;
212213
}
213214

src/lj_trace.c

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,8 @@ int lj_trace_flushall(lua_State *L)
309309
/* Free the whole machine code and invalidate all exit stub groups. */
310310
lj_mcode_free(J);
311311
memset(J->exitstubgroup, 0, sizeof(J->exitstubgroup));
312-
lj_vmevent_send(L, TRACE,
313-
setstrV(L, L->top++, lj_str_newlit(L, "flush"));
312+
lj_vmevent_send(J2G(J), TRACE,
313+
setstrV(V, V->top++, lj_str_newlit(V, "flush"));
314314
);
315315
return 0;
316316
}
@@ -416,7 +416,6 @@ static void penalty_pc(jit_State *J, GCproto *pt, BCIns *pc, TraceError e)
416416
/* Start tracing. */
417417
static void trace_start(jit_State *J)
418418
{
419-
lua_State *L;
420419
TraceNo traceno;
421420

422421
if ((J->pt->flags & PROTO_NOJIT)) { /* JIT disabled for this proto? */
@@ -460,20 +459,19 @@ static void trace_start(jit_State *J)
460459
J->ktrace = 0;
461460
setgcref(J->cur.startpt, obj2gco(J->pt));
462461

463-
L = J->L;
464-
lj_vmevent_send(L, TRACE,
465-
setstrV(L, L->top++, lj_str_newlit(L, "start"));
466-
setintV(L->top++, traceno);
467-
setfuncV(L, L->top++, J->fn);
468-
setintV(L->top++, proto_bcpos(J->pt, J->pc));
462+
lj_vmevent_send(J2G(J), TRACE,
463+
setstrV(V, V->top++, lj_str_newlit(V, "start"));
464+
setintV(V->top++, traceno);
465+
setfuncV(V, V->top++, J->fn);
466+
setintV(V->top++, proto_bcpos(J->pt, J->pc));
469467
if (J->parent) {
470-
setintV(L->top++, J->parent);
471-
setintV(L->top++, J->exitno);
468+
setintV(V->top++, J->parent);
469+
setintV(V->top++, J->exitno);
472470
} else {
473471
BCOp op = bc_op(*J->pc);
474472
if (op == BC_CALLM || op == BC_CALL || op == BC_ITERC) {
475-
setintV(L->top++, J->exitno); /* Parent of stitched trace. */
476-
setintV(L->top++, -1);
473+
setintV(V->top++, J->exitno); /* Parent of stitched trace. */
474+
setintV(V->top++, -1);
477475
}
478476
}
479477
);
@@ -488,7 +486,6 @@ static void trace_stop(jit_State *J)
488486
GCproto *pt = &gcref(J->cur.startpt)->pt;
489487
TraceNo traceno = J->cur.traceno;
490488
GCtrace *T = J->curfinal;
491-
lua_State *L;
492489

493490
switch (op) {
494491
case BC_FORL:
@@ -544,11 +541,10 @@ static void trace_stop(jit_State *J)
544541
J->postproc = LJ_POST_NONE;
545542
trace_save(J, T);
546543

547-
L = J->L;
548-
lj_vmevent_send(L, TRACE,
549-
setstrV(L, L->top++, lj_str_newlit(L, "stop"));
550-
setintV(L->top++, traceno);
551-
setfuncV(L, L->top++, J->fn);
544+
lj_vmevent_send(J2G(J), TRACE,
545+
setstrV(V, V->top++, lj_str_newlit(V, "stop"));
546+
setintV(V->top++, traceno);
547+
setfuncV(V, V->top++, J->fn);
552548
);
553549
}
554550

@@ -605,18 +601,17 @@ static int trace_abort(jit_State *J)
605601
/* Is there anything to abort? */
606602
traceno = J->cur.traceno;
607603
if (traceno) {
608-
ptrdiff_t errobj = savestack(L, L->top-1); /* Stack may be resized. */
609604
J->cur.link = 0;
610605
J->cur.linktype = LJ_TRLINK_NONE;
611-
lj_vmevent_send(L, TRACE,
606+
lj_vmevent_send(J2G(J), TRACE,
612607
cTValue *bot = tvref(L->stack)+LJ_FR2;
613608
cTValue *frame;
614609
const BCIns *pc;
615610
BCPos pos = 0;
616-
setstrV(L, L->top++, lj_str_newlit(L, "abort"));
617-
setintV(L->top++, traceno);
611+
setstrV(V, V->top++, lj_str_newlit(V, "abort"));
612+
setintV(V->top++, traceno);
618613
/* Find original Lua function call to generate a better error message. */
619-
for (frame = J->L->base-1, pc = J->pc; ; frame = frame_prev(frame)) {
614+
for (frame = L->base-1, pc = J->pc; ; frame = frame_prev(frame)) {
620615
if (isluafunc(frame_func(frame))) {
621616
pos = proto_bcpos(funcproto(frame_func(frame)), pc);
622617
break;
@@ -628,10 +623,10 @@ static int trace_abort(jit_State *J)
628623
pc = frame_pc(frame) - 1;
629624
}
630625
}
631-
setfuncV(L, L->top++, frame_func(frame));
632-
setintV(L->top++, pos);
633-
copyTV(L, L->top++, restorestack(L, errobj));
634-
copyTV(L, L->top++, &J->errinfo);
626+
setfuncV(V, V->top++, frame_func(frame));
627+
setintV(V->top++, pos);
628+
copyTV(V, V->top++, L->top-1);
629+
copyTV(V, V->top++, &J->errinfo);
635630
);
636631
/* Drop aborted trace after the vmevent (which may still access it). */
637632
setgcrefnull(J->trace[traceno]);
@@ -678,16 +673,16 @@ static TValue *trace_state(lua_State *L, lua_CFunction dummy, void *ud)
678673
case LJ_TRACE_RECORD:
679674
trace_pendpatch(J, 0);
680675
setvmstate(J2G(J), RECORD);
681-
lj_vmevent_send_(L, RECORD,
676+
lj_vmevent_send_(J2G(J), RECORD,
682677
/* Save/restore state for trace recorder. */
683678
TValue savetv = J2G(J)->tmptv;
684679
TValue savetv2 = J2G(J)->tmptv2;
685680
TraceNo parent = J->parent;
686681
ExitNo exitno = J->exitno;
687-
setintV(L->top++, J->cur.traceno);
688-
setfuncV(L, L->top++, J->fn);
689-
setintV(L->top++, J->pt ? (int32_t)proto_bcpos(J->pt, J->pc) : -1);
690-
setintV(L->top++, J->framedepth);
682+
setintV(V->top++, J->cur.traceno);
683+
setfuncV(V, V->top++, J->fn);
684+
setintV(V->top++, J->pt ? (int32_t)proto_bcpos(J->pt, J->pc) : -1);
685+
setintV(V->top++, J->framedepth);
691686
,
692687
J2G(J)->tmptv = savetv;
693688
J2G(J)->tmptv2 = savetv2;
@@ -837,23 +832,23 @@ static TValue *trace_exit_gc_cp(lua_State *L, lua_CFunction dummy, void *unused)
837832

838833
#ifndef LUAJIT_DISABLE_VMEVENT
839834
/* Push all registers from exit state. */
840-
static void trace_exit_regs(lua_State *L, ExitState *ex)
835+
static void trace_exit_regs(lua_State *V, ExitState *ex)
841836
{
842837
int32_t i;
843-
setintV(L->top++, RID_NUM_GPR);
844-
setintV(L->top++, RID_NUM_FPR);
838+
setintV(V->top++, RID_NUM_GPR);
839+
setintV(V->top++, RID_NUM_FPR);
845840
for (i = 0; i < RID_NUM_GPR; i++) {
846841
if (sizeof(ex->gpr[i]) == sizeof(int32_t))
847-
setintV(L->top++, (int32_t)ex->gpr[i]);
842+
setintV(V->top++, (int32_t)ex->gpr[i]);
848843
else
849-
setnumV(L->top++, (lua_Number)ex->gpr[i]);
844+
setnumV(V->top++, (lua_Number)ex->gpr[i]);
850845
}
851846
#if !LJ_SOFTFP
852847
for (i = 0; i < RID_NUM_FPR; i++) {
853-
setnumV(L->top, ex->fpr[i]);
854-
if (LJ_UNLIKELY(tvisnan(L->top)))
855-
setnanV(L->top);
856-
L->top++;
848+
setnumV(V->top, ex->fpr[i]);
849+
if (LJ_UNLIKELY(tvisnan(V->top)))
850+
setnanV(V->top);
851+
V->top++;
857852
}
858853
#endif
859854
}
@@ -895,6 +890,8 @@ int LJ_FASTCALL lj_trace_exit(jit_State *J, void *exptr)
895890

896891
#ifdef EXITSTATE_PCREG
897892
J->parent = trace_exit_find(J, (MCode *)(intptr_t)ex->gpr[EXITSTATE_PCREG]);
893+
#else
894+
UNUSED(ex);
898895
#endif
899896
T = traceref(J, J->parent); UNUSED(T);
900897
#ifdef EXITSTATE_CHECKEXIT
@@ -915,11 +912,11 @@ int LJ_FASTCALL lj_trace_exit(jit_State *J, void *exptr)
915912
if (exitcode) copyTV(L, L->top++, &exiterr); /* Anchor the error object. */
916913

917914
if (!(LJ_HASPROFILE && (G(L)->hookmask & HOOK_PROFILE)))
918-
lj_vmevent_send(L, TEXIT,
919-
lj_state_checkstack(L, 4+RID_NUM_GPR+RID_NUM_FPR+LUA_MINSTACK);
920-
setintV(L->top++, J->parent);
921-
setintV(L->top++, J->exitno);
922-
trace_exit_regs(L, ex);
915+
lj_vmevent_send(G(L), TEXIT,
916+
lj_state_checkstack(V, 4+RID_NUM_GPR+RID_NUM_FPR+LUA_MINSTACK);
917+
setintV(V->top++, J->parent);
918+
setintV(V->top++, J->exitno);
919+
trace_exit_regs(V, ex);
923920
);
924921

925922
pc = exd.pc;

src/lj_vmevent.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ ptrdiff_t lj_vmevent_prepare(lua_State *L, VMEvent ev)
3838
void lj_vmevent_call(lua_State *L, ptrdiff_t argbase)
3939
{
4040
global_State *g = G(L);
41+
lua_State *oldL = gco2th(gcref(g->cur_L));
4142
uint8_t oldmask = g->vmevmask;
4243
uint8_t oldh = hook_save(g);
4344
int status;
@@ -51,6 +52,10 @@ void lj_vmevent_call(lua_State *L, ptrdiff_t argbase)
5152
fputs(tvisstr(L->top) ? strVdata(L->top) : "?", stderr);
5253
fputc('\n', stderr);
5354
}
55+
setgcref(g->cur_L, obj2gco(oldL));
56+
#if LJ_HASJIT
57+
G2J(g)->L = oldL;
58+
#endif
5459
hook_restore(g, oldh);
5560
if (g->vmevmask != VMEVENT_NOCACHE)
5661
g->vmevmask = oldmask; /* Restore event mask, but not if not modified. */

src/lj_vmevent.h

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,23 +32,25 @@ typedef enum {
3232
} VMEvent;
3333

3434
#ifdef LUAJIT_DISABLE_VMEVENT
35-
#define lj_vmevent_send(L, ev, args) UNUSED(L)
36-
#define lj_vmevent_send_(L, ev, args, post) UNUSED(L)
35+
#define lj_vmevent_send(g, ev, args) UNUSED(g)
36+
#define lj_vmevent_send_(g, ev, args, post) UNUSED(g)
3737
#else
38-
#define lj_vmevent_send(L, ev, args) \
39-
if (G(L)->vmevmask & VMEVENT_MASK(LJ_VMEVENT_##ev)) { \
40-
ptrdiff_t argbase = lj_vmevent_prepare(L, LJ_VMEVENT_##ev); \
38+
#define lj_vmevent_send(g, ev, args) \
39+
if ((g)->vmevmask & VMEVENT_MASK(LJ_VMEVENT_##ev)) { \
40+
lua_State *V = vmthread(g); \
41+
ptrdiff_t argbase = lj_vmevent_prepare(V, LJ_VMEVENT_##ev); \
4142
if (argbase) { \
4243
args \
43-
lj_vmevent_call(L, argbase); \
44+
lj_vmevent_call(V, argbase); \
4445
} \
4546
}
46-
#define lj_vmevent_send_(L, ev, args, post) \
47-
if (G(L)->vmevmask & VMEVENT_MASK(LJ_VMEVENT_##ev)) { \
48-
ptrdiff_t argbase = lj_vmevent_prepare(L, LJ_VMEVENT_##ev); \
47+
#define lj_vmevent_send_(g, ev, args, post) \
48+
if ((g)->vmevmask & VMEVENT_MASK(LJ_VMEVENT_##ev)) { \
49+
lua_State *V = vmthread(g); \
50+
ptrdiff_t argbase = lj_vmevent_prepare(V, LJ_VMEVENT_##ev); \
4951
if (argbase) { \
5052
args \
51-
lj_vmevent_call(L, argbase); \
53+
lj_vmevent_call(V, argbase); \
5254
post \
5355
} \
5456
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
local tap = require('tap')
2+
3+
-- The test file to demonstrate LuaJIT crash during stack overflow
4+
-- in the VM event handle.
5+
-- See also, https://github.com/LuaJIT/LuaJIT/issues/1403.
6+
7+
local test = tap.test('lj-1403-vmevent-crash-on-stkov'):skipcond({
8+
['Test requires JIT enabled'] = not jit.status(),
9+
})
10+
11+
test:plan(1)
12+
13+
local jit_dump = require('jit.dump')
14+
15+
-- XXX: Some specific stack usage without a stack top check by the
16+
-- Lua function header.
17+
local t = setmetatable({}, {__newindex = pcall, __call = type})
18+
-- luacheck: no unused
19+
local function prober(...)
20+
-- Invokes `pcall(t, t, t)`.
21+
t[t] = t
22+
end
23+
24+
jit.opt.start('hotloop=1')
25+
-- Need the invocation of the VM event.
26+
jit_dump.start('i', '/dev/null')
27+
28+
-- The code below causes the stack overflow in the VM event
29+
-- handler. The unwinding of the error breaks the JIT semantics
30+
-- and leads to a crash.
31+
local function looper()
32+
local r = pcall(prober)
33+
if not r then
34+
local n = 1
35+
while n < 3 do
36+
prober(1, 2)
37+
n = n + 1
38+
end
39+
end
40+
looper()
41+
end
42+
43+
pcall(coroutine.wrap(looper))
44+
45+
test:ok(true, 'no crash')
46+
47+
test:done(true)

0 commit comments

Comments
 (0)