Move the unwinder out of bytecode.cpp

Move all the stack unwinding code to its own module, delete
some redundant enumerations, document a few things.  Gets rid of most
of the remnants of the old setjmp/longjmp-based implementation at the
enterVM level but doesn't do much to the unwinder itself (coming in
separate diff to hopefully be easier to review).
Esse commit está contido em:
Jordan DeLong
2013-06-04 18:12:36 -07:00
commit de sgolemon
commit b01043bf3c
10 arquivos alterados com 540 adições e 439 exclusões
-4
Ver Arquivo
@@ -650,7 +650,6 @@ public:
void pushVMState(VMState &savedVM, const ActRec* reentryAR);
void popVMState();
int hhvmPrepareThrow();
ActRec* getPrevVMState(const ActRec* fp,
Offset* prevPc = nullptr,
TypedValue** prevSp = nullptr,
@@ -659,7 +658,6 @@ public:
bool withSelf = false,
bool withThis = false,
VMParserFrame* parserFrame = nullptr);
int handleUnwind(UnwindStatus unwindType);
VarEnv* getVarEnv();
void setVar(StringData* name, TypedValue* v, bool ref);
Array getLocalDefinedVariables(int frame);
@@ -667,7 +665,6 @@ public:
HPHP::PCFilter* m_lastLocFilter;
bool m_interpreting;
bool m_dbgNoBreak;
int switchMode(bool unwindBuiltin);
void doFCall(HPHP::ActRec* ar, PC& pc);
bool doFCallArray(PC& pc);
CVarRef getEvaledArg(const StringData* val);
@@ -677,7 +674,6 @@ private:
void enterVM(TypedValue* retval, ActRec* ar);
void reenterVM(TypedValue* retval, ActRec* ar, TypedValue* savedSP);
void doFPushCuf(PC& pc, bool forward, bool safe);
void unwindBuiltinFrame();
template <bool forwarding>
void pushClsMethodImpl(Class* cls, StringData* name,
ObjectData* obj, int numArgs);
+2 -1
Ver Arquivo
@@ -21,6 +21,7 @@
#include "hphp/runtime/debugger/cmd/cmd_interrupt.h"
#include "hphp/runtime/vm/debugger_hook.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
#include "hphp/runtime/vm/unwind.h"
#include "tbb/concurrent_hash_map.h"
#include "hphp/util/logger.h"
#include "hphp/system/lib/systemlib.h"
@@ -78,7 +79,7 @@ void f_hphpd_break(bool condition /* = true */) {
if (RuntimeOption::EvalJit && !g_vmContext->m_interpreting &&
DEBUGGER_FORCE_INTR) {
TRACE(5, "switch mode\n");
throw VMSwitchModeException(true);
throw VMSwitchModeBuiltin();
}
TRACE(5, "out f_hphpd_break()\n");
}
+2 -1
Ver Arquivo
@@ -29,6 +29,7 @@
#include "hphp/runtime/base/runtime_option.h"
#include "hphp/runtime/base/intercept.h"
#include "hphp/runtime/vm/backup_gc.h"
#include "hphp/runtime/vm/unwind.h"
#include "unicode/uchar.h"
#include "unicode/utf8.h"
#include "hphp/runtime/base/file_repository.h"
@@ -1550,7 +1551,7 @@ void f_fb_enable_code_coverage() {
raise_notice("Calling fb_enable_code_coverage from a nested "
"VM instance may cause unpredicable results");
}
throw VMSwitchModeException(true);
throw VMSwitchModeBuiltin();
}
Variant f_fb_disable_code_coverage() {
+34 -397
Ver Arquivo
@@ -46,6 +46,7 @@
#include "hphp/runtime/vm/runtime.h"
#include "hphp/runtime/vm/jit/targetcache.h"
#include "hphp/runtime/vm/type_constraint.h"
#include "hphp/runtime/vm/unwind.h"
#include "hphp/runtime/vm/jit/translator-inline.h"
#include "hphp/runtime/ext/ext_string.h"
#include "hphp/runtime/ext/ext_error.h"
@@ -101,14 +102,6 @@ using Transl::tx64;
#endif
TRACE_SET_MOD(bcinterp);
namespace {
struct VMPrepareUnwind : std::exception {
const char* what() const throw() { return "VMPrepareUnwind"; }
};
}
ActRec* ActRec::arGetSfp() const {
ActRec* prevFrame = (ActRec*)m_savedRbp;
if (LIKELY(((uintptr_t)prevFrame - Util::s_stackLimit) >=
@@ -154,8 +147,6 @@ Class* arGetContextClassImpl<true>(const ActRec* ar) {
const StaticString s_call_user_func("call_user_func");
const StaticString s_call_user_func_array("call_user_func_array");
const StaticString s_hphpd_break("hphpd_break");
const StaticString s_fb_enable_code_coverage("fb_enable_code_coverage");
const StaticString s_stdclass("stdclass");
const StaticString s___call("__call");
const StaticString s___callStatic("__callStatic");
@@ -867,225 +858,6 @@ string Stack::toString(const ActRec* fp, int offset,
return os.str();
}
UnwindStatus Stack::unwindFrag(ActRec* fp, int offset,
PC& pc, Fault& fault) {
const Func* func = fp->m_func;
FTRACE(1, "unwindFrag: func {} ({})\n",
func->fullName()->data(), func->unit()->filepath()->data());
const bool unwindingGeneratorFrame = func->isGenerator();
auto const curOp = *reinterpret_cast<const Opcode*>(pc);
using namespace HPHP;
const bool unwindingReturningFrame = curOp == OpRetC || curOp == OpRetV;
TypedValue* evalTop;
if (UNLIKELY(unwindingGeneratorFrame)) {
assert(!isValidAddress((uintptr_t)fp));
evalTop = generatorStackBase(fp);
} else {
assert(isValidAddress((uintptr_t)fp));
evalTop = frameStackBase(fp);
}
assert(isValidAddress((uintptr_t)evalTop));
assert(evalTop >= m_top);
while (m_top < evalTop) {
popTV();
}
/*
* This code is repeatedly called with the same offset when an
* exception is raised and rethrown by fault handlers. This
* `faultNest' iterator is here to skip the EHEnt handlers that have
* already been run for this in-flight exception.
*/
if (const EHEnt* eh = func->findEH(offset)) {
int faultNest = 0;
for (;;) {
assert(faultNest <= fault.m_handledCount);
if (faultNest == fault.m_handledCount) {
++fault.m_handledCount;
switch (eh->m_ehtype) {
case EHEnt::EHType_Fault:
FTRACE(1, "unwindFrag: entering fault at {}: save {}\n",
eh->m_fault,
func->unit()->offsetOf(pc));
fault.m_savedRaiseOffset = func->unit()->offsetOf(pc);
pc = (uchar*)(func->unit()->entry() + eh->m_fault);
DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
return UnwindResumeVM;
case EHEnt::EHType_Catch:
// Note: we skip catch clauses if we have a pending C++ exception
// as part of our efforts to avoid running more PHP code in the
// face of such exceptions.
if ((fault.m_faultType == Fault::UserException) &&
(ThreadInfo::s_threadInfo->m_pendingException == nullptr)) {
ObjectData* obj = fault.m_userException;
for (auto& idOff : eh->m_catches) {
auto handler = func->unit()->at(idOff.second);
FTRACE(1, "unwindFrag: catch candidate {}\n", handler);
Class* cls = Unit::lookupClass(
func->unit()->lookupNamedEntityId(idOff.first)
);
if (cls && obj->instanceof(cls)) {
pc = handler;
FTRACE(1, "unwindFrag: entering catch at {}\n", pc);
DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
return UnwindResumeVM;
}
}
}
break;
}
}
if (eh->m_parentIndex != -1) {
eh = &func->ehtab()[eh->m_parentIndex];
} else {
break;
}
++faultNest;
}
}
// We found no more handlers in this frame, so the nested fault
// count starts over for the caller frame.
fault.m_handledCount = 0;
if (fp->isFromFPushCtor() && fp->hasThis()) {
fp->getThis()->setNoDestruct();
}
// A generator's locals don't live on this stack.
if (LIKELY(!unwindingGeneratorFrame)) {
/*
* If we're unwinding through a frame that's returning, it's only
* possible that its locals have already been decref'd.
*
* Here's why:
*
* - If a destructor for any of these things throws a php
* exception, it's swallowed at the dtor boundary and we keep
* running php.
*
* - If the destructor for any of these things throws a fatal,
* it's swallowed, and we set surprise flags to throw a fatal
* from now on.
*
* - If the second case happened and we have to run another
* destructor, its enter hook will throw, but it will be
* swallowed again.
*
* - Finally, the exit hook for the returning function can
* throw, but this happens last so everything is destructed.
*
*/
if (!unwindingReturningFrame) {
try {
// Note that we must convert locals and the $this to
// uninit/zero during unwind. This is because a backtrace
// from another destructing object during this unwind may try
// to read them.
frame_free_locals_unwind(fp, func->numLocals());
} catch (...) {}
}
ndiscard(func->numSlotsInFrame());
}
FTRACE(1, "unwindFrag: propagate\n");
return UnwindPropagate;
}
void Stack::unwindARFrag(ActRec* ar) {
while (m_top < (TypedValue*)ar) {
popTV();
}
}
void Stack::unwindAR(ActRec* fp, const FPIEnt* fe) {
while (true) {
TRACE(1, "unwindAR: function %s, pIdx %d\n",
fp->m_func->name()->data(), fe->m_parentIndex);
ActRec* ar;
if (LIKELY(!fp->m_func->isGenerator())) {
ar = arAtOffset(fp, -fe->m_fpOff);
} else {
// FIXME: duplicated logic from visitStackElems
TypedValue* genStackBase = generatorStackBase(fp);
ActRec* fakePrevFP =
(ActRec*)(genStackBase + fp->m_func->numSlotsInFrame());
ar = arAtOffset(fakePrevFP, -fe->m_fpOff);
}
assert((TypedValue*)ar >= m_top);
unwindARFrag(ar);
if (ar->isFromFPushCtor()) {
assert(ar->hasThis());
ar->getThis()->setNoDestruct();
}
popAR();
if (fe->m_parentIndex != -1) {
fe = &fp->m_func->fpitab()[fe->m_parentIndex];
} else {
return;
}
}
}
UnwindStatus Stack::unwindFrame(ActRec*& fp, int offset, PC& pc, Fault fault) {
VMExecutionContext* context = g_vmContext;
while (true) {
SrcKey sk(fp->m_func, offset);
SKTRACE(1, sk, "unwindFrame: func %s, offset %d fp %p\n",
fp->m_func->name()->data(),
offset, fp);
// If the exception is already propagating, if it was in any FPI
// region we already handled unwinding it the first time around.
if (fault.m_handledCount == 0) {
if (const FPIEnt *fe = fp->m_func->findFPI(offset)) {
unwindAR(fp, fe);
}
}
if (unwindFrag(fp, offset, pc, fault) == UnwindResumeVM) {
// We've kept our own copy of the Fault, because m_faults may
// change if we have a reentry during unwinding. When we're
// ready to resume, we need to replace the current fault to
// reflect any state changes we've made (handledCount, etc).
assert(!context->m_faults.empty());
context->m_faults.back() = fault;
return UnwindResumeVM;
}
ActRec *prevFp = fp->arGetSfp();
SKTRACE(1, sk, "unwindFrame: fp %p prevFp %p\n",
fp, prevFp);
if (LIKELY(!fp->m_func->isGenerator())) {
// We don't need to refcount the AR's refcounted members; that was
// taken care of in frame_free_locals, called from unwindFrag().
// If it's a generator, the AR doesn't live on this stack.
discardAR();
}
if (prevFp == fp) {
TRACE(1, "unwindFrame: reached the end of this nesting's ActRec "
"chain\n");
break;
}
// Keep the pc up to date while unwinding.
Offset prevOff = fp->m_soff + prevFp->m_func->base();
const Func *prevF = prevFp->m_func;
assert(isValidAddress((uintptr_t)prevFp) || prevF->isGenerator());
pc = prevF->unit()->at(prevOff);
fp = prevFp;
offset = prevOff;
}
return UnwindPropagate;
}
bool Stack::wouldOverflow(int numCells) const {
// The funny approach here is to validate the translator's assembly
// technique. We've aligned and sized the stack so that the high order
@@ -1792,132 +1564,57 @@ void VMExecutionContext::enterVMWork(ActRec* enterFnAr) {
}
}
// Enumeration codes for the handling of VM exceptions.
enum {
EXCEPTION_START = 0,
EXCEPTION_PROPAGATE,
EXCEPTION_RESUMEVM,
EXCEPTION_DEBUGGER
};
static void pushFault(Fault::Type t, Exception* e, const Object* o = nullptr) {
FTRACE(1, "pushing new fault: {} {} {}\n",
t == Fault::UserException ? "[user exception]" : "[cpp exception]",
e, o);
VMExecutionContext* ec = g_vmContext;
Fault fault;
fault.m_faultType = t;
if (t == Fault::UserException) {
// User object.
assert(o);
fault.m_userException = o->get();
fault.m_userException->incRefCount();
} else {
fault.m_cppException = e;
}
ec->m_faults.push_back(fault);
}
static int exception_handler() {
int longJmpType;
try {
throw;
} catch (const Object& e) {
pushFault(Fault::UserException, nullptr, &e);
longJmpType = g_vmContext->hhvmPrepareThrow();
} catch (VMSwitchModeException &e) {
longJmpType = g_vmContext->switchMode(e.unwindBuiltin());
} catch (Exception &e) {
pushFault(Fault::CppException, e.clone());
longJmpType = g_vmContext->hhvmPrepareThrow();
} catch (std::exception& e) {
pushFault(Fault::CppException,
new Exception("unexpected %s: %s", typeid(e).name(), e.what()));
longJmpType = g_vmContext->hhvmPrepareThrow();
} catch (...) {
pushFault(Fault::CppException,
new Exception("unknown exception"));
longJmpType = g_vmContext->hhvmPrepareThrow();
}
return longJmpType;
}
void VMExecutionContext::enterVM(TypedValue* retval, ActRec* ar) {
DEBUG_ONLY int faultDepth = m_faults.size();
SCOPE_EXIT { assert(m_faults.size() == faultDepth); };
m_firstAR = ar;
ar->m_savedRip = (uintptr_t)tx64->getCallToExit();
ar->m_savedRip = reinterpret_cast<uintptr_t>(tx64->getCallToExit());
assert(isReturnHelper(ar->m_savedRip));
DEBUG_ONLY int faultDepth = m_faults.size();
SCOPE_EXIT {
if (debug) assert(m_faults.size() == faultDepth);
};
/*
* TODO(#1343044): some of the structure of this code dates back to
* when it used to be setjmp/longjmp based. It is probable we could
* simplify it a little more, and maybe combine some of the logic
* with exception_handler().
*
* When an exception is propagating, each nesting of the VM is
* responsible for unwinding its portion of the execution stack, and
* finding user handlers if it is a catchable exception.
*
* This try/catch is where all this logic is centered. The actual
* unwinding happens under hhvmPrepareThrow, which returns a new
* "jumpCode" here to indicate what to do next. Either we'll enter
* the VM loop again at a user error/fault handler, or propagate the
* exception to a less-nested VM.
* unwinding happens under exception_handler in unwind.cpp, which
* returns a UnwindAction here to indicate what to do next.
*
* Either we'll enter the VM loop again at a user error/fault
* handler, or propagate the exception to a less-nested VM.
*/
int jumpCode = EXCEPTION_START;
short_jump:
bool first = true;
resume:
try {
switch (jumpCode) {
case EXCEPTION_START:
if (first) {
first = false;
if (m_fp && !ar->m_varEnv) {
enterVMPrologue(ar);
} else {
if (prepareFuncEntry(ar, m_pc)) {
enterVMWork(ar);
}
} else if (prepareFuncEntry(ar, m_pc)) {
enterVMWork(ar);
}
break;
case EXCEPTION_PROPAGATE:
// Jump out of this try/catch before throwing.
goto propagate;
case EXCEPTION_DEBUGGER:
// Triggered by switchMode() to switch VM mode
// do nothing but reenter the VM with same VM stack
/* Fallthrough */
case EXCEPTION_RESUMEVM:
} else {
enterVMWork(0);
break;
default:
NOT_REACHED();
}
} catch (const VMPrepareUnwind&) {
// This is slightly different from VMPrepareThrow, because we need
// to re-raise the exception as if it came from the same offset.
Fault fault = m_faults.back();
Offset faultPC = fault.m_savedRaiseOffset;
FTRACE(1, "unwind: restoring offset {}\n", faultPC);
assert(faultPC != kInvalidOffset);
fault.m_savedRaiseOffset = kInvalidOffset;
UnwindStatus unwindType = m_stack.unwindFrame(m_fp, faultPC, m_pc, fault);
jumpCode = handleUnwind(unwindType);
goto short_jump;
// Everything succeeded with no exception---return to the previous
// VM nesting level.
*retval = *m_stack.topTV();
m_stack.discard();
return;
} catch (...) {
assert(tl_regState == REGSTATE_CLEAN);
jumpCode = exception_handler();
assert(jumpCode != EXCEPTION_START);
goto short_jump;
always_assert(tl_regState == REGSTATE_CLEAN);
auto const action = exception_handler();
if (action == UnwindAction::ResumeVM) {
goto resume;
}
always_assert(action == UnwindAction::Propagate);
}
*retval = *m_stack.topTV();
m_stack.discard();
return;
propagate:
// Here we have to propagate an exception out of this VM's nesting
// level.
assert(m_faults.size() > 0);
Fault fault = m_faults.back();
m_faults.pop_back();
@@ -1957,17 +1654,6 @@ void VMExecutionContext::reenterVM(TypedValue* retval,
TRACE(1, "Reentry: exit fp %p pc %p\n", m_fp, m_pc);
}
int VMExecutionContext::switchMode(bool unwindBuiltin) {
if (unwindBuiltin) {
// from Jit calling a builtin, should unwind a frame, and push a
// return value on stack
tx64->sync(); // just to set tl_regState
unwindBuiltinFrame();
m_stack.pushNull();
}
return EXCEPTION_DEBUGGER;
}
void VMExecutionContext::invokeFunc(TypedValue* retval,
const Func* f,
CArrRef params,
@@ -2220,39 +1906,6 @@ void VMExecutionContext::invokeUnit(TypedValue* retval, Unit* unit) {
m_globalVarEnv, nullptr, InvokePseudoMain);
}
void VMExecutionContext::unwindBuiltinFrame() {
// Unwind the frame for a builtin. Currently only used for
// hphpd_break and fb_enable_code_coverage
assert(m_fp->m_func->info());
assert(m_fp->m_func->name()->isame(s_hphpd_break.get()) ||
m_fp->m_func->name()->isame(s_fb_enable_code_coverage.get()));
// Free any values that may be on the eval stack
TypedValue *evalTop = (TypedValue*)getFP();
while (m_stack.topTV() < evalTop) {
m_stack.popTV();
}
// Free the locals and VarEnv if there is one
frame_free_locals_inl(m_fp, m_fp->m_func->numLocals());
// Tear down the frame
Offset pc = -1;
ActRec* sfp = getPrevVMState(m_fp, &pc);
assert(pc != -1);
m_fp = sfp;
m_pc = m_fp->m_func->unit()->at(pc);
m_stack.discardAR();
}
int VMExecutionContext::hhvmPrepareThrow() {
Fault& fault = m_faults.back();
tx64->sync();
TRACE(2, "hhvmPrepareThrow: %p(\"%s\") {\n", m_fp,
m_fp->m_func->name()->data());
UnwindStatus unwindType;
unwindType = m_stack.unwindFrame(m_fp, pcOff(),
m_pc, fault);
return handleUnwind(unwindType);
}
/*
* Given a pointer to a VM frame, returns the previous VM frame in the call
* stack. This function will also pass back by reference the previous PC (if
@@ -4327,22 +3980,6 @@ inline void OPTBLD_INLINE VMExecutionContext::iopClone(PC& pc) {
tv->m_data.pobj = newobj;
}
inline int OPTBLD_INLINE
VMExecutionContext::handleUnwind(UnwindStatus unwindType) {
int longJumpType;
if (unwindType == UnwindPropagate) {
longJumpType = EXCEPTION_PROPAGATE;
if (m_nestedVMs.empty()) {
m_fp = nullptr;
m_pc = nullptr;
}
} else {
assert(unwindType == UnwindResumeVM);
longJumpType = EXCEPTION_RESUMEVM;
}
return longJumpType;
}
inline void OPTBLD_INLINE VMExecutionContext::iopExit(PC& pc) {
NEXT();
int exitCode = 0;
@@ -7553,7 +7190,7 @@ void VMExecutionContext::dispatchN(int numInstrs) {
// We are about to go back to Jit, check whether we should
// stick with interpreter
if (DEBUGGER_FORCE_INTR) {
throw VMSwitchModeException(false);
throw VMSwitchMode();
}
}
@@ -7563,7 +7200,7 @@ void VMExecutionContext::dispatchBB() {
// We are about to go back to Jit, check whether we should
// stick with interpreter
if (DEBUGGER_FORCE_INTR) {
throw VMSwitchModeException(false);
throw VMSwitchMode();
}
}
-18
Ver Arquivo
@@ -498,11 +498,6 @@ struct Fault {
Offset m_savedRaiseOffset;
};
enum UnwindStatus {
UnwindResumeVM,
UnwindPropagate,
};
// Interpreter evaluation stack.
class Stack {
TypedValue* m_elms;
@@ -526,17 +521,6 @@ private:
int offset, const TypedValue* ftop,
const std::string& prefix) const;
UnwindStatus unwindFrag(ActRec* fp, int offset, PC& pc, Fault& f);
// Pops everything between the current stack pointer and the passed ActRec*.
// It assumes everything there is values, not ActRecs.
void unwindARFrag(ActRec* ar);
// Pops everything up to and including the outermost unactivated ActRec. Since
// it's impossible to have more than one chain of nested unactivated ActRecs
// on the stack, this means that after this function returns, everything
// between the stack pointer and frame pointer is a value, Iter or local.
void unwindAR(ActRec* fp, const FPIEnt* fe);
public:
static const int sSurprisePageSize;
static const uint sMinStackElms;
@@ -547,8 +531,6 @@ public:
std::string toString(const ActRec* fp, int offset,
std::string prefix="") const;
UnwindStatus unwindFrame(ActRec*& fp, int offset, PC& pc, Fault f);
bool wouldOverflow(int numCells) const;
/*
+1 -1
Ver Arquivo
@@ -36,7 +36,7 @@
extern "C" void __register_frame(void*);
extern "C" void __deregister_frame(void*);
TRACE_SET_MOD(tunwind);
TRACE_SET_MOD(unwind);
namespace HPHP { namespace Transl {
+410
Ver Arquivo
@@ -0,0 +1,410 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include "hphp/runtime/vm/unwind.h"
#include "hphp/util/trace.h"
#include "hphp/runtime/vm/core_types.h"
#include "hphp/runtime/vm/bytecode.h"
#include "hphp/runtime/vm/func.h"
#include "hphp/runtime/vm/unit.h"
#include "hphp/runtime/vm/runtime.h"
#include "hphp/runtime/vm/debugger_hook.h"
namespace HPHP {
TRACE_SET_MOD(unwind);
namespace {
//////////////////////////////////////////////////////////////////////
UnwindAction unwindFrag(Stack& stack, ActRec* fp, int offset,
PC& pc, Fault& fault) {
const Func* func = fp->m_func;
FTRACE(1, "unwindFrag: func {} ({})\n",
func->fullName()->data(), func->unit()->filepath()->data());
const bool unwindingGeneratorFrame = func->isGenerator();
auto const curOp = *reinterpret_cast<const Opcode*>(pc);
using namespace HPHP;
const bool unwindingReturningFrame = curOp == OpRetC || curOp == OpRetV;
TypedValue* evalTop;
if (UNLIKELY(unwindingGeneratorFrame)) {
assert(!stack.isValidAddress((uintptr_t)fp));
evalTop = Stack::generatorStackBase(fp);
} else {
assert(stack.isValidAddress((uintptr_t)fp));
evalTop = Stack::frameStackBase(fp);
}
assert(stack.isValidAddress((uintptr_t)evalTop));
assert(evalTop >= stack.top());
while (stack.top() < evalTop) {
stack.popTV();
}
/*
* This code is repeatedly called with the same offset when an
* exception is raised and rethrown by fault handlers. This
* `faultNest' iterator is here to skip the EHEnt handlers that have
* already been run for this in-flight exception.
*/
if (const EHEnt* eh = func->findEH(offset)) {
int faultNest = 0;
for (;;) {
assert(faultNest <= fault.m_handledCount);
if (faultNest == fault.m_handledCount) {
++fault.m_handledCount;
switch (eh->m_ehtype) {
case EHEnt::EHType_Fault:
FTRACE(1, "unwindFrag: entering fault at {}: save {}\n",
eh->m_fault,
func->unit()->offsetOf(pc));
fault.m_savedRaiseOffset = func->unit()->offsetOf(pc);
pc = (uchar*)(func->unit()->entry() + eh->m_fault);
DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
return UnwindAction::ResumeVM;
case EHEnt::EHType_Catch:
// Note: we skip catch clauses if we have a pending C++ exception
// as part of our efforts to avoid running more PHP code in the
// face of such exceptions.
if ((fault.m_faultType == Fault::UserException) &&
(ThreadInfo::s_threadInfo->m_pendingException == nullptr)) {
ObjectData* obj = fault.m_userException;
for (auto& idOff : eh->m_catches) {
auto handler = func->unit()->at(idOff.second);
FTRACE(1, "unwindFrag: catch candidate {}\n", handler);
Class* cls = Unit::lookupClass(
func->unit()->lookupNamedEntityId(idOff.first)
);
if (cls && obj->instanceof(cls)) {
pc = handler;
FTRACE(1, "unwindFrag: entering catch at {}\n", pc);
DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
return UnwindAction::ResumeVM;
}
}
}
break;
}
}
if (eh->m_parentIndex != -1) {
eh = &func->ehtab()[eh->m_parentIndex];
} else {
break;
}
++faultNest;
}
}
// We found no more handlers in this frame, so the nested fault
// count starts over for the caller frame.
fault.m_handledCount = 0;
if (fp->isFromFPushCtor() && fp->hasThis()) {
fp->getThis()->setNoDestruct();
}
// A generator's locals don't live on this stack.
if (LIKELY(!unwindingGeneratorFrame)) {
/*
* If we're unwinding through a frame that's returning, it's only
* possible that its locals have already been decref'd.
*
* Here's why:
*
* - If a destructor for any of these things throws a php
* exception, it's swallowed at the dtor boundary and we keep
* running php.
*
* - If the destructor for any of these things throws a fatal,
* it's swallowed, and we set surprise flags to throw a fatal
* from now on.
*
* - If the second case happened and we have to run another
* destructor, its enter hook will throw, but it will be
* swallowed again.
*
* - Finally, the exit hook for the returning function can
* throw, but this happens last so everything is destructed.
*
*/
if (!unwindingReturningFrame) {
try {
// Note that we must convert locals and the $this to
// uninit/zero during unwind. This is because a backtrace
// from another destructing object during this unwind may try
// to read them.
frame_free_locals_unwind(fp, func->numLocals());
} catch (...) {}
}
stack.ndiscard(func->numSlotsInFrame());
}
FTRACE(1, "unwindFrag: propagate\n");
return UnwindAction::Propagate;
}
// Pops everything between the current stack pointer and the passed ActRec*.
// It assumes everything there is values, not ActRecs.
void unwindARFrag(Stack& stack, ActRec* ar) {
while (stack.top() < (TypedValue*)ar) {
stack.popTV();
}
}
// Pops everything up to and including the outermost unactivated ActRec. Since
// it's impossible to have more than one chain of nested unactivated ActRecs
// on the stack, this means that after this function returns, everything
// between the stack pointer and frame pointer is a value, Iter or local.
void unwindAR(Stack& stack, ActRec* fp, const FPIEnt* fe) {
while (true) {
TRACE(1, "unwindAR: function %s, pIdx %d\n",
fp->m_func->name()->data(), fe->m_parentIndex);
ActRec* ar;
if (LIKELY(!fp->m_func->isGenerator())) {
ar = arAtOffset(fp, -fe->m_fpOff);
} else {
// FIXME: duplicated logic from visitStackElems
TypedValue* genStackBase = Stack::generatorStackBase(fp);
ActRec* fakePrevFP =
(ActRec*)(genStackBase + fp->m_func->numSlotsInFrame());
ar = arAtOffset(fakePrevFP, -fe->m_fpOff);
}
assert((TypedValue*)ar >= stack.top());
unwindARFrag(stack, ar);
if (ar->isFromFPushCtor()) {
assert(ar->hasThis());
ar->getThis()->setNoDestruct();
}
stack.popAR();
if (fe->m_parentIndex != -1) {
fe = &fp->m_func->fpitab()[fe->m_parentIndex];
} else {
return;
}
}
}
UnwindAction unwindFrame(Stack& stack,
ActRec*& fp,
Offset offset,
PC& pc,
Fault fault) {
VMExecutionContext* context = g_vmContext;
while (true) {
FTRACE(1, "unwindFrame: func {}, offset {} fp {}\n",
fp->m_func->name()->data(),
offset,
static_cast<void*>(fp));
// If the exception is already propagating, if it was in any FPI
// region we already handled unwinding it the first time around.
if (fault.m_handledCount == 0) {
if (const FPIEnt *fe = fp->m_func->findFPI(offset)) {
unwindAR(stack, fp, fe);
}
}
if (unwindFrag(stack, fp, offset, pc, fault) == UnwindAction::ResumeVM) {
// We've kept our own copy of the Fault, because m_faults may
// change if we have a reentry during unwinding. When we're
// ready to resume, we need to replace the current fault to
// reflect any state changes we've made (handledCount, etc).
assert(!context->m_faults.empty());
context->m_faults.back() = fault;
return UnwindAction::ResumeVM;
}
ActRec *prevFp = fp->arGetSfp();
FTRACE(1, "unwindFrame: fp {} prevFp {}\n",
static_cast<void*>(fp),
static_cast<void*>(prevFp));
if (LIKELY(!fp->m_func->isGenerator())) {
// We don't need to refcount the AR's refcounted members; that was
// taken care of in frame_free_locals, called from unwindFrag().
// If it's a generator, the AR doesn't live on this stack.
stack.discardAR();
}
if (prevFp == fp) {
TRACE(1, "unwindFrame: reached the end of this nesting's ActRec "
"chain\n");
break;
}
// Keep the pc up to date while unwinding.
Offset prevOff = fp->m_soff + prevFp->m_func->base();
const Func *prevF = prevFp->m_func;
assert(stack.isValidAddress((uintptr_t)prevFp) || prevF->isGenerator());
pc = prevF->unit()->at(prevOff);
fp = prevFp;
offset = prevOff;
}
return UnwindAction::Propagate;
}
void pushFault(Fault::Type t, Exception* e, const Object* o = nullptr) {
FTRACE(1, "pushing new fault: {} {} {}\n",
t == Fault::UserException ? "[user exception]" : "[cpp exception]",
e, o);
VMExecutionContext* ec = g_vmContext;
Fault fault;
fault.m_faultType = t;
if (t == Fault::UserException) {
// User object.
assert(o);
fault.m_userException = o->get();
fault.m_userException->incRefCount();
} else {
fault.m_cppException = e;
}
ec->m_faults.push_back(fault);
}
UnwindAction handleUnwind(UnwindAction unwindType) {
UnwindAction longJumpType;
if (unwindType == UnwindAction::Propagate) {
longJumpType = UnwindAction::Propagate;
if (g_vmContext->m_nestedVMs.empty()) {
g_vmContext->m_fp = nullptr;
g_vmContext->m_pc = nullptr;
}
} else {
assert(unwindType == UnwindAction::ResumeVM);
longJumpType = UnwindAction::ResumeVM;
}
return longJumpType;
}
UnwindAction hhvmPrepareThrow() {
Fault& fault = g_vmContext->m_faults.back();
TRACE(2, "hhvmPrepareThrow: %p(\"%s\") {\n",
g_vmContext->getFP(),
g_vmContext->getFP()->m_func->name()->data());
UnwindAction unwindType;
unwindType = unwindFrame(g_vmContext->getStack(),
g_vmContext->m_fp, // by ref
g_vmContext->pcOff(),
g_vmContext->m_pc, // by ref
fault);
return handleUnwind(unwindType);
}
const StaticString s_hphpd_break("hphpd_break");
const StaticString s_fb_enable_code_coverage("fb_enable_code_coverage");
// Unwind the frame for a builtin. Currently only used when switching
// modes for hphpd_break and fb_enable_code_coverage.
void unwindBuiltinFrame() {
auto& fp = g_vmContext->m_fp;
assert(fp->m_func->info());
assert(fp->m_func->name()->isame(s_hphpd_break.get()) ||
fp->m_func->name()->isame(s_fb_enable_code_coverage.get()));
auto& stack = g_vmContext->getStack();
// 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*>(g_vmContext->getFP());
while (stack.topTV() < evalTop) {
stack.popTV();
}
// Free the locals and VarEnv if there is one
frame_free_locals_inl(fp, fp->m_func->numLocals());
// Tear down the frame
Offset pc = -1;
ActRec* sfp = g_vmContext->getPrevVMState(fp, &pc);
assert(pc != -1);
fp = sfp;
g_vmContext->m_pc = fp->m_func->unit()->at(pc);
stack.discardAR();
}
//////////////////////////////////////////////////////////////////////
}
UnwindAction exception_handler() noexcept {
try { throw; }
/*
* Unwind (repropagating from a fault funclet) is slightly different
* from the throw case, because we need to re-raise the exception as
* if it came from the same offset to handle nested fault handlers
* correctly.
*/
catch (const VMPrepareUnwind&) {
Fault fault = g_vmContext->m_faults.back();
Offset faultPC = fault.m_savedRaiseOffset;
FTRACE(1, "unwind: restoring offset {}\n", faultPC);
assert(faultPC != kInvalidOffset);
fault.m_savedRaiseOffset = kInvalidOffset;
UnwindAction unwindType = unwindFrame(
g_vmContext->getStack(),
g_vmContext->m_fp,
faultPC,
g_vmContext->m_pc,
fault
);
return handleUnwind(unwindType);
}
catch (const Object& e) {
pushFault(Fault::UserException, nullptr, &e);
return hhvmPrepareThrow();
}
catch (VMSwitchMode&) {
return UnwindAction::ResumeVM;
}
catch (VMSwitchModeBuiltin&) {
unwindBuiltinFrame();
g_vmContext->getStack().pushNull(); // return value
return UnwindAction::ResumeVM;
}
catch (Exception& e) {
pushFault(Fault::CppException, e.clone());
return hhvmPrepareThrow();
}
catch (std::exception& e) {
pushFault(Fault::CppException,
new Exception("unexpected %s: %s", typeid(e).name(), e.what()));
return hhvmPrepareThrow();
}
catch (...) {
pushFault(Fault::CppException,
new Exception("unknown exception"));
return hhvmPrepareThrow();
}
not_reached();
}
//////////////////////////////////////////////////////////////////////
}
+90
Ver Arquivo
@@ -0,0 +1,90 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_UNWIND_H_
#define incl_HPHP_UNWIND_H_
#include "hphp/runtime/vm/core_types.h"
namespace HPHP {
//////////////////////////////////////////////////////////////////////
struct Stack;
struct ActRec;
struct Fault;
/*
* Enumerates actions that should be taken by the enterVM loop after
* unwinding an exception.
*/
enum class UnwindAction {
/*
* The exception was not handled in this nesting of the VM---it
* needs to be rethrown.
*/
Propagate,
/*
* A catch or fault handler was identified and the VM state has been
* prepared for entry to it.
*/
ResumeVM,
};
/*
* The main entry point to the unwinder.
*
* When an exception propagates up to the top-level try/catch in
* enterVM, it calls to this module to perform stack unwinding as
* appropriate. This function must be called from within the catch
* handler (it rethrows the exception to determine what to do).
*
* The returned UnwindAction instructs enterVM on how to proceed.
*/
UnwindAction exception_handler() noexcept;
//////////////////////////////////////////////////////////////////////
/*
* This exception is thrown when executing an Unwind bytecode, which
* will reraise the current fault and resume propagating it.
*/
struct VMPrepareUnwind : std::exception {
const char* what() const throw() { return "VMPrepareUnwind"; }
};
/*
* Thrown when we need to "switch modes" by re-starting the current VM
* invocation. For example, if we need to break for the debugger, or
* enable code coverage mode.
*/
struct VMSwitchMode : std::exception {
const char* what() const throw() { return "VMSwitchMode"; }
};
/*
* Same as VMSwitchMode, except for use from a builtin---the frame for
* the builtin function should be unwound before resuming the VM.
*/
struct VMSwitchModeBuiltin : std::exception {
const char* what() const throw() { return "VMSwitchModeBuiltin"; }
};
//////////////////////////////////////////////////////////////////////
}
#endif
-16
Ver Arquivo
@@ -32,9 +32,6 @@ namespace HPHP {
throw *this; \
}
/**
* Base class for all exceptions.
*/
class Exception : public std::exception {
public:
Exception(const char *fmt, ...);
@@ -89,19 +86,6 @@ public:
EXCEPTION_COMMON_IMPL(FileOpenException);
};
///////////////////////////////////////////////////////////////////////////////
class VMSwitchModeException : public Exception {
private:
bool m_unwindBuiltin;
public:
explicit VMSwitchModeException(bool unwindBuiltin)
: m_unwindBuiltin(unwindBuiltin) {}
bool unwindBuiltin() const { return m_unwindBuiltin; }
EXCEPTION_COMMON_IMPL(VMSwitchModeException);
};
///////////////////////////////////////////////////////////////////////////////
}
+1 -1
Ver Arquivo
@@ -64,7 +64,7 @@ namespace Trace {
TM(trans) \
TM(tx64) \
TM(tx64stats) \
TM(tunwind) \
TM(unwind) \
TM(txlease) \
TM(fixup) \
TM(tcspace) \