99f3924872
After my rewrite to use the FunctionnEnter hook to implement intercept, there was a potential problem. If an intercept handler was intercepted, and we hadn't yet jitted the prolog for the intercept handler, and we lost the race to jit it, and the intercept handler threw an exception, the unwinder could skip some c++ frames, and fail to unwind a re-entry. This was caused by attempting to emulate the previous intercept behavior, where the original function is not included in the backtrace. I don't think thats necessary; for one thing, the intercept handler has always appeared in the backtrace, even though its (generally) not the replacement function, but a function that forwards to the replacement function. So this diff just leaves the original function's fraem in place. If the backtraces *do* turn out to be problematic, I'll fix it later in debugBacktrace.
239 linhas
7.5 KiB
C++
239 linhas
7.5 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| 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/event_hook.h"
|
|
#include "hphp/runtime/base/types.h"
|
|
#include "hphp/runtime/vm/func.h"
|
|
#include "hphp/runtime/vm/jit/translator-inline.h"
|
|
#include "hphp/runtime/base/builtin_functions.h"
|
|
#include "hphp/runtime/base/complex_types.h"
|
|
#include "hphp/runtime/ext/ext_function.h"
|
|
#include "hphp/runtime/vm/runtime.h"
|
|
|
|
namespace HPHP {
|
|
|
|
static StaticString s_args("args");
|
|
static StaticString s_enter("enter");
|
|
static StaticString s_exit("exit");
|
|
static StaticString s_exception("exception");
|
|
static StaticString s_name("name");
|
|
static StaticString s_return("return");
|
|
|
|
void EventHook::Enable() {
|
|
ThreadInfo::s_threadInfo->m_reqInjectionData.setEventHookFlag();
|
|
}
|
|
|
|
void EventHook::Disable() {
|
|
ThreadInfo::s_threadInfo->m_reqInjectionData.clearEventHookFlag();
|
|
}
|
|
|
|
void EventHook::EnableIntercept() {
|
|
ThreadInfo::s_threadInfo->m_reqInjectionData.setInterceptFlag();
|
|
}
|
|
|
|
void EventHook::DisableIntercept() {
|
|
ThreadInfo::s_threadInfo->m_reqInjectionData.clearInterceptFlag();
|
|
}
|
|
|
|
ssize_t EventHook::CheckSurprise() {
|
|
ThreadInfo* info = ThreadInfo::s_threadInfo.getNoCheck();
|
|
return check_request_surprise(info);
|
|
}
|
|
|
|
class ExecutingSetprofileCallbackGuard {
|
|
public:
|
|
ExecutingSetprofileCallbackGuard() {
|
|
g_vmContext->m_executingSetprofileCallback = true;
|
|
}
|
|
|
|
~ExecutingSetprofileCallbackGuard() {
|
|
g_vmContext->m_executingSetprofileCallback = false;
|
|
}
|
|
};
|
|
|
|
void EventHook::RunUserProfiler(const ActRec* ar, int mode) {
|
|
// Don't do anything if we are running the profiling function itself
|
|
// or if we haven't set up a profiler.
|
|
if (g_vmContext->m_executingSetprofileCallback ||
|
|
g_vmContext->m_setprofileCallback.isNull()) {
|
|
return;
|
|
}
|
|
// Don't profile 86ctor, since its an implementation detail,
|
|
// and we dont guarantee to call it
|
|
if (ar->m_func->cls() && ar->m_func == ar->m_func->cls()->getCtor() &&
|
|
Func::isSpecial(ar->m_func->name())) {
|
|
return;
|
|
}
|
|
Transl::VMRegAnchor _;
|
|
ExecutingSetprofileCallbackGuard guard;
|
|
|
|
Array params;
|
|
Array frameinfo;
|
|
|
|
if (mode == ProfileEnter) {
|
|
params.append(s_enter);
|
|
frameinfo.set(s_args, hhvm_get_frame_args(ar));
|
|
} else {
|
|
params.append(s_exit);
|
|
if (!g_vmContext->m_faults.empty()) {
|
|
Fault fault = g_vmContext->m_faults.back();
|
|
if (fault.m_faultType == Fault::UserException) {
|
|
frameinfo.set(s_exception, fault.m_userException);
|
|
}
|
|
} else if (!ar->m_func->info() &&
|
|
!ar->m_func->isGenerator()) {
|
|
// TODO (#1131400) This is wrong for builtins
|
|
frameinfo.set(s_return, tvAsCVarRef(g_vmContext->m_stack.topTV()));
|
|
}
|
|
}
|
|
|
|
params.append(VarNR(ar->m_func->fullName()));
|
|
params.append(frameinfo);
|
|
|
|
vm_call_user_func(g_vmContext->m_setprofileCallback, params);
|
|
}
|
|
|
|
static Array get_frame_args_with_ref(const ActRec* ar) {
|
|
int numParams = ar->m_func->numParams();
|
|
int numArgs = ar->numArgs();
|
|
HphpArray* retval = ArrayData::Make(numArgs);
|
|
|
|
TypedValue* local = (TypedValue*)(uintptr_t(ar) - sizeof(TypedValue));
|
|
for (int i = 0; i < numArgs; ++i) {
|
|
if (i < numParams) {
|
|
// This corresponds to one of the function's formal parameters, so it's
|
|
// on the stack.
|
|
retval->appendWithRef(tvAsCVarRef(local), false);
|
|
--local;
|
|
} else {
|
|
// This is not a formal parameter, so it's in the ExtraArgs.
|
|
retval->appendWithRef(tvAsCVarRef(ar->getExtraArg(i - numParams)), false);
|
|
}
|
|
}
|
|
|
|
return Array(retval);
|
|
}
|
|
|
|
bool EventHook::RunInterceptHandler(ActRec* ar) {
|
|
const Func* func = ar->m_func;
|
|
if (LIKELY(func->maybeIntercepted() == 0)) return true;
|
|
|
|
Variant *h = get_intercept_handler(func->fullNameRef(),
|
|
&func->maybeIntercepted());
|
|
if (!h) return true;
|
|
|
|
Transl::VMRegAnchor _;
|
|
|
|
PC savePc = g_vmContext->m_pc;
|
|
|
|
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()));
|
|
}
|
|
Array intArgs =
|
|
CREATE_VECTOR5(ar->m_func->fullNameRef(),
|
|
called_on,
|
|
get_frame_args_with_ref(ar),
|
|
h->asCArrRef()[1],
|
|
ref(doneFlag));
|
|
|
|
Variant ret = vm_call_user_func(h->asCArrRef()[0], intArgs);
|
|
if (doneFlag.toBoolean()) {
|
|
Offset pcOff;
|
|
ActRec* outer = g_vmContext->getPrevVMState(ar, &pcOff);
|
|
assert(outer);
|
|
|
|
frame_free_locals_inl_no_hook<true>(ar, ar->m_func->numLocals());
|
|
Stack& stack = g_vmContext->getStack();
|
|
stack.top() = (Cell*)(ar + 1);
|
|
tvDup(ret.asTypedValue(), stack.allocTV());
|
|
|
|
g_vmContext->m_fp = outer;
|
|
g_vmContext->m_pc = outer->m_func->unit()->at(pcOff);
|
|
|
|
return false;
|
|
}
|
|
g_vmContext->m_fp = ar;
|
|
g_vmContext->m_pc = savePc;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool EventHook::onFunctionEnter(const ActRec* ar, int funcType) {
|
|
ssize_t flags = CheckSurprise();
|
|
if (flags & RequestInjectionData::InterceptFlag &&
|
|
!RunInterceptHandler(const_cast<ActRec*>(ar))) {
|
|
return false;
|
|
}
|
|
if (flags & RequestInjectionData::EventHookFlag) {
|
|
RunUserProfiler(ar, ProfileEnter);
|
|
#ifdef HOTPROFILER
|
|
Profiler* profiler = ThreadInfo::s_threadInfo->m_profiler;
|
|
if (profiler != nullptr) {
|
|
const char* name;
|
|
switch (funcType) {
|
|
case NormalFunc:
|
|
name = ar->m_func->fullName()->data();
|
|
if (name[0] == '\0') {
|
|
// We're evaling some code for internal purposes, most
|
|
// likely getting the default value for a function parameter
|
|
name = "{internal}";
|
|
}
|
|
break;
|
|
case PseudoMain:
|
|
name = StringData::GetStaticString(
|
|
std::string("run_init::") + ar->m_func->unit()->filepath()->data())
|
|
->data();
|
|
break;
|
|
case Eval:
|
|
name = "_";
|
|
break;
|
|
default:
|
|
not_reached();
|
|
}
|
|
begin_profiler_frame(profiler, name);
|
|
}
|
|
#endif
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void EventHook::onFunctionExit(const ActRec* ar) {
|
|
#ifdef HOTPROFILER
|
|
Profiler* profiler = ThreadInfo::s_threadInfo->m_profiler;
|
|
if (profiler != nullptr) {
|
|
end_profiler_frame(profiler);
|
|
}
|
|
#endif
|
|
|
|
// If we have a pending exception, then we're in the process of unwinding
|
|
// for that exception. We avoid running more PHP code (the user profiler) and
|
|
// also avoid raising more exceptions for surprises (including the pending
|
|
// exception).
|
|
if (ThreadInfo::s_threadInfo->m_pendingException == nullptr) {
|
|
RunUserProfiler(ar, ProfileExit);
|
|
// XXX Disabled until t2329497 is fixed:
|
|
// CheckSurprise();
|
|
}
|
|
}
|
|
|
|
} // namespace HPHP
|