TCA emitEndCatchHelper(CodeBlock& cb, UniqueStubs& us) { auto const udrspo = rvmtl()[unwinderDebuggerReturnSPOff()]; auto const debuggerReturn = vwrap(cb, [&] (Vout& v) { v << load{udrspo, rvmsp()}; v << storeqi{0, udrspo}; }); svcreq::emit_persistent(cb, folly::none, REQ_POST_DEBUGGER_RET); auto const resumeCPPUnwind = vwrap(cb, [] (Vout& v) { static_assert(sizeof(tl_regState) == 1, "The following store must match the size of tl_regState."); auto const regstate = emitTLSAddr(v, tls_datum(tl_regState)); v << storebi{static_cast<int32_t>(VMRegState::CLEAN), regstate}; v << load{rvmtl()[unwinderExnOff()], rarg(0)}; v << call{TCA(_Unwind_Resume), arg_regs(1)}; }); us.endCatchHelperPast = cb.frontier(); vwrap(cb, [] (Vout& v) { v << ud2{}; }); alignJmpTarget(cb); return vwrap(cb, [&] (Vout& v) { auto const done1 = v.makeBlock(); auto const sf1 = v.makeReg(); v << cmpqim{0, udrspo, sf1}; v << jcci{CC_NE, sf1, done1, debuggerReturn}; v = done1; // Normal end catch situation: call back to tc_unwind_resume, which returns // the catch trace (or null) in %rax, and the new vmfp in %rdx. v << copy{rvmfp(), rarg(0)}; v << call{TCA(tc_unwind_resume)}; v << copy{reg::rdx, rvmfp()}; auto const done2 = v.makeBlock(); auto const sf2 = v.makeReg(); v << testq{reg::rax, reg::rax, sf2}; v << jcci{CC_Z, sf2, done2, resumeCPPUnwind}; v = done2; // We need to do a syncForLLVMCatch(), but vmfp is already in rdx. v << jmpr{reg::rax}; }); }
void addDbgGuardImpl(SrcKey sk, SrcRec* sr) { TCA realCode = sr->getTopTranslation(); if (!realCode) return; // No translations, nothing to do. auto& cb = mcg->code.main(); auto const dbgGuard = vwrap(cb, [&] (Vout& v) { if (!sk.resumed()) { auto const off = sr->nonResumedSPOff(); v << lea{rvmfp()[-cellsToBytes(off.offset)], rvmsp()}; } auto const tinfo = v.makeReg(); auto const attached = v.makeReg(); auto const sf = v.makeReg(); auto const done = v.makeBlock(); constexpr size_t dbgOff = offsetof(ThreadInfo, m_reqInjectionData) + RequestInjectionData::debuggerReadOnlyOffset(); v << ldimmq{reinterpret_cast<uintptr_t>(sk.pc()), rarg(0)}; emitTLSLoad(v, tls_datum(ThreadInfo::s_threadInfo), tinfo); v << loadb{tinfo[dbgOff], attached}; v << testbi{static_cast<int8_t>(0xffu), attached, sf}; v << jcci{CC_NZ, sf, done, mcg->ustubs().interpHelper}; v = done; v << fallthru{}; }, CodeKind::Helper); // Emit a jump to the actual code. auto const dbgBranchGuardSrc = emitSmashableJmp(cb, realCode); // Add the guard to the SrcRec. sr->addDebuggerGuard(dbgGuard, dbgBranchGuardSrc); }
/* * Service request stub emitter. * * Emit a service request stub of type `sr' at `start' in `cb'. */ void emit_svcreq(CodeBlock& cb, TCA start, bool persist, folly::Optional<FPInvOffset> spOff, ServiceRequest sr, const ArgVec& argv) { FTRACE(2, "svcreq @{} {}(", start, to_name(sr)); auto const is_reused = start != cb.frontier(); CodeBlock stub; stub.init(start, stub_size(), "svcreq_stub"); { Vauto vasm{stub}; auto& v = vasm.main(); // If we have an spOff, materialize rvmsp() so that handleSRHelper() can do // a VM reg sync. (When we don't have an spOff, the caller of the service // request was responsible for making sure rvmsp already contained the top // of the stack.) if (spOff) { v << lea{rvmfp()[-cellsToBytes(spOff->offset)], rvmsp()}; } auto live_out = leave_trace_regs(); assert(argv.size() <= kMaxArgs); // Pick up CondCode arguments first---vasm may optimize immediate loads // into operations which clobber status flags. for (auto i = 0; i < argv.size(); ++i) { auto const& arg = argv[i]; if (arg.kind != Arg::Kind::CondCode) continue; FTRACE(2, "c({}), ", cc_names[arg.cc]); v << setcc{arg.cc, r_svcreq_sf(), rbyte(r_svcreq_arg(i))}; } for (auto i = 0; i < argv.size(); ++i) { auto const& arg = argv[i]; auto const r = r_svcreq_arg(i); switch (arg.kind) { case Arg::Kind::Immed: FTRACE(2, "{}, ", arg.imm); v << copy{v.cns(arg.imm), r}; break; case Arg::Kind::Address: FTRACE(2, "{}(%rip), ", arg.imm); v << leap{reg::rip[arg.imm], r}; break; case Arg::Kind::CondCode: break; } live_out |= r; } FTRACE(2, ") : stub@"); if (persist) { FTRACE(2, "<none>"); v << copy{v.cns(0), r_svcreq_stub()}; } else { FTRACE(2, "{}", stub.base()); v << leap{reg::rip[int64_t(stub.base())], r_svcreq_stub()}; } v << copy{v.cns(sr), r_svcreq_req()}; live_out |= r_svcreq_stub(); live_out |= r_svcreq_req(); v << jmpi{TCA(handleSRHelper), live_out}; // We pad ephemeral stubs unconditionally. This is required for // correctness by the x64 code relocator. vasm.unit().padding = !persist; } if (!is_reused) cb.skip(stub.used()); }
void lower(Vunit& unit, syncvmsp& inst, Vlabel b, size_t i) { unit.blocks[b].code[i] = copy{inst.s, rvmsp()}; }
void lower(Vunit& unit, defvmsp& inst, Vlabel b, size_t i) { unit.blocks[b].code[i] = copy{rvmsp(), inst.d}; }
void lower(VLS& env, syncvmsp& inst, Vlabel b, size_t i) { env.unit.blocks[b].code[i] = copy{inst.s, rvmsp()}; }
void lower(VLS& env, defvmsp& inst, Vlabel b, size_t i) { env.unit.blocks[b].code[i] = copy{rvmsp(), inst.d}; }
TCA emitFunctionEnterHelper(CodeBlock& cb, UniqueStubs& us) { alignJmpTarget(cb); auto const start = vwrap(cb, [&] (Vout& v) { auto const ar = v.makeReg(); v << copy{rvmfp(), ar}; // Fully set up the call frame for the stub. We can't skip this like we do // in other stubs because we need the return IP for this frame in the %rbp // chain, in order to find the proper fixup for the VMRegAnchor in the // intercept handler. v << stublogue{true}; v << copy{rsp(), rvmfp()}; // When we call the event hook, it might tell us to skip the callee // (because of fb_intercept). If that happens, we need to return to the // caller, but the handler will have already popped the callee's frame. // So, we need to save these values for later. v << pushm{ar[AROFF(m_savedRip)]}; v << pushm{ar[AROFF(m_sfp)]}; v << copy2{ar, v.cns(EventHook::NormalFunc), rarg(0), rarg(1)}; bool (*hook)(const ActRec*, int) = &EventHook::onFunctionCall; v << call{TCA(hook)}; }); us.functionEnterHelperReturn = vwrap2(cb, [&] (Vout& v, Vout& vcold) { auto const sf = v.makeReg(); v << testb{rret(), rret(), sf}; unlikelyIfThen(v, vcold, CC_Z, sf, [&] (Vout& v) { auto const saved_rip = v.makeReg(); // The event hook has already cleaned up the stack and popped the // callee's frame, so we're ready to continue from the original call // site. We just need to grab the fp/rip of the original frame that we // saved earlier, and sync rvmsp(). v << pop{rvmfp()}; v << pop{saved_rip}; // Drop our call frame; the stublogue{} instruction guarantees that this // is exactly 16 bytes. v << lea{rsp()[16], rsp()}; // Sync vmsp and return to the caller. This unbalances the return stack // buffer, but if we're intercepting, we probably don't care. v << load{rvmtl()[rds::kVmspOff], rvmsp()}; v << jmpr{saved_rip}; }); // Skip past the stuff we saved for the intercept case. v << lea{rsp()[16], rsp()}; // Restore rvmfp() and return to the callee's func prologue. v << stubret{RegSet(), true}; }); return start; }