2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/event-hook.h"
18 #include "hphp/runtime/base/types.h"
19 #include "hphp/runtime/vm/func.h"
20 #include "hphp/runtime/vm/jit/mc-generator.h"
21 #include "hphp/runtime/vm/jit/translator-inline.h"
22 #include "hphp/runtime/base/builtin-functions.h"
23 #include "hphp/runtime/base/complex-types.h"
24 #include "hphp/runtime/ext/ext_function.h"
25 #include "hphp/runtime/vm/runtime.h"
26 #include "hphp/runtime/vm/vm-regs.h"
27 #include "hphp/runtime/base/thread-info.h"
28 #include "hphp/runtime/ext/ext_xenon.h"
29 #include "hphp/runtime/ext/asio/asio_session.h"
33 static StaticString
s_args("args");
34 static StaticString
s_enter("enter");
35 static StaticString
s_exit("exit");
36 static StaticString
s_exception("exception");
37 static StaticString
s_name("name");
38 static StaticString
s_return("return");
40 // implemented in runtime/ext/ext_hotprofiler.cpp
41 extern void begin_profiler_frame(Profiler
*p
, const char *symbol
);
42 extern void end_profiler_frame(Profiler
*p
, const char *symbol
);
44 void EventHook::Enable() {
45 ThreadInfo::s_threadInfo
->m_reqInjectionData
.setEventHookFlag();
48 void EventHook::Disable() {
49 ThreadInfo::s_threadInfo
->m_reqInjectionData
.clearEventHookFlag();
52 void EventHook::EnableAsync() {
53 ThreadInfo::s_threadInfo
->m_reqInjectionData
.setAsyncEventHookFlag();
56 void EventHook::DisableAsync() {
57 ThreadInfo::s_threadInfo
->m_reqInjectionData
.clearAsyncEventHookFlag();
60 void EventHook::EnableIntercept() {
61 ThreadInfo::s_threadInfo
->m_reqInjectionData
.setInterceptFlag();
64 void EventHook::DisableIntercept() {
65 ThreadInfo::s_threadInfo
->m_reqInjectionData
.clearInterceptFlag();
68 ssize_t
EventHook::CheckSurprise() {
69 ThreadInfo
* info
= ThreadInfo::s_threadInfo
.getNoCheck();
70 return check_request_surprise(info
);
73 ssize_t
EventHook::GetConditionFlags() {
74 return RDS::header()->conditionFlags
.load();
77 class ExecutingSetprofileCallbackGuard
{
79 ExecutingSetprofileCallbackGuard() {
80 g_context
->m_executingSetprofileCallback
= true;
83 ~ExecutingSetprofileCallbackGuard() {
84 g_context
->m_executingSetprofileCallback
= false;
90 bool shouldRunUserProfiler(const Func
* func
) {
91 // Don't do anything if we are running the profiling function itself
92 // or if we haven't set up a profiler.
93 if (g_context
->m_executingSetprofileCallback
||
94 g_context
->m_setprofileCallback
.isNull()) {
97 // Don't profile 86ctor, since its an implementation detail,
98 // and we dont guarantee to call it
99 if (func
->cls() && func
== func
->cls()->getCtor() &&
100 Func::isSpecial(func
->name())) {
106 void runUserProfilerOnFunctionEnter(const ActRec
* ar
) {
108 ExecutingSetprofileCallbackGuard guard
;
111 params
.append(s_enter
);
112 params
.append(VarNR(ar
->func()->fullName()));
115 frameinfo
.set(s_args
, hhvm_get_frame_args(ar
, 0));
116 params
.append(frameinfo
);
118 vm_call_user_func(g_context
->m_setprofileCallback
, params
);
121 void runUserProfilerOnFunctionExit(const ActRec
* ar
, const TypedValue
* retval
,
122 ObjectData
* exception
) {
124 ExecutingSetprofileCallbackGuard guard
;
127 params
.append(s_exit
);
128 params
.append(VarNR(ar
->func()->fullName()));
132 frameinfo
.set(s_return
, tvAsCVarRef(retval
));
133 } else if (exception
) {
134 frameinfo
.set(s_exception
, exception
);
136 params
.append(frameinfo
);
138 vm_call_user_func(g_context
->m_setprofileCallback
, params
);
143 static Array
get_frame_args_with_ref(const ActRec
* ar
) {
144 int numParams
= ar
->func()->numParams();
145 int numArgs
= ar
->numArgs();
147 PackedArrayInit
retArray(numArgs
);
149 auto local
= reinterpret_cast<TypedValue
*>(
150 uintptr_t(ar
) - sizeof(TypedValue
)
152 for (int i
= 0; i
< numArgs
; ++i
) {
154 // This corresponds to one of the function's formal parameters, so it's
156 retArray
.appendWithRef(tvAsCVarRef(local
));
159 // This is not a formal parameter, so it's in the ExtraArgs.
160 retArray
.appendWithRef(tvAsCVarRef(ar
->getExtraArg(i
- numParams
)));
164 return retArray
.toArray();
167 bool EventHook::RunInterceptHandler(ActRec
* ar
) {
168 const Func
* func
= ar
->func();
169 if (LIKELY(func
->maybeIntercepted() == 0)) return true;
171 // Intercept only original generator / async function calls, not resumption.
172 if (ar
->resumed()) return true;
174 Variant
* h
= get_intercept_handler(func
->fullNameStr(),
175 &func
->maybeIntercepted());
179 * In production mode, only functions that we have assumed can be
180 * intercepted during static analysis should actually be
183 if (RuntimeOption::RepoAuthoritative
&&
184 !RuntimeOption::EvalJitEnableRenameFunction
) {
185 if (!(func
->attrs() & AttrInterceptable
)) {
186 raise_error("fb_intercept was used on a non-interceptable function (%s) "
187 "in RepoAuthoritative mode", func
->fullName()->data());
195 Variant doneFlag
= true;
199 called_on
= Variant(ar
->getThis());
200 } else if (ar
->hasClass()) {
201 // For static methods, give handler the name of called class
202 called_on
= Variant(const_cast<StringData
*>(ar
->getClass()->name()));
206 .append(VarNR(ar
->func()->fullName()))
208 .append(get_frame_args_with_ref(ar
))
209 .append(h
->asCArrRef()[1])
213 Variant ret
= vm_call_user_func(h
->asCArrRef()[0], intArgs
);
214 if (doneFlag
.toBoolean()) {
216 ActRec
* outer
= g_context
->getPrevVMState(ar
, &pcOff
);
218 frame_free_locals_inl_no_hook
<true>(ar
, ar
->func()->numLocals());
219 Stack
& stack
= vmStack();
220 stack
.top() = (Cell
*)(ar
+ 1);
221 cellDup(*ret
.asCell(), *stack
.allocTV());
224 vmpc() = outer
? outer
->func()->unit()->at(pcOff
) : nullptr;
234 const char* EventHook::GetFunctionNameForProfiler(const Func
* func
,
238 case EventHook::NormalFunc
:
239 name
= func
->fullName()->data();
240 if (name
[0] == '\0') {
241 // We're evaling some code for internal purposes, most
242 // likely getting the default value for a function parameter
246 case EventHook::PseudoMain
:
247 name
= makeStaticString(
248 std::string("run_init::") + func
->unit()->filepath()->data())
251 case EventHook::Eval
:
260 void EventHook::onFunctionEnter(const ActRec
* ar
, int funcType
, ssize_t flags
) {
262 if (flags
& RequestInjectionData::XenonSignalFlag
) {
263 Xenon::getInstance().log(true);
267 if (flags
& RequestInjectionData::EventHookFlag
) {
268 if (shouldRunUserProfiler(ar
->func())) {
269 runUserProfilerOnFunctionEnter(ar
);
272 Profiler
* profiler
= ThreadInfo::s_threadInfo
->m_profiler
;
273 if (profiler
!= nullptr) {
274 begin_profiler_frame(profiler
,
275 GetFunctionNameForProfiler(ar
->func(), funcType
));
281 void EventHook::onFunctionExit(const ActRec
* ar
, const TypedValue
* retval
,
282 const Fault
* fault
, ssize_t flags
) {
284 if (flags
& RequestInjectionData::XenonSignalFlag
) {
285 Xenon::getInstance().log(false);
290 // Inlined calls normally skip the function enter and exit events. If we
291 // side exit in an inlined callee, we want to make sure to skip the exit
292 // event to avoid unbalancing the call stack.
293 if ((flags
& RequestInjectionData::EventHookFlag
) &&
294 (JIT::TCA
)ar
->m_savedRip
!= JIT::tx
->uniqueStubs
.retInlHelper
) {
296 Profiler
* profiler
= ThreadInfo::s_threadInfo
->m_profiler
;
297 if (profiler
!= nullptr) {
298 // NB: we don't have a function type flag to match what we got in
299 // onFunctionEnter. That's okay, though... we tolerate this in
301 end_profiler_frame(profiler
,
302 GetFunctionNameForProfiler(ar
->func(), NormalFunc
));
306 if (shouldRunUserProfiler(ar
->func())) {
307 if (ThreadInfo::s_threadInfo
->m_pendingException
!= nullptr) {
308 // Avoid running PHP code when exception from destructor is pending.
309 // TODO(#2329497) will not happen once CheckSurprise is used
311 runUserProfilerOnFunctionExit(ar
, retval
, nullptr);
312 } else if (fault
->m_faultType
== Fault::Type::UserException
) {
313 runUserProfilerOnFunctionExit(ar
, retval
, fault
->m_userException
);
315 // Avoid running PHP code when unwinding C++ exception.
321 bool EventHook::onFunctionCall(const ActRec
* ar
, int funcType
) {
322 ssize_t flags
= CheckSurprise();
323 if (flags
& RequestInjectionData::InterceptFlag
&&
324 !RunInterceptHandler(const_cast<ActRec
*>(ar
))) {
327 onFunctionEnter(ar
, funcType
, flags
);
331 void EventHook::onFunctionResume(const ActRec
* ar
) {
332 ssize_t flags
= CheckSurprise();
333 onFunctionEnter(ar
, EventHook::NormalFunc
, flags
);
336 void EventHook::onFunctionSuspend(const ActRec
* ar
, bool suspendingResumed
) {
337 ssize_t flags
= CheckSurprise();
338 onFunctionExit(ar
, nullptr, nullptr, flags
);
341 if ((flags
& RequestInjectionData::AsyncEventHookFlag
) &&
342 ar
->func()->isAsyncFunction()) {
343 assert(ar
->resumed());
344 auto afwh
= frame_afwh(ar
);
345 auto session
= AsioSession::Get();
346 // Blocking await @ eager execution => AsyncFunctionWaitHandle created.
347 if (!suspendingResumed
&& session
->hasOnAsyncFunctionCreateCallback()) {
348 session
->onAsyncFunctionCreate(afwh
, afwh
->getChild());
350 // Blocking await @ resumed execution => AsyncFunctionWaitHandle awaiting.
351 if (suspendingResumed
&& session
->hasOnAsyncFunctionAwaitCallback()) {
352 session
->onAsyncFunctionAwait(afwh
, afwh
->getChild());
357 void EventHook::onFunctionReturn(ActRec
* ar
, const TypedValue
& retval
) {
358 // Null out $this for the exiting function, it has been decref'd so it's
360 ar
->setThisOrClassAllowNull(nullptr);
362 // The locals are already gone. Mark them as decref'd so that if this hook
363 // fails and unwinder kicks in, it won't try to decref them again.
364 ar
->setLocalsDecRefd();
366 ssize_t flags
= CheckSurprise();
367 onFunctionExit(ar
, &retval
, nullptr, flags
);
370 if ((flags
& RequestInjectionData::AsyncEventHookFlag
) &&
371 ar
->func()->isAsyncFunction() && ar
->resumed()) {
372 auto session
= AsioSession::Get();
373 // Return @ resumed execution => AsyncFunctionWaitHandle succeeded.
374 if (session
->hasOnAsyncFunctionSuccessCallback()) {
375 auto afwh
= frame_afwh(ar
);
376 session
->onAsyncFunctionSuccess(afwh
, cellAsCVarRef(retval
));
381 void EventHook::onFunctionUnwind(const ActRec
* ar
, const Fault
& fault
) {
382 // TODO(#2329497) can't CheckSurprise() yet, unwinder unable to replace fault
383 ssize_t flags
= GetConditionFlags();
384 onFunctionExit(ar
, nullptr, &fault
, flags
);