Bladeren bron

x64: Add support for CET IBT.

Note: this is not enabled by default, look for CET in lj_arch.h
Contributed by Yuichiro Naito. #1391
Mike Pall 2 maanden geleden
bovenliggende
commit
25a61a1821
7 gewijzigde bestanden met toevoegingen van 95 en 10 verwijderingen
  1. 4 0
      src/Makefile
  2. 19 1
      src/jit/dis_x86.lua
  3. 11 0
      src/lj_arch.h
  4. 3 0
      src/lj_asm.c
  5. 7 0
      src/lj_emit_x86.h
  6. 3 0
      src/lj_target_x86.h
  7. 48 9
      src/vm_x64.dasc

+ 4 - 0
src/Makefile

@@ -446,6 +446,10 @@ ifneq (,$(findstring LJ_ABI_PAUTH 1,$(TARGET_TESTARCH)))
   DASM_AFLAGS+= -D PAUTH
   DASM_AFLAGS+= -D PAUTH
   TARGET_ARCH+= -DLJ_ABI_PAUTH=1
   TARGET_ARCH+= -DLJ_ABI_PAUTH=1
 endif
 endif
+ifneq (,$(findstring LJ_CET_BR 1,$(TARGET_TESTARCH)))
+  DASM_AFLAGS+= -D CET_BR
+  TARGET_ARCH+= -DLJ_CET_BR=1
+endif
 DASM_AFLAGS+= -D VER=$(subst LJ_ARCH_VERSION_,,$(filter LJ_ARCH_VERSION_%,$(subst LJ_ARCH_VERSION ,LJ_ARCH_VERSION_,$(TARGET_TESTARCH))))
 DASM_AFLAGS+= -D VER=$(subst LJ_ARCH_VERSION_,,$(filter LJ_ARCH_VERSION_%,$(subst LJ_ARCH_VERSION ,LJ_ARCH_VERSION_,$(TARGET_TESTARCH))))
 ifeq (Windows,$(TARGET_SYS))
 ifeq (Windows,$(TARGET_SYS))
   DASM_AFLAGS+= -D WIN
   DASM_AFLAGS+= -D WIN

+ 19 - 1
src/jit/dis_x86.lua

@@ -122,7 +122,7 @@ local map_opc2 = {
 "movlhpsXrm$movhpsXrm|movshdupXrm|movhpdXrm",
 "movlhpsXrm$movhpsXrm|movshdupXrm|movhpdXrm",
 "movhpsXmr||movhpdXmr",
 "movhpsXmr||movhpdXmr",
 "$prefetcht!Bm","hintnopVm","hintnopVm","hintnopVm",
 "$prefetcht!Bm","hintnopVm","hintnopVm","hintnopVm",
-"hintnopVm","hintnopVm","hintnopVm","hintnopVm",
+"hintnopVm","hintnopVm","endbr*hintnopVm","hintnopVm",
 --2x
 --2x
 "movUmx$","movUmy$","movUxm$","movUym$","movUmz$",nil,"movUzm$",nil,
 "movUmx$","movUmy$","movUxm$","movUym$","movUmz$",nil,"movUzm$",nil,
 "movapsXrm||movapdXrm",
 "movapsXrm||movapdXrm",
@@ -804,6 +804,24 @@ map_act = {
     return dispatch(ctx, map_opcvm[ctx.mrm])
     return dispatch(ctx, map_opcvm[ctx.mrm])
   end,
   end,
 
 
+  -- Special NOP for endbr64/endbr32.
+  endbr = function(ctx, name, pat)
+    if ctx.rep then
+      local pos = ctx.pos
+      local b = byte(ctx.code, pos)
+      local text
+      if b == 0xfa then text = "endbr64"
+      elseif b == 0xfb then text = "endbr64"
+      end
+      if text then
+	ctx.pos = pos + 1
+	ctx.rep = nil
+	return putop(ctx, text)
+      end
+    end
+    return dispatch(ctx, pat)
+  end,
+
   -- Floating point opcode dispatch.
   -- Floating point opcode dispatch.
   fp = function(ctx, name, pat)
   fp = function(ctx, name, pat)
     local mrm = getmrm(ctx); if not mrm then return incomplete(ctx) end
     local mrm = getmrm(ctx); if not mrm then return incomplete(ctx) end

+ 11 - 0
src/lj_arch.h

@@ -219,6 +219,17 @@
 #error "macOS requires GC64 -- don't disable it"
 #error "macOS requires GC64 -- don't disable it"
 #endif
 #endif
 
 
+#if (__CET__ & 1) && defined(LUAJIT_ENABLE_CET_BR)
+/*
+** Control-Flow Enforcement Technique (CET) indirect branch tracking (IBT).
+** This is not enabled by default because it causes a notable slowdown of
+** the interpreter on all x64 CPUs, whether they have CET enabled or not.
+** If your toolchain enables -fcf-protection=branch by default, you need
+** to build with: make XCFLAGS=-DLUAJIT_ENABLE_CET_BR
+*/
+#define LJ_CET_BR		1
+#endif
+
 #elif LUAJIT_TARGET == LUAJIT_ARCH_ARM
 #elif LUAJIT_TARGET == LUAJIT_ARCH_ARM
 
 
 #define LJ_ARCH_NAME		"arm"
 #define LJ_ARCH_NAME		"arm"

+ 3 - 0
src/lj_asm.c

@@ -2586,6 +2586,9 @@ void lj_asm_trace(jit_State *J, GCtrace *T)
       asm_head_side(as);
       asm_head_side(as);
     else
     else
       asm_head_root(as);
       asm_head_root(as);
+#if LJ_CET_BR
+    emit_endbr(as);
+#endif
     asm_phi_fixup(as);
     asm_phi_fixup(as);
 
 
     if (J->curfinal->nins >= T->nins) {  /* IR didn't grow? */
     if (J->curfinal->nins >= T->nins) {  /* IR didn't grow? */

+ 7 - 0
src/lj_emit_x86.h

@@ -70,6 +70,13 @@ static LJ_AINLINE MCode *emit_op(x86Op xo, Reg rr, Reg rb, Reg rx,
   return p;
   return p;
 }
 }
 
 
+#if LJ_CET_BR
+static void emit_endbr(ASMState *as)
+{
+  emit_u32(as, XI_ENDBR64);
+}
+#endif
+
 /* op + modrm */
 /* op + modrm */
 #define emit_opm(xo, mode, rr, rb, p, delta) \
 #define emit_opm(xo, mode, rr, rb, p, delta) \
   (p[(delta)-1] = MODRM((mode), (rr), (rb)), \
   (p[(delta)-1] = MODRM((mode), (rr), (rb)), \

+ 3 - 0
src/lj_target_x86.h

@@ -242,6 +242,9 @@ typedef enum {
   XV_SHLX =	XV_660f38(f7),
   XV_SHLX =	XV_660f38(f7),
   XV_SHRX =	XV_f20f38(f7),
   XV_SHRX =	XV_f20f38(f7),
 
 
+  /* Special NOP instructions. */
+  XI_ENDBR64 =	0xfa1e0ff3,
+
   /* Variable-length opcodes. XO_* prefix. */
   /* Variable-length opcodes. XO_* prefix. */
   XO_OR =	XO_(0b),
   XO_OR =	XO_(0b),
   XO_MOV =	XO_(8b),
   XO_MOV =	XO_(8b),

+ 48 - 9
src/vm_x64.dasc

@@ -189,16 +189,24 @@
 |
 |
 |.endif
 |.endif
 |
 |
+|//-- Control-Flow Enforcement Technique (CET) ---------------------------
+|
+|.if CET_BR
+|.macro endbr; endbr64; .endmacro
+|.else
+|.macro endbr; .endmacro
+|.endif
+|
 |//-----------------------------------------------------------------------
 |//-----------------------------------------------------------------------
 |
 |
 |// Instruction headers.
 |// Instruction headers.
-|.macro ins_A; .endmacro
-|.macro ins_AD; .endmacro
-|.macro ins_AJ; .endmacro
-|.macro ins_ABC; movzx RBd, RCH; movzx RCd, RCL; .endmacro
-|.macro ins_AB_; movzx RBd, RCH; .endmacro
-|.macro ins_A_C; movzx RCd, RCL; .endmacro
-|.macro ins_AND; not RD; .endmacro
+|.macro ins_A; endbr; .endmacro
+|.macro ins_AD; endbr; .endmacro
+|.macro ins_AJ; endbr; .endmacro
+|.macro ins_ABC; endbr; movzx RBd, RCH; movzx RCd, RCL; .endmacro
+|.macro ins_AB_; endbr; movzx RBd, RCH; .endmacro
+|.macro ins_A_C; endbr; movzx RCd, RCL; .endmacro
+|.macro ins_AND; endbr; not RD; .endmacro
 |
 |
 |// Instruction decode+dispatch. Carefully tuned (nope, lodsd is not faster).
 |// Instruction decode+dispatch. Carefully tuned (nope, lodsd is not faster).
 |.macro ins_NEXT
 |.macro ins_NEXT
@@ -479,20 +487,24 @@ static void build_subroutines(BuildCtx *ctx)
   |  jmp <3
   |  jmp <3
   |
   |
   |->vm_unwind_yield:
   |->vm_unwind_yield:
+  |  endbr
   |  mov al, LUA_YIELD
   |  mov al, LUA_YIELD
   |  jmp ->vm_unwind_c_eh
   |  jmp ->vm_unwind_c_eh
   |
   |
   |->vm_unwind_c:			// Unwind C stack, return from vm_pcall.
   |->vm_unwind_c:			// Unwind C stack, return from vm_pcall.
+  |  endbr
   |  // (void *cframe, int errcode)
   |  // (void *cframe, int errcode)
   |  mov eax, CARG2d			// Error return status for vm_pcall.
   |  mov eax, CARG2d			// Error return status for vm_pcall.
   |  mov rsp, CARG1
   |  mov rsp, CARG1
   |->vm_unwind_c_eh:			// Landing pad for external unwinder.
   |->vm_unwind_c_eh:			// Landing pad for external unwinder.
+  |  endbr
   |  mov L:RB, SAVE_L
   |  mov L:RB, SAVE_L
   |  mov GL:RB, L:RB->glref
   |  mov GL:RB, L:RB->glref
   |  mov dword GL:RB->vmstate, ~LJ_VMST_C
   |  mov dword GL:RB->vmstate, ~LJ_VMST_C
   |  jmp ->vm_leave_unw
   |  jmp ->vm_leave_unw
   |
   |
   |->vm_unwind_rethrow:
   |->vm_unwind_rethrow:
+  |  endbr
   |.if not X64WIN
   |.if not X64WIN
   |  mov CARG1, SAVE_L
   |  mov CARG1, SAVE_L
   |  mov CARG2d, eax
   |  mov CARG2d, eax
@@ -501,10 +513,12 @@ static void build_subroutines(BuildCtx *ctx)
   |.endif
   |.endif
   |
   |
   |->vm_unwind_ff:			// Unwind C stack, return from ff pcall.
   |->vm_unwind_ff:			// Unwind C stack, return from ff pcall.
+  |  endbr
   |  // (void *cframe)
   |  // (void *cframe)
   |  and CARG1, CFRAME_RAWMASK
   |  and CARG1, CFRAME_RAWMASK
   |  mov rsp, CARG1
   |  mov rsp, CARG1
   |->vm_unwind_ff_eh:			// Landing pad for external unwinder.
   |->vm_unwind_ff_eh:			// Landing pad for external unwinder.
+  |  endbr
   |  mov L:RB, SAVE_L
   |  mov L:RB, SAVE_L
   |  mov RDd, 1+1			// Really 1+2 results, incr. later.
   |  mov RDd, 1+1			// Really 1+2 results, incr. later.
   |  mov BASE, L:RB->base
   |  mov BASE, L:RB->base
@@ -675,6 +689,7 @@ static void build_subroutines(BuildCtx *ctx)
   |//-- Continuation dispatch ----------------------------------------------
   |//-- Continuation dispatch ----------------------------------------------
   |
   |
   |->cont_dispatch:
   |->cont_dispatch:
+  |  endbr
   |  // BASE = meta base, RA = resultofs, RD = nresults+1 (also in MULTRES)
   |  // BASE = meta base, RA = resultofs, RD = nresults+1 (also in MULTRES)
   |  add RA, BASE
   |  add RA, BASE
   |  and PC, -8
   |  and PC, -8
@@ -706,6 +721,7 @@ static void build_subroutines(BuildCtx *ctx)
   |.endif
   |.endif
   |
   |
   |->cont_cat:				// BASE = base, RC = result, RB = mbase
   |->cont_cat:				// BASE = base, RC = result, RB = mbase
+  |  endbr
   |  movzx RAd, PC_RB
   |  movzx RAd, PC_RB
   |  sub RB, 32
   |  sub RB, 32
   |  lea RA, [BASE+RA*8]
   |  lea RA, [BASE+RA*8]
@@ -774,6 +790,7 @@ static void build_subroutines(BuildCtx *ctx)
   |  test RC, RC
   |  test RC, RC
   |  jz >3
   |  jz >3
   |->cont_ra:				// BASE = base, RC = result
   |->cont_ra:				// BASE = base, RC = result
+  |  endbr
   |  movzx RAd, PC_RA
   |  movzx RAd, PC_RA
   |  mov RB, [RC]
   |  mov RB, [RC]
   |  mov [BASE+RA*8], RB
   |  mov [BASE+RA*8], RB
@@ -851,6 +868,7 @@ static void build_subroutines(BuildCtx *ctx)
   |  mov RB, [BASE+RA*8]
   |  mov RB, [BASE+RA*8]
   |  mov [RC], RB
   |  mov [RC], RB
   |->cont_nop:				// BASE = base, (RC = result)
   |->cont_nop:				// BASE = base, (RC = result)
+  |  endbr
   |  ins_next
   |  ins_next
   |
   |
   |3:  // Call __newindex metamethod.
   |3:  // Call __newindex metamethod.
@@ -921,6 +939,7 @@ static void build_subroutines(BuildCtx *ctx)
   |  ins_next
   |  ins_next
   |
   |
   |->cont_condt:			// BASE = base, RC = result
   |->cont_condt:			// BASE = base, RC = result
+  |  endbr
   |  add PC, 4
   |  add PC, 4
   |  mov ITYPE, [RC]
   |  mov ITYPE, [RC]
   |  sar ITYPE, 47
   |  sar ITYPE, 47
@@ -929,6 +948,7 @@ static void build_subroutines(BuildCtx *ctx)
   |  jmp <6
   |  jmp <6
   |
   |
   |->cont_condf:			// BASE = base, RC = result
   |->cont_condf:			// BASE = base, RC = result
+  |  endbr
   |  mov ITYPE, [RC]
   |  mov ITYPE, [RC]
   |  sar ITYPE, 47
   |  sar ITYPE, 47
   |  cmp ITYPEd, LJ_TISTRUECOND		// Branch if result is false.
   |  cmp ITYPEd, LJ_TISTRUECOND		// Branch if result is false.
@@ -1132,16 +1152,17 @@ static void build_subroutines(BuildCtx *ctx)
   |
   |
   |.macro .ffunc, name
   |.macro .ffunc, name
   |->ff_ .. name:
   |->ff_ .. name:
+  | endbr
   |.endmacro
   |.endmacro
   |
   |
   |.macro .ffunc_1, name
   |.macro .ffunc_1, name
   |->ff_ .. name:
   |->ff_ .. name:
-  |  cmp NARGS:RDd, 1+1;  jb ->fff_fallback
+  |  endbr; cmp NARGS:RDd, 1+1;  jb ->fff_fallback
   |.endmacro
   |.endmacro
   |
   |
   |.macro .ffunc_2, name
   |.macro .ffunc_2, name
   |->ff_ .. name:
   |->ff_ .. name:
-  |  cmp NARGS:RDd, 2+1;  jb ->fff_fallback
+  |  endbr; cmp NARGS:RDd, 2+1;  jb ->fff_fallback
   |.endmacro
   |.endmacro
   |
   |
   |.macro .ffunc_n, name, op
   |.macro .ffunc_n, name, op
@@ -2207,6 +2228,7 @@ static void build_subroutines(BuildCtx *ctx)
   |
   |
   |->vm_record:				// Dispatch target for recording phase.
   |->vm_record:				// Dispatch target for recording phase.
   |.if JIT
   |.if JIT
+  |  endbr
   |  movzx RDd, byte [DISPATCH+DISPATCH_GL(hookmask)]
   |  movzx RDd, byte [DISPATCH+DISPATCH_GL(hookmask)]
   |  test RDL, HOOK_VMEVENT		// No recording while in vmevent.
   |  test RDL, HOOK_VMEVENT		// No recording while in vmevent.
   |  jnz >5
   |  jnz >5
@@ -2220,12 +2242,14 @@ static void build_subroutines(BuildCtx *ctx)
   |.endif
   |.endif
   |
   |
   |->vm_rethook:			// Dispatch target for return hooks.
   |->vm_rethook:			// Dispatch target for return hooks.
+  |  endbr
   |  movzx RDd, byte [DISPATCH+DISPATCH_GL(hookmask)]
   |  movzx RDd, byte [DISPATCH+DISPATCH_GL(hookmask)]
   |  test RDL, HOOK_ACTIVE		// Hook already active?
   |  test RDL, HOOK_ACTIVE		// Hook already active?
   |  jnz >5
   |  jnz >5
   |  jmp >1
   |  jmp >1
   |
   |
   |->vm_inshook:			// Dispatch target for instr/line hooks.
   |->vm_inshook:			// Dispatch target for instr/line hooks.
+  |  endbr
   |  movzx RDd, byte [DISPATCH+DISPATCH_GL(hookmask)]
   |  movzx RDd, byte [DISPATCH+DISPATCH_GL(hookmask)]
   |  test RDL, HOOK_ACTIVE		// Hook already active?
   |  test RDL, HOOK_ACTIVE		// Hook already active?
   |  jnz >5
   |  jnz >5
@@ -2253,6 +2277,7 @@ static void build_subroutines(BuildCtx *ctx)
   |  jmp aword [DISPATCH+OP*8+GG_DISP2STATIC]	// Re-dispatch to static ins.
   |  jmp aword [DISPATCH+OP*8+GG_DISP2STATIC]	// Re-dispatch to static ins.
   |
   |
   |->cont_hook:				// Continue from hook yield.
   |->cont_hook:				// Continue from hook yield.
+  |  endbr
   |  add PC, 4
   |  add PC, 4
   |  mov RA, [RB-40]
   |  mov RA, [RB-40]
   |  mov MULTRES, RAd			// Restore MULTRES for *M ins.
   |  mov MULTRES, RAd			// Restore MULTRES for *M ins.
@@ -2277,6 +2302,7 @@ static void build_subroutines(BuildCtx *ctx)
   |.endif
   |.endif
   |
   |
   |->vm_callhook:			// Dispatch target for call hooks.
   |->vm_callhook:			// Dispatch target for call hooks.
+  |  endbr
   |  mov SAVE_PC, PC
   |  mov SAVE_PC, PC
   |.if JIT
   |.if JIT
   |  jmp >1
   |  jmp >1
@@ -2312,6 +2338,7 @@ static void build_subroutines(BuildCtx *ctx)
   |
   |
   |->cont_stitch:			// Trace stitching.
   |->cont_stitch:			// Trace stitching.
   |.if JIT
   |.if JIT
+  |  endbr
   |  // BASE = base, RC = result, RB = mbase
   |  // BASE = base, RC = result, RB = mbase
   |  mov TRACE:ITYPE, [RB-40]		// Save previous trace.
   |  mov TRACE:ITYPE, [RB-40]		// Save previous trace.
   |  cleartp TRACE:ITYPE
   |  cleartp TRACE:ITYPE
@@ -2364,6 +2391,7 @@ static void build_subroutines(BuildCtx *ctx)
   |
   |
   |->vm_profhook:			// Dispatch target for profiler hook.
   |->vm_profhook:			// Dispatch target for profiler hook.
 #if LJ_HASPROFILE
 #if LJ_HASPROFILE
+  |  endbr
   |  mov L:RB, SAVE_L
   |  mov L:RB, SAVE_L
   |  mov L:RB->base, BASE
   |  mov L:RB->base, BASE
   |  mov CARG2, PC			// Caveat: CARG2 == BASE
   |  mov CARG2, PC			// Caveat: CARG2 == BASE
@@ -2383,6 +2411,7 @@ static void build_subroutines(BuildCtx *ctx)
   |// The 16 bit exit number is stored with two (sign-extended) push imm8.
   |// The 16 bit exit number is stored with two (sign-extended) push imm8.
   |->vm_exit_handler:
   |->vm_exit_handler:
   |.if JIT
   |.if JIT
+  |  endbr
   |  push r13; push r12
   |  push r13; push r12
   |  push r11; push r10; push r9; push r8
   |  push r11; push r10; push r9; push r8
   |  push rdi; push rsi; push rbp; lea rbp, [rsp+88]; push rbp
   |  push rdi; push rsi; push rbp; lea rbp, [rsp+88]; push rbp
@@ -2431,6 +2460,7 @@ static void build_subroutines(BuildCtx *ctx)
   |  jmp >1
   |  jmp >1
   |.endif
   |.endif
   |->vm_exit_interp:
   |->vm_exit_interp:
+  |  endbr
   |  // RD = MULTRES or negated error code, BASE, PC and DISPATCH set.
   |  // RD = MULTRES or negated error code, BASE, PC and DISPATCH set.
   |.if JIT
   |.if JIT
   |  // Restore additional callee-save registers only used in compiled code.
   |  // Restore additional callee-save registers only used in compiled code.
@@ -2524,6 +2554,7 @@ static void build_subroutines(BuildCtx *ctx)
   |.macro vm_round, name, mode, cond
   |.macro vm_round, name, mode, cond
   |->name:
   |->name:
   |->name .. _sse:
   |->name .. _sse:
+  |  endbr
   |  sseconst_abs xmm2, RD
   |  sseconst_abs xmm2, RD
   |  sseconst_2p52 xmm3, RD
   |  sseconst_2p52 xmm3, RD
   |  movaps xmm1, xmm0
   |  movaps xmm1, xmm0
@@ -2634,6 +2665,7 @@ static void build_subroutines(BuildCtx *ctx)
   |// Next idx returned in edx.
   |// Next idx returned in edx.
   |->vm_next:
   |->vm_next:
   |.if JIT
   |.if JIT
+  |  endbr
   |  mov NEXT_ASIZE, NEXT_TAB->asize
   |  mov NEXT_ASIZE, NEXT_TAB->asize
   |1:  // Traverse array part.
   |1:  // Traverse array part.
   |  cmp NEXT_IDX, NEXT_ASIZE;  jae >5
   |  cmp NEXT_IDX, NEXT_ASIZE;  jae >5
@@ -4087,6 +4119,7 @@ static void build_ins(BuildCtx *ctx, BCOp op, int defop)
 
 
   case BC_ITERN:
   case BC_ITERN:
     |.if JIT
     |.if JIT
+    |  endbr
     |  hotloop RBd
     |  hotloop RBd
     |.endif
     |.endif
     |->vm_IITERN:
     |->vm_IITERN:
@@ -4266,6 +4299,7 @@ static void build_ins(BuildCtx *ctx, BCOp op, int defop)
     |  jnz >7				// Not returning to a fixarg Lua func?
     |  jnz >7				// Not returning to a fixarg Lua func?
     switch (op) {
     switch (op) {
     case BC_RET:
     case BC_RET:
+      |  endbr
       |->BC_RET_Z:
       |->BC_RET_Z:
       |  mov KBASE, BASE		// Use KBASE for result move.
       |  mov KBASE, BASE		// Use KBASE for result move.
       |  sub RDd, 1
       |  sub RDd, 1
@@ -4284,10 +4318,12 @@ static void build_ins(BuildCtx *ctx, BCOp op, int defop)
       |  ja >6
       |  ja >6
       break;
       break;
     case BC_RET1:
     case BC_RET1:
+      |  endbr
       |  mov RB, [BASE+RA]
       |  mov RB, [BASE+RA]
       |  mov [BASE-16], RB
       |  mov [BASE-16], RB
       /* fallthrough */
       /* fallthrough */
     case BC_RET0:
     case BC_RET0:
+      |  endbr
       |5:
       |5:
       |  cmp PC_RB, RDL			// More results expected?
       |  cmp PC_RB, RDL			// More results expected?
       |  ja >6
       |  ja >6
@@ -4334,6 +4370,7 @@ static void build_ins(BuildCtx *ctx, BCOp op, int defop)
 
 
   case BC_FORL:
   case BC_FORL:
     |.if JIT
     |.if JIT
+    |  endbr
     |  hotloop RBd
     |  hotloop RBd
     |.endif
     |.endif
     | // Fall through. Assumes BC_IFORL follows and ins_AJ is a no-op.
     | // Fall through. Assumes BC_IFORL follows and ins_AJ is a no-op.
@@ -4485,6 +4522,7 @@ static void build_ins(BuildCtx *ctx, BCOp op, int defop)
 
 
   case BC_ITERL:
   case BC_ITERL:
     |.if JIT
     |.if JIT
+    |  endbr
     |  hotloop RBd
     |  hotloop RBd
     |.endif
     |.endif
     | // Fall through. Assumes BC_IITERL follows and ins_AJ is a no-op.
     | // Fall through. Assumes BC_IITERL follows and ins_AJ is a no-op.
@@ -4578,6 +4616,7 @@ static void build_ins(BuildCtx *ctx, BCOp op, int defop)
 
 
   case BC_FUNCF:
   case BC_FUNCF:
     |.if JIT
     |.if JIT
+    |  endbr
     |  hotcall RBd
     |  hotcall RBd
     |.endif
     |.endif
   case BC_FUNCV:  /* NYI: compiled vararg functions. */
   case BC_FUNCV:  /* NYI: compiled vararg functions. */