// 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 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()); }
Array XenonRequestLocalData::logAsyncStack() { VMRegAnchor _; Array bt; auto currentWaitHandle = c_ResumableWaitHandle::getRunning(vmfp()); if (currentWaitHandle == nullptr) { // if we have a nullptr, then we have no async stack to store for this log return bt; } Array depStack = currentWaitHandle->t_getdependencystack(); for (ArrayIter iter(depStack); iter; ++iter) { Array frameData; if (iter.secondRef().isNull()) { frameData.set(s_function, "<prep>", true); } else { auto wh = objToWaitableWaitHandle(iter.secondRef().toObject()); frameData.set(s_function, wh->t_getname(), true); // Async function wait handles may have a source location to add. if (wh->getKind() == c_WaitHandle::Kind::AsyncFunction) { auto afwh = static_cast<c_AsyncFunctionWaitHandle*>(wh); if (!afwh->isRunning()) { frameData.set(s_file, afwh->getFileName(), true); frameData.set(s_line, afwh->getLineNumber(), true); } } } bt.append(frameData); } return bt; }
static Array HHVM_FUNCTION(xdebug_get_declared_vars) { if (RuntimeOption::RepoAuthoritative) { raise_error("xdebug_get_declared_vars unsupported in RepoAuthoritative " "mode"); } // Grab the callee function VMRegAnchor _; // Ensure consistent state for vmfp auto func = g_context->getPrevFunc(vmfp()); if (!func) { return Array::Create(); } // Add each named local to the returned array. Note that since this function // is supposed to return all _declared_ variables in scope, which includes // variables that have been unset. auto const numNames = func->numNamedLocals(); PackedArrayInit vars(numNames); for (Id i = 0; i < numNames; ++i) { assert(func->lookupVarId(func->localVarName(i)) == i); String varname(const_cast<StringData*>(func->localVarName(i))); // Skip the internal closure "0Closure" variable if (!s_closure_varname.equal(varname)) { vars.append(varname); } } return vars.toArray(); }
void XDebugProfiler::collectFrameData(FrameData& frameData, const TypedValue* retVal) { VMRegAnchor _; // Ensure consistent state for vmfp and vmpc ActRec* fp = vmfp(); bool is_func_begin = retVal == nullptr; frameData.is_func_begin = is_func_begin; // The function reference and call file/line are stored when tracing/profiling // on function enter if ((m_tracingEnabled || m_profilingEnabled) && is_func_begin) { frameData.func = fp->func(); // Need the previous frame in order to get the call line. If we cannot // get the previous frame, default to 1 Offset offset; const ActRec* prevFp = g_context->getPrevVMState(fp, &offset); if (prevFp != nullptr) { frameData.line = prevFp->unit()->getLineNumber(offset); } else { frameData.line = 1; } } else { frameData.func = nullptr; frameData.line = 1; } // Time is stored if profiling, tracing, or collect_time is enabled, but it // only needs to be collected on function exit if profiling or if computerized // tracing output is enabled if (m_profilingEnabled || (is_func_begin && (m_collectTime || m_tracingEnabled)) || (m_tracingEnabled && (m_tracingOpts & k_XDEBUG_TRACE_COMPUTERIZED))) { frameData.time = Timer::GetCurrentTimeMicros(); } else { frameData.time = 0; } // Memory usage is stored on function begin if tracing, or if collect_memory // is enabled, or on function end if computerized tracing output is enabled if ((is_func_begin && (m_tracingEnabled || m_collectMemory)) || (m_tracingEnabled && (m_tracingOpts & k_XDEBUG_TRACE_COMPUTERIZED))) { frameData.memory_usage = MM().getStats().usage; } else { frameData.memory_usage = 0; } // If tracing is enabled, we may need to collect a serialized version of // the arguments or the return value. if (m_tracingEnabled && is_func_begin && XDEBUG_GLOBAL(CollectParams) > 0) { // TODO(#3704) This relies on xdebug_var_dump throw_not_implemented("Tracing with collect_params enabled"); } else if (m_tracingEnabled && !is_func_begin && XDEBUG_GLOBAL(CollectReturn)) { // TODO(#3704) This relies on xdebug_var_dump throw_not_implemented("Tracing with collect_return enabled"); } else { frameData.context_str = nullptr; } }
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 ); }
int DebuggerProxy::getStackDepth() { TRACE(2, "DebuggerProxy::getStackDepth\n"); int depth = 0; auto fp = vmfp(); if (!fp) return 0; fp = fp->sfp(); while (fp) { fp = fp->sfp(); depth++; } return depth; }
Variant HHVM_FUNCTION(get_class_methods, const Variant& class_or_object) { auto const cls = get_cls(class_or_object); if (!cls) return init_null(); VMRegAnchor _; auto retVal = Array::attach(MixedArray::MakeReserve(cls->numMethods())); Class::getMethodNames( cls, arGetContextClassFromBuiltin(vmfp()), retVal ); return HHVM_FN(array_values)(retVal).toArray(); }
static String HHVM_FUNCTION(xdebug_call_file) { // PHP5 xdebug returns the top-level file if the callee is top-level. auto fp = get_call_fp(); const Func *func; if (fp == nullptr) { VMRegAnchor _; func = g_context->getPrevFunc(vmfp()); assert(func); } else { func = fp->func(); } return String(const_cast<StringData*>(func->filename())); }
int DebuggerProxy::getRealStackDepth() { TRACE(2, "DebuggerProxy::getRealStackDepth\n"); int depth = 0; auto const context = g_context.getNoCheck(); auto fp = vmfp(); if (!fp) return 0; while (fp != nullptr) { fp = context->getPrevVMState(fp, nullptr, nullptr); depth++; } return depth; }
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 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; } }
void XDebugHook::onOpcode(PC pc) { auto server = XDEBUG_GLOBAL(Server); if (server == nullptr) { return; } // Likely case is that there was no break command. auto brk = server->getAndClearBreak(); if (LIKELY(brk == nullptr)) { return; } server->log("Request thread received break command"); VMRegAnchor anchor; auto const unit = vmfp()->func()->unit(); auto const line = unit->getLineNumber(unit->offsetOf(pc)); auto const filepath = const_cast<StringData*>(unit->filepath()); auto const transpath = File::TranslatePath(String(filepath)); // XDebugServer::breakpoint will send the response for the command before the // break command, but we first need to send a response for the break command. auto response = xdebug_xml_node_init("response"); server->addXmlns(*response); auto const& cmd_str = brk->getCommandStr(); auto const& trans_id = brk->getTransactionId(); // Manually add status and reason. XDebugServer still thinks we're running // because we haven't run XDebugServer::breakpoint yet. xdebug_xml_add_attribute(response, "status", "break"); xdebug_xml_add_attribute(response, "reason", "ok"); // Ditto with command, XDebugServer is tracking the command before the break. xdebug_xml_add_attribute_dup(response, "command", cmd_str.data()); xdebug_xml_add_attribute_dup(response, "transaction_id", trans_id.data()); delete brk; server->sendMessage(*response); xdebug_xml_node_dtor(response); // Now we can go into a command loop. server->breakpoint(transpath, init_null(), init_null(), line); }
void HHVM_FUNCTION(set_frame_metadata, const Variant& metadata) { VMRegAnchor _; auto fp = vmfp(); if (fp && fp->skipFrame()) fp = g_context->getPrevVMState(fp); if (UNLIKELY(!fp)) return; if (LIKELY(!(fp->func()->attrs() & AttrMayUseVV)) || LIKELY(!fp->hasVarEnv())) { auto const local = fp->func()->lookupVarId(s_86metadata.get()); if (LIKELY(local != kInvalidId)) { cellSet(*metadata.asCell(), *tvAssertCell(frame_local(fp, local))); } else { SystemLib::throwInvalidArgumentExceptionObject( "Unsupported dynamic call of set_frame_metadata()"); } } else { fp->getVarEnv()->set(s_86metadata.get(), metadata.asTypedValue()); } }
// 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 XDebugProfiler::enableTracing(const String& filename, int64_t opts) { assert(!m_tracingEnabled); // Attempt to open the passed filename. php5 xdebug doesn't enable tracing // if we cannot open the file, so we need to open it now as opposed to when we // actually do the writing in order to ensure we handle this case. We keep the // file handle open in order to ensure we can still write on tracing stop FILE* file; if (opts & k_XDEBUG_TRACE_APPEND) { file = fopen(filename.data(), "a"); } else { file = fopen(filename.data(), "w"); } // If file is null, opening the passed filename failed. php5 xdebug doesn't // do anything in this case, but we should probably notify the user if (file == nullptr) { raise_warning("xdebug profiler failed to open tracing file %s for writing.", filename.data()); return; } m_tracingEnabled = true; m_tracingStartIdx = m_nextFrameIdx; m_tracingFilename = filename; m_tracingFile = file; m_tracingOpts = opts; // If we're not at the top level, need to grab the call sites for each frame // on the stack. VMRegAnchor _; Offset offset; ActRec* fp = vmfp(); while ((fp = g_context->getPrevVMState(fp, &offset)) != nullptr) { FrameData frame; frame.func = fp->func(); frame.line = fp->unit()->getLineNumber(offset); m_tracingStartFrameData.push_back(frame); } }
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]); } }
// throws on context depth level overflows and cross-context cycles void c_WaitableWaitHandle::join() { EagerVMRegAnchor _; auto const savedFP = vmfp(); assert(!isFinished()); AsioSession* session = AsioSession::Get(); if (UNLIKELY(session->hasOnJoinCallback())) { session->onJoin(this); } // enter new asio context and set up guard that will exit once we are done session->enterContext(savedFP); auto exit_guard = folly::makeGuard([&] { session->exitContext(); }); // import this wait handle to the newly created context // throws if cross-context cycle found enterContext(session->getCurrentContextIdx()); // run queues until we are finished session->getCurrentContext()->runUntil(this); assert(isFinished()); }
static Variant eval_for_assert(ActRec* const curFP, const String& codeStr) { String prefixedCode = concat3("<?php return ", codeStr, ";"); auto const oldErrorLevel = s_option_data->assertQuietEval ? HHVM_FN(error_reporting)(Variant(0)) : 0; SCOPE_EXIT { if (s_option_data->assertQuietEval) HHVM_FN(error_reporting)(oldErrorLevel); }; auto const unit = g_context->compileEvalString(prefixedCode.get()); if (unit == nullptr) { raise_recoverable_error("Syntax error in assert()"); // Failure to compile the eval string doesn't count as an // assertion failure. return Variant(true); } if (!(curFP->func()->attrs() & AttrMayUseVV)) { throw_not_supported("assert()", "assert called from non-varenv function"); } if (!curFP->hasVarEnv()) { curFP->setVarEnv(VarEnv::createLocal(curFP)); } auto varEnv = curFP->getVarEnv(); if (curFP != vmfp()) { // If we aren't using FCallBuiltin, the stack frame of the call to assert // will be in middle of the code we are about to eval and our caller, whose // varEnv we want to use. The invokeFunc below will get very confused if // this is the case, since it will be using a varEnv that belongs to the // wrong function on the stack. So, we rebind it here, to match what // invokeFunc will expect. assert(!vmfp()->hasVarEnv()); vmfp()->setVarEnv(varEnv); varEnv->enterFP(curFP, vmfp()); } ObjectData* thiz = nullptr; Class* cls = nullptr; Class* ctx = curFP->func()->cls(); if (ctx) { if (curFP->hasThis()) { thiz = curFP->getThis(); cls = thiz->getVMClass(); } else { cls = curFP->getClass(); } } auto const func = unit->getMain(ctx); return Variant::attach( g_context->invokeFunc( func, init_null_variant, thiz, cls, varEnv, nullptr, ExecutionContext::InvokePseudoMain ) ); }
Object HHVM_FUNCTION(asio_get_running) { VMRegAnchor _; return Object{c_ResumableWaitHandle::getRunning(vmfp())}; }
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); } }
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; }
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; }
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; }
void HttpServer::runOrExitProcess() { if (StaticContentCache::TheFileCache && StructuredLog::enabled() && StructuredLog::coinflip(RuntimeOption::EvalStaticContentsLogRate)) { CacheManager::setLogger([](bool existsCheck, const std::string& name) { auto record = StructuredLogEntry{}; record.setInt("existsCheck", existsCheck); record.setStr("file", name); bool needsCppStack = true; if (!g_context.isNull()) { VMRegAnchor _; if (vmfp()) { auto const bt = createBacktrace(BacktraceArgs().withArgValues(false)); std::vector<std::string> frameStrings; std::vector<folly::StringPiece> frames; for (int i = 0; i < bt.size(); i++) { auto f = bt.rvalAt(i).toArray(); if (f.exists(s_file)) { std::string s = f.rvalAt(s_file).toString().toCppString(); if (f.exists(s_line)) { s += folly::sformat(":{}", f.rvalAt(s_line).toInt64()); } frameStrings.emplace_back(std::move(s)); frames.push_back(frameStrings.back()); } } record.setVec("stack", frames); needsCppStack = false; } } if (needsCppStack) { record.setStackTrace("stack", StackTrace{StackTrace::Force{}}); } StructuredLog::log("hhvm_file_cache", record); }); } auto startupFailure = [] (const std::string& msg) { Logger::Error(msg); Logger::Error("Shutting down due to failure(s) to bind in " "HttpServer::runAndExitProcess"); // Logger flushes itself---we don't need to run any atexit handlers // (historically we've mostly just SEGV'd while trying) ... _Exit(1); }; if (!RuntimeOption::InstanceId.empty()) { std::string msg = "Starting instance " + RuntimeOption::InstanceId; if (!RuntimeOption::DeploymentId.empty()) { msg += " from deployment " + RuntimeOption::DeploymentId; } Logger::Info(msg); } m_watchDog.start(); if (RuntimeOption::ServerPort) { if (!startServer(true)) { startupFailure("Unable to start page server"); not_reached(); } Logger::Info("page server started"); } StartTime = time(nullptr); if (RuntimeOption::AdminServerPort) { if (!startServer(false)) { startupFailure("Unable to start admin server"); not_reached(); } Logger::Info("admin server started"); } for (unsigned int i = 0; i < m_satellites.size(); i++) { std::string name = m_satellites[i]->getName(); try { m_satellites[i]->start(); Logger::Info("satellite server %s started", name.c_str()); } catch (Exception &e) { startupFailure( folly::format("Unable to start satellite server {}: {}", name, e.getMessage()).str() ); not_reached(); } } if (!Eval::Debugger::StartServer()) { startupFailure("Unable to start debugger server"); not_reached(); } else if (RuntimeOption::EnableDebuggerServer) { Logger::Info("debugger server started"); } try { InitFiniNode::ServerInit(); } catch (std::exception &e) { startupFailure( folly::sformat("Exception in InitFiniNode::ServerInit(): {}", e.what())); } { BootStats::mark("servers started"); Logger::Info("all servers started"); createPid(); Lock lock(this); BootStats::done(); // continously running until /stop is received on admin server, or // takeover is requested. while (!m_stopped) { wait(); } if (m_stopReason) { Logger::Warning("Server stopping with reason: %s\n", m_stopReason); } // if we were killed, bail out immediately if (m_killed) { Logger::Info("page server killed"); return; } } if (RuntimeOption::ServerPort) { Logger::Info("stopping page server"); m_pageServer->stop(); } onServerShutdown(); EvictFileCache(); waitForServers(); m_watchDog.waitForEnd(); playShutdownRequest(RuntimeOption::ServerCleanupRequest); hphp_process_exit(); Logger::Info("all servers stopped"); }