void handleStackOverflow(ActRec* calleeAR) { /* * First synchronize registers. * * We're called in two situations: either this is the first frame after a * re-entry, in which case calleeAR->m_sfp is enterTCHelper's native stack, * or we're called in the middle of one VM entry (from a func prologue). We * want to raise the exception from the caller's FCall instruction in the * second case, and in the first case we have to raise in a special way * inside this re-entry. * * Either way the stack depth is below the calleeAR by numArgs, because we * haven't run func prologue duties yet. */ auto& unsafeRegs = vmRegsUnsafe(); auto const isReentry = calleeAR == vmFirstAR(); auto const arToSync = isReentry ? calleeAR : calleeAR->m_sfp; unsafeRegs.fp = arToSync; unsafeRegs.stack.top() = reinterpret_cast<Cell*>(calleeAR) - calleeAR->numArgs(); auto const func_base = arToSync->func()->base(); // calleeAR m_soff is 0 in the re-entry case, so we'll set pc to the func // base. But it also doesn't matter because we're going to throw a special // VMReenterStackOverflow in that case so the unwinder won't worry about it. unsafeRegs.pc = arToSync->func()->unit()->at(func_base + calleeAR->m_soff); tl_regState = VMRegState::CLEAN; if (!isReentry) { /* * The normal case - we were called via FCall, or FCallArray. We need to * construct the pc of the fcall from the return address (which will be * after the fcall). Because fcall is a variable length instruction, and * because we sometimes delete instructions from the instruction stream, we * need to use fpi regions to find the fcall. */ const FPIEnt* fe = liveFunc()->findPrecedingFPI( liveUnit()->offsetOf(vmpc())); vmpc() = liveUnit()->at(fe->m_fcallOff); assertx(isFCallStar(peek_op(vmpc()))); raise_error("Stack overflow"); } else { /* * We were called via re-entry. Leak the params and the ActRec, and tell * the unwinder that there's nothing left to do in this "entry". * * Also, the caller hasn't set up the m_invName area on the ActRec (unless * it was a magic call), since it's the prologue's responsibility if it's a * non-magic call. We can just null it out since we're fatalling. */ vmsp() = reinterpret_cast<Cell*>(calleeAR + 1); calleeAR->setVarEnv(nullptr); throw VMReenterStackOverflow(); } not_reached(); }
void sync_regstate(_Unwind_Context* context) { assertx(tl_regState == VMRegState::DIRTY); uintptr_t frameRbp = _Unwind_GetGR(context, Debug::RBP); uintptr_t frameRip = _Unwind_GetIP(context); FTRACE(2, "syncing regstate for rbp: {:#x} rip: {:#x}\n", frameRbp, frameRip); /* * fixupWork expects to be looking at the first frame that is out of * the TC. We have RBP/RIP for the TC frame that called out here, * so we make a fake ActRec here to give it what it expects. * * Note: this doesn't work for IndirectFixup situations. However, * currently IndirectFixup is only used for destructors, which * aren't allowed to throw, so this is ok. */ ActRec fakeAr; fakeAr.m_sfp = reinterpret_cast<ActRec*>(frameRbp); fakeAr.m_savedRip = frameRip; Stats::inc(Stats::TC_SyncUnwind); mcg->fixupMap().fixupWork(g_context.getNoCheck(), &fakeAr); tl_regState = VMRegState::CLEAN; FTRACE(2, "synced vmfp: {} vmsp: {} vmpc: {}\n", vmfp(), vmsp(), vmpc()); }
void collectImpl(const char* phase) { VMRegAnchor _; if (t_eager_gc && RuntimeOption::EvalFilterGCPoints) { t_eager_gc = false; auto pc = vmpc(); if (t_surprise_filter.test(pc)) return; t_surprise_filter.insert(pc); TRACE(2, "eager gc %s at %p\n", phase, pc); } else { TRACE(2, "normal gc %s at %p\n", phase, vmpc()); } Marker mkr; mkr.init(); mkr.trace(); mkr.sweep(); }
TCA fcallHelper(ActRec* ar) { assert_native_stack_aligned(); assertx(!ar->resumed()); if (LIKELY(!RuntimeOption::EvalFailJitPrologs)) { auto const tca = mcg->getFuncPrologue( const_cast<Func*>(ar->m_func), ar->numArgs(), ar ); if (tca) return tca; } // Check for stack overflow in the same place func prologues make their // StackCheck::Early check (see irgen-func-prologue.cpp). This handler also // cleans and syncs vmRegs for us. if (checkCalleeStackOverflow(ar)) handleStackOverflow(ar); try { VMRegAnchor _(ar); if (doFCall(ar, vmpc())) { return mcg->tx().uniqueStubs.resumeHelperRet; } // We've been asked to skip the function body (fb_intercept). The vmregs // have already been fixed; indicate this with a nullptr return. return nullptr; } catch (...) { // The VMRegAnchor above took care of us, but we need to tell the unwinder // (since ~VMRegAnchor() will have reset tl_regState). tl_regState = VMRegState::CLEAN; throw; } }
// Unwind the frame for a builtin. Currently only used when switching // modes for hphpd_break and fb_enable_code_coverage. void unwindBuiltinFrame() { auto& stack = vmStack(); auto& fp = vmfp(); assert(fp->m_func->methInfo()); assert(fp->m_func->name()->isame(s_hphpd_break.get()) || fp->m_func->name()->isame(s_fb_enable_code_coverage.get())); // Free any values that may be on the eval stack. We know there // can't be FPI regions and it can't be a generator body because // it's a builtin frame. auto const evalTop = reinterpret_cast<TypedValue*>(vmfp()); while (stack.topTV() < evalTop) { stack.popTV(); } // Free the locals and VarEnv if there is one auto rv = make_tv<KindOfNull>(); frame_free_locals_inl(fp, fp->m_func->numLocals(), &rv); // Tear down the frame Offset pc = -1; ActRec* sfp = g_context->getPrevVMState(fp, &pc); assert(pc != -1); fp = sfp; vmpc() = fp->m_func->unit()->at(pc); stack.discardAR(); stack.pushNull(); // return value }
void FixupMap::fixupWorkSimulated(ExecutionContext* ec) const { TRACE(1, "fixup(begin):\n"); auto isVMFrame = [] (ActRec* ar, const vixl::Simulator* sim) { // If this assert is failing, you may have forgotten a sync point somewhere assert(ar); bool ret = uintptr_t(ar) - s_stackLimit >= s_stackSize && !sim->is_on_stack(ar); assert(!ret || (ar >= vmStack().getStackLowAddress() && ar < vmStack().getStackHighAddress()) || ar->resumed()); return ret; }; // For each nested simulator (corresponding to nested VM invocations), look at // its PC to find a potential fixup key. // // Callstack walking is necessary, because we may get called from a // uniqueStub. for (int i = ec->m_activeSims.size() - 1; i >= 0; --i) { auto const* sim = ec->m_activeSims[i]; auto* rbp = reinterpret_cast<ActRec*>(sim->xreg(JIT::ARM::rVmFp.code())); auto tca = reinterpret_cast<TCA>(sim->pc()); TRACE(2, "considering frame %p, %p\n", rbp, tca); while (rbp && !isVMFrame(rbp, sim)) { tca = reinterpret_cast<TCA>(rbp->m_savedRip); rbp = rbp->m_sfp; } if (!rbp) continue; auto* ent = m_fixups.find(tca); if (!ent) { continue; } if (ent->isIndirect()) { not_implemented(); } VMRegs regs; regsFromActRec(tca, rbp, ent->fixup, ®s); TRACE(2, "fixup(end): func %s fp %p sp %p pc %p\b", regs.m_fp->m_func->name()->data(), regs.m_fp, regs.m_sp, regs.m_pc); vmfp() = const_cast<ActRec*>(regs.m_fp); vmpc() = reinterpret_cast<PC>(regs.m_pc); vmsp() = regs.m_sp; return; } // This shouldn't be reached. always_assert(false); }
UnwindAction enterUnwinder() { auto fault = g_context->m_faults.back(); return unwind( vmfp(), // by ref vmStack(), // by ref vmpc(), // by ref fault ); }
void CmdOut::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) { TRACE(2, "CmdOut::onBeginInterrupt\n"); assert(!m_complete); // Complete cmds should not be asked to do work. m_needsVMInterrupt = false; if (m_skippingOverPopR) { m_complete = true; return; } int currentVMDepth = g_context->m_nesting; int currentStackDepth = proxy.getStackDepth(); // Deeper or same depth? Keep running. if ((currentVMDepth > m_vmDepth) || ((currentVMDepth == m_vmDepth) && (currentStackDepth >= m_stackDepth))) { TRACE(2, "CmdOut: deeper, keep running...\n"); return; } if (interrupt.getInterruptType() == ExceptionHandler) { // If we're about to enter an exception handler we turn interrupts on to // ensure we stop when control reaches the handler. The normal logic below // will decide if we're done at that point or not. TRACE(2, "CmdOut: exception thrown\n"); removeLocationFilter(); m_needsVMInterrupt = true; return; } TRACE(2, "CmdOut: shallower stack depth, done.\n"); cleanupStepOuts(); int depth = decCount(); if (depth == 0) { PC pc = vmpc(); // Step over PopR following a call if (*reinterpret_cast<const Op*>(pc) == Op::PopR) { m_skippingOverPopR = true; m_needsVMInterrupt = true; } else { m_complete = true; } return; } else { TRACE(2, "CmdOut: not complete, step out again.\n"); onSetup(proxy, interrupt); } }
void CmdNext::onSetup(DebuggerProxy& proxy, CmdInterrupt& interrupt) { TRACE(2, "CmdNext::onSetup\n"); assertx(!m_complete); // Complete cmds should not be asked to do work. m_stackDepth = proxy.getStackDepth(); m_vmDepth = g_context->m_nesting; m_loc = interrupt.getFileLine(); ActRec *fp = vmfp(); if (!fp) { // If we have no frame just wait for the next instruction to be interpreted. m_needsVMInterrupt = true; return; } PC pc = vmpc(); stepCurrentLine(interrupt, fp, pc); }
TCA fcallHelper(ActRec* ar, void* sp) { try { assert(!ar->resumed()); TCA tca = mcg->getFuncPrologue((Func*)ar->m_func, ar->numArgs(), ar); if (tca) { return tca; } if (!ar->m_func->isClonedClosure()) { /* * If the func is a cloned closure, then the original * closure has already run the prologue, and the prologues * array is just being used as entry points for the * dv funclets. Dont run the prologue again. */ VMRegAnchor _(ar); if (g_context->doFCall(ar, vmpc())) { return tx->uniqueStubs.resumeHelperRet; } // We've been asked to skip the function body // (fb_intercept). frame, stack and pc have // already been fixed - flag that with a negative // return address. return (TCA)-ar->m_savedRip; } setupAfterPrologue(ar, sp); assert(ar == vmfp()); return tx->uniqueStubs.resumeHelper; } catch (...) { /* The return address is set to __fcallHelperThunk, which has no unwind information. Its "logically" part of the tc, but the c++ unwinder wont know that. So point our return address at the called function's return address (which will be in the tc). Note that the registers really are clean - we cleaned them in the try above - so we just have to tell the unwinder that. */ DECLARE_FRAME_POINTER(framePtr); tl_regState = VMRegState::CLEAN; framePtr->m_savedRip = ar->m_savedRip; throw; } }
// stack trace helper static ProfileStackTrace getStackTrace() { ProfileStackTrace trace; if (g_context.isNull()) return trace; VMRegAnchor _; ActRec *fp = vmfp(); if (!fp) return trace; PC pc = vmpc(); const Func *f = fp->m_func; Unit *u = f->unit(); Offset off = pc - u->entry(); for (;;) { trace.push_back({ f, off, fp->resumed() }); fp = g_context->getPrevVMStateUNSAFE(fp, &off); if (!fp) break; f = fp->m_func; } return trace; }
void enterTC(TCA start, ActRec* stashedAR) { if (debug) { fflush(stdout); fflush(stderr); } assertx(tc::isValidCodeAddress(start)); assertx(((uintptr_t)vmsp() & (sizeof(Cell) - 1)) == 0); assertx(((uintptr_t)vmfp() & (sizeof(Cell) - 1)) == 0); INC_TPC(enter_tc); if (Trace::moduleEnabled(Trace::ringbuffer, 1)) { auto skData = SrcKey{liveFunc(), vmpc(), liveResumed()}.toAtomicInt(); Trace::ringbufferEntry(Trace::RBTypeEnterTC, skData, (uint64_t)start); } tl_regState = VMRegState::DIRTY; enterTCImpl(start, stashedAR); tl_regState = VMRegState::CLEAN; assertx(isValidVMStackAddress(vmsp())); vmfp() = nullptr; }
void throwable_init(ObjectData* throwable) { assertx(is_throwable(throwable)); assertx(throwable_has_expected_props()); auto trace = HHVM_FN(debug_backtrace)(exception_get_trace_options()); cellMove( make_tv<KindOfArray>(trace.detach()), throwable->propVec()[s_traceIdx]); VMRegAnchor _; auto const fp = vmfp(); if (UNLIKELY(!fp)) return; if (UNLIKELY(fp->func()->isBuiltin())) { throwable_init_file_and_line_from_builtin(throwable); } else { assertx(throwable->propVec()[s_fileIdx].m_type == KindOfNull); assertx(throwable->propVec()[s_lineIdx].m_type == KindOfNull); auto const unit = fp->func()->unit(); auto const file = const_cast<StringData*>(unit->filepath()); auto const line = unit->getLineNumber(unit->offsetOf(vmpc())); cellDup(make_tv<KindOfString>(file), throwable->propVec()[s_fileIdx]); cellDup(make_tv<KindOfInt64>(line), throwable->propVec()[s_lineIdx]); } }
Array createBacktrace(const BacktraceArgs& btArgs) { auto bt = Array::Create(); // If there is a parser frame, put it at the beginning of the backtrace. if (btArgs.m_parserFrame) { bt.append( make_map_array( s_file, btArgs.m_parserFrame->filename, s_line, btArgs.m_parserFrame->lineNumber ) ); } VMRegAnchor _; // If there are no VM frames, we're done. if (!rds::header() || !vmfp()) return bt; int depth = 0; ActRec* fp = nullptr; Offset pc = 0; // Get the fp and pc of the top frame (possibly skipping one frame). if (btArgs.m_skipTop) { fp = getPrevActRec(vmfp(), &pc); // We skipped over the only VM frame, we're done. if (!fp) return bt; } else { fp = vmfp(); auto const unit = fp->func()->unit(); assert(unit); pc = unit->offsetOf(vmpc()); } // Handle the top frame. if (btArgs.m_withSelf) { // Builtins don't have a file and line number. if (!fp->func()->isBuiltin()) { auto const unit = fp->func()->unit(); assert(unit); auto const filename = fp->func()->filename(); ArrayInit frame(btArgs.m_parserFrame ? 4 : 2, ArrayInit::Map{}); frame.set(s_file, Variant{const_cast<StringData*>(filename)}); frame.set(s_line, unit->getLineNumber(pc)); if (btArgs.m_parserFrame) { frame.set(s_function, s_include); frame.set(s_args, Array::Create(btArgs.m_parserFrame->filename)); } bt.append(frame.toVariant()); depth++; } } // Handle the subsequent VM frames. Offset prevPc = 0; for (auto prevFp = getPrevActRec(fp, &prevPc); fp != nullptr && (btArgs.m_limit == 0 || depth < btArgs.m_limit); fp = prevFp, pc = prevPc, prevFp = getPrevActRec(fp, &prevPc)) { // Do not capture frame for HPHP only functions. if (fp->func()->isNoInjection()) continue; ArrayInit frame(7, ArrayInit::Map{}); auto const curUnit = fp->func()->unit(); auto const curOp = *reinterpret_cast<const Op*>(curUnit->at(pc)); auto const isReturning = curOp == Op::RetC || curOp == Op::RetV || curOp == Op::CreateCont || curOp == Op::Await || fp->localsDecRefd(); // Builtins and generators don't have a file and line number if (prevFp && !prevFp->func()->isBuiltin()) { auto const prevUnit = prevFp->func()->unit(); auto prevFile = prevUnit->filepath(); if (prevFp->func()->originalFilename()) { prevFile = prevFp->func()->originalFilename(); } assert(prevFile); frame.set(s_file, Variant{const_cast<StringData*>(prevFile)}); // In the normal method case, the "saved pc" for line number printing is // pointing at the cell conversion (Unbox/Pop) instruction, not the call // itself. For multi-line calls, this instruction is associated with the // subsequent line which results in an off-by-n. We're subtracting one // in order to look up the line associated with the FCall/FCallArray // instruction. Exception handling and the other opcodes (ex. BoxR) // already do the right thing. The emitter associates object access with // the subsequent expression and this would be difficult to modify. auto const opAtPrevPc = *reinterpret_cast<const Op*>(prevUnit->at(prevPc)); Offset pcAdjust = 0; if (opAtPrevPc == Op::PopR || opAtPrevPc == Op::UnboxR || opAtPrevPc == Op::UnboxRNop) { pcAdjust = 1; } frame.set(s_line, prevFp->func()->unit()->getLineNumber(prevPc - pcAdjust)); } // Check for include. String funcname{const_cast<StringData*>(fp->func()->name())}; if (fp->func()->isClosureBody()) { // Strip the file hash from the closure name. String fullName{const_cast<StringData*>(fp->func()->baseCls()->name())}; funcname = fullName.substr(0, fullName.find(';')); } // Check for pseudomain. if (funcname.empty()) { if (!prevFp && !btArgs.m_withPseudoMain) continue; else if (!prevFp) funcname = s_main; else funcname = s_include; } frame.set(s_function, funcname); if (!funcname.same(s_include)) { // Closures have an m_this but they aren't in object context. auto ctx = arGetContextClass(fp); if (ctx != nullptr && !fp->func()->isClosureBody()) { frame.set(s_class, Variant{const_cast<StringData*>(ctx->name())}); if (fp->hasThis() && !isReturning) { if (btArgs.m_withThis) { frame.set(s_object, Object(fp->getThis())); } frame.set(s_type, s_arrow); } else { frame.set(s_type, s_double_colon); } } } bool const mayUseVV = fp->func()->attrs() & AttrMayUseVV; auto const withNames = btArgs.m_withArgNames; auto const withValues = btArgs.m_withArgValues; if (!btArgs.m_withArgNames && !btArgs.m_withArgValues) { // do nothing } else if (funcname.same(s_include)) { if (depth != 0) { auto filepath = const_cast<StringData*>(curUnit->filepath()); frame.set(s_args, make_packed_array(filepath)); } } else if (!RuntimeOption::EnableArgsInBacktraces || isReturning) { // Provide an empty 'args' array to be consistent with hphpc. frame.set(s_args, empty_array()); } else { auto args = Array::Create(); auto const nparams = fp->func()->numNonVariadicParams(); auto const nargs = fp->numArgs(); auto const nformals = std::min<int>(nparams, nargs); if (UNLIKELY(mayUseVV) && UNLIKELY(fp->hasVarEnv() && fp->getVarEnv()->getFP() != fp)) { // VarEnv is attached to eval or debugger frame, other than the current // frame. Access locals thru VarEnv. auto varEnv = fp->getVarEnv(); auto func = fp->func(); for (int i = 0; i < nformals; i++) { auto const argname = func->localVarName(i); auto const tv = varEnv->lookup(argname); Variant val; if (tv != nullptr) { // the variable hasn't been unset val = withValues ? tvAsVariant(tv) : ""; } if (withNames) { args.set(String(const_cast<StringData*>(argname)), val); } else { args.append(val); } } } else { for (int i = 0; i < nformals; i++) { Variant val = withValues ? tvAsVariant(frame_local(fp, i)) : ""; if (withNames) { auto const argname = fp->func()->localVarName(i); args.set(String(const_cast<StringData*>(argname)), val); } else { args.append(val); } } } // Builtin extra args are not stored in varenv. if (UNLIKELY(mayUseVV) && nargs > nparams && fp->hasExtraArgs()) { for (int i = nparams; i < nargs; i++) { auto arg = fp->getExtraArg(i - nparams); args.append(tvAsVariant(arg)); } } frame.set(s_args, args); } if (btArgs.m_withMetadata && !isReturning) { if (UNLIKELY(mayUseVV) && UNLIKELY(fp->hasVarEnv())) { auto tv = fp->getVarEnv()->lookup(s_86metadata.get()); if (tv != nullptr && tv->m_type != KindOfUninit) { frame.set(s_metadata, tvAsVariant(tv)); } } else { auto local = fp->func()->lookupVarId(s_86metadata.get()); if (local != kInvalidId) { auto tv = frame_local(fp, local); if (tv->m_type != KindOfUninit) { frame.set(s_metadata, tvAsVariant(tv)); } } } } bt.append(frame.toVariant()); depth++; } return bt; }
void CmdNext::onBeginInterrupt(DebuggerProxy& proxy, CmdInterrupt& interrupt) { TRACE(2, "CmdNext::onBeginInterrupt\n"); assertx(!m_complete); // Complete cmds should not be asked to do work. ActRec *fp = vmfp(); if (!fp) { // If we have no frame just wait for the next instruction to be interpreted. m_needsVMInterrupt = true; return; } PC pc = vmpc(); Unit* unit = fp->m_func->unit(); Offset offset = unit->offsetOf(pc); TRACE(2, "CmdNext: pc %p, opcode %s at '%s' offset %d\n", pc, opcodeToName(peek_op(pc)), fp->m_func->fullName()->data(), offset); int currentVMDepth = g_context->m_nesting; int currentStackDepth = proxy.getStackDepth(); TRACE(2, "CmdNext: original depth %d:%d, current depth %d:%d\n", m_vmDepth, m_stackDepth, currentVMDepth, currentStackDepth); // Where are we on the stack now vs. when we started? Breaking the answer down // into distinct variables helps the clarity of the algorithm below. bool deeper = false; bool originalDepth = false; if ((currentVMDepth == m_vmDepth) && (currentStackDepth == m_stackDepth)) { originalDepth = true; } else if ((currentVMDepth > m_vmDepth) || ((currentVMDepth == m_vmDepth) && (currentStackDepth > m_stackDepth))) { deeper = true; } m_needsVMInterrupt = false; // Will be set again below if still needed. // First consider if we've got internal breakpoints setup. These are used when // we can make an accurate prediction of where execution should flow, // eventually, and when we want to let the program run normally until we get // there. if (hasStepOuts() || hasStepResumable()) { TRACE(2, "CmdNext: checking internal breakpoint(s)\n"); if (atStepOutOffset(unit, offset)) { if (deeper) return; // Recursion TRACE(2, "CmdNext: hit step-out\n"); } else if (atStepResumableOffset(unit, offset)) { if (m_stepResumableId != getResumableId(fp)) return; TRACE(2, "CmdNext: hit step-cont\n"); // We're in the resumable we expect. This may be at a // different stack depth, though, especially if we've moved from // the original function to the resumable. Update the depth // accordingly. if (!originalDepth) { m_vmDepth = currentVMDepth; m_stackDepth = currentStackDepth; deeper = false; originalDepth = true; } } else if (interrupt.getInterruptType() == ExceptionHandler) { // Entering an exception handler may take us someplace we weren't // expecting. Adjust internal breakpoints accordingly. First case is easy. if (deeper) { TRACE(2, "CmdNext: exception handler, deeper\n"); return; } // For step-conts, we ignore handlers at the original level if we're not // in the original resumable. We don't care about exception handlers // in resumables being driven at the same level. if (hasStepResumable() && originalDepth && (m_stepResumableId != getResumableId(fp))) { TRACE(2, "CmdNext: exception handler, original depth, wrong cont\n"); return; } // Sometimes we have handlers in generated code, i.e., Continuation::next. // These just help propagate exceptions so ignore those. if (fp->m_func->line1() == 0) { TRACE(2, "CmdNext: exception handler, ignoring func with no source\n"); return; } if (fp->m_func->isBuiltin()) { TRACE(2, "CmdNext: exception handler, ignoring builtin functions\n"); return; } TRACE(2, "CmdNext: exception handler altering expected flow\n"); } else { // We have internal breakpoints setup, but we haven't hit one yet. Keep // running until we reach one. TRACE(2, "CmdNext: waiting to hit internal breakpoint...\n"); return; } // We've hit one internal breakpoint at a useful place, or decided we don't, // need them, so we can remove them all now. cleanupStepOuts(); cleanupStepResumable(); } if (interrupt.getInterruptType() == ExceptionHandler) { // If we're about to enter an exception handler we turn interrupts on to // ensure we stop when control reaches the handler. The normal logic below // will decide if we're done at that point or not. TRACE(2, "CmdNext: exception handler\n"); removeLocationFilter(); m_needsVMInterrupt = true; return; } if (m_skippingAwait) { m_skippingAwait = false; stepAfterAwait(); return; } if (deeper) { TRACE(2, "CmdNext: deeper, setup step out to get back to original line\n"); setupStepOuts(); // We can nuke the entire location filter here since we'll re-install it // when we get back to the old level. Keeping it installed may be more // efficient if we were on a large line, but there is a penalty for every // opcode executed while it's installed and that's bad if there's a lot of // code called from that line. removeLocationFilter(); return; } if (originalDepth && (m_loc == interrupt.getFileLine())) { TRACE(2, "CmdNext: not complete, still on same line\n"); stepCurrentLine(interrupt, fp, pc); return; } TRACE(2, "CmdNext: operation complete.\n"); m_complete = (decCount() == 0); if (!m_complete) { TRACE(2, "CmdNext: repeat count > 0, start fresh.\n"); onSetup(proxy, interrupt); } }
bool EventHook::RunInterceptHandler(ActRec* ar) { const Func* func = ar->func(); if (LIKELY(func->maybeIntercepted() == 0)) return true; // Intercept only original generator / async function calls, not resumption. if (ar->resumed()) return true; Variant* h = get_intercept_handler(func->fullNameStr(), &func->maybeIntercepted()); if (!h) return true; /* * In production mode, only functions that we have assumed can be * intercepted during static analysis should actually be * intercepted. */ if (RuntimeOption::RepoAuthoritative && !RuntimeOption::EvalJitEnableRenameFunction) { if (!(func->attrs() & AttrInterceptable)) { raise_error("fb_intercept was used on a non-interceptable function (%s) " "in RepoAuthoritative mode", func->fullName()->data()); } } VMRegAnchor _; PC savePc = vmpc(); Variant doneFlag = true; Variant called_on; if (ar->hasThis()) { called_on = Variant(ar->getThis()); } else if (ar->hasClass()) { // For static methods, give handler the name of called class called_on = Variant(const_cast<StringData*>(ar->getClass()->name())); } Variant intArgs = PackedArrayInit(5) .append(VarNR(ar->func()->fullName())) .append(called_on) .append(get_frame_args_with_ref(ar)) .append(h->asCArrRef()[1]) .appendRef(doneFlag) .toArray(); Variant ret = vm_call_user_func(h->asCArrRef()[0], intArgs); if (doneFlag.toBoolean()) { Offset pcOff; ActRec* outer = g_context->getPrevVMState(ar, &pcOff); frame_free_locals_inl_no_hook<true>(ar, ar->func()->numLocals()); Stack& stack = vmStack(); stack.top() = (Cell*)(ar + 1); cellDup(*ret.asCell(), *stack.allocTV()); vmfp() = outer; vmpc() = outer ? outer->func()->unit()->at(pcOff) : nullptr; return false; } vmfp() = ar; vmpc() = savePc; return true; }
Array createBacktrace(const BacktraceArgs& btArgs) { Array bt = Array::Create(); // If there is a parser frame, put it at the beginning of // the backtrace if (btArgs.m_parserFrame) { bt.append( make_map_array( s_file, btArgs.m_parserFrame->filename, s_line, btArgs.m_parserFrame->lineNumber ) ); } VMRegAnchor _; if (!vmfp()) { // If there are no VM frames, we're done return bt; } int depth = 0; ActRec* fp = nullptr; Offset pc = 0; // Get the fp and pc of the top frame (possibly skipping one frame) { if (btArgs.m_skipTop) { fp = g_context->getPrevVMState(vmfp(), &pc); if (!fp) { // We skipped over the only VM frame, we're done return bt; } } else { fp = vmfp(); Unit *unit = vmfp()->m_func->unit(); assert(unit); pc = unit->offsetOf(vmpc()); } // Handle the top frame if (btArgs.m_withSelf) { // Builtins don't have a file and line number if (!fp->m_func->isBuiltin()) { Unit* unit = fp->m_func->unit(); assert(unit); const char* filename = fp->m_func->filename()->data(); Offset off = pc; ArrayInit frame(btArgs.m_parserFrame ? 4 : 2, ArrayInit::Map{}); frame.set(s_file, filename); frame.set(s_line, unit->getLineNumber(off)); if (btArgs.m_parserFrame) { frame.set(s_function, s_include); frame.set(s_args, Array::Create(btArgs.m_parserFrame->filename)); } bt.append(frame.toVariant()); depth++; } } } // Handle the subsequent VM frames Offset prevPc = 0; for (ActRec* prevFp = g_context->getPrevVMState(fp, &prevPc); fp != nullptr && (btArgs.m_limit == 0 || depth < btArgs.m_limit); fp = prevFp, pc = prevPc, prevFp = g_context->getPrevVMState(fp, &prevPc)) { // do not capture frame for HPHP only functions if (fp->m_func->isNoInjection()) { continue; } ArrayInit frame(7, ArrayInit::Map{}); auto const curUnit = fp->m_func->unit(); auto const curOp = *reinterpret_cast<const Op*>(curUnit->at(pc)); auto const isReturning = curOp == Op::RetC || curOp == Op::RetV || curOp == Op::CreateCont || curOp == Op::Await || fp->localsDecRefd(); // Builtins and generators don't have a file and line number if (prevFp && !prevFp->m_func->isBuiltin() && !fp->resumed()) { auto const prevUnit = prevFp->m_func->unit(); auto prevFile = prevUnit->filepath(); if (prevFp->m_func->originalFilename()) { prevFile = prevFp->m_func->originalFilename(); } assert(prevFile); frame.set(s_file, const_cast<StringData*>(prevFile)); // In the normal method case, the "saved pc" for line number printing is // pointing at the cell conversion (Unbox/Pop) instruction, not the call // itself. For multi-line calls, this instruction is associated with the // subsequent line which results in an off-by-n. We're subtracting one // in order to look up the line associated with the FCall/FCallArray // instruction. Exception handling and the other opcodes (ex. BoxR) // already do the right thing. The emitter associates object access with // the subsequent expression and this would be difficult to modify. auto const opAtPrevPc = *reinterpret_cast<const Op*>(prevUnit->at(prevPc)); Offset pcAdjust = 0; if (opAtPrevPc == OpPopR || opAtPrevPc == OpUnboxR) { pcAdjust = 1; } frame.set(s_line, prevFp->m_func->unit()->getLineNumber(prevPc - pcAdjust)); } // check for include String funcname = const_cast<StringData*>(fp->m_func->name()); if (fp->m_func->isClosureBody()) { static StringData* s_closure_label = makeStaticString("{closure}"); funcname = s_closure_label; } // check for pseudomain if (funcname.empty()) { if (!prevFp) continue; funcname = s_include; } frame.set(s_function, funcname); if (!funcname.same(s_include)) { // Closures have an m_this but they aren't in object context Class* ctx = arGetContextClass(fp); if (ctx != nullptr && !fp->m_func->isClosureBody()) { frame.set(s_class, ctx->name()->data()); if (fp->hasThis() && !isReturning) { if (btArgs.m_withThis) { frame.set(s_object, Object(fp->getThis())); } frame.set(s_type, "->"); } else { frame.set(s_type, "::"); } } } Array args = Array::Create(); if (btArgs.m_ignoreArgs) { // do nothing } else if (funcname.same(s_include)) { if (depth) { args.append(const_cast<StringData*>(curUnit->filepath())); frame.set(s_args, args); } } else if (!RuntimeOption::EnableArgsInBacktraces || isReturning) { // Provide an empty 'args' array to be consistent with hphpc frame.set(s_args, args); } else { const int nparams = fp->m_func->numNonVariadicParams(); int nargs = fp->numArgs(); int nformals = std::min(nparams, nargs); if (UNLIKELY(fp->hasVarEnv() && fp->getVarEnv()->getFP() != fp)) { // VarEnv is attached to eval or debugger frame, other than the current // frame. Access locals thru VarEnv. auto varEnv = fp->getVarEnv(); auto func = fp->func(); for (int i = 0; i < nformals; i++) { TypedValue *arg = varEnv->lookup(func->localVarName(i)); args.append(tvAsVariant(arg)); } } else { for (int i = 0; i < nformals; i++) { TypedValue *arg = frame_local(fp, i); args.append(tvAsVariant(arg)); } } /* builtin extra args are not stored in varenv */ if (nargs > nparams && fp->hasExtraArgs()) { for (int i = nparams; i < nargs; i++) { TypedValue *arg = fp->getExtraArg(i - nparams); args.append(tvAsVariant(arg)); } } frame.set(s_args, args); } bt.append(frame.toVariant()); depth++; } return bt; }