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"
29 static StaticString
s_args("args");
30 static StaticString
s_enter("enter");
31 static StaticString
s_exit("exit");
32 static StaticString
s_exception("exception");
33 static StaticString
s_name("name");
34 static StaticString
s_return("return");
36 void EventHook::Enable() {
37 ThreadInfo::s_threadInfo
->m_reqInjectionData
.setEventHookFlag();
40 void EventHook::Disable() {
41 ThreadInfo::s_threadInfo
->m_reqInjectionData
.clearEventHookFlag();
44 void EventHook::EnableIntercept() {
45 ThreadInfo::s_threadInfo
->m_reqInjectionData
.setInterceptFlag();
48 void EventHook::DisableIntercept() {
49 ThreadInfo::s_threadInfo
->m_reqInjectionData
.clearInterceptFlag();
52 ssize_t
EventHook::CheckSurprise() {
53 ThreadInfo
* info
= ThreadInfo::s_threadInfo
.getNoCheck();
54 return check_request_surprise(info
);
57 class ExecutingSetprofileCallbackGuard
{
59 ExecutingSetprofileCallbackGuard() {
60 g_context
->m_executingSetprofileCallback
= true;
63 ~ExecutingSetprofileCallbackGuard() {
64 g_context
->m_executingSetprofileCallback
= false;
68 void EventHook::RunUserProfiler(const ActRec
* ar
, int mode
) {
69 // Don't do anything if we are running the profiling function itself
70 // or if we haven't set up a profiler.
71 if (g_context
->m_executingSetprofileCallback
||
72 g_context
->m_setprofileCallback
.isNull()) {
75 // Don't profile 86ctor, since its an implementation detail,
76 // and we dont guarantee to call it
77 if (ar
->m_func
->cls() && ar
->m_func
== ar
->m_func
->cls()->getCtor() &&
78 Func::isSpecial(ar
->m_func
->name())) {
82 ExecutingSetprofileCallbackGuard guard
;
87 if (mode
== ProfileEnter
) {
88 params
.append(s_enter
);
89 frameinfo
.set(s_args
, hhvm_get_frame_args(ar
, 0));
91 params
.append(s_exit
);
92 if (!g_context
->m_faults
.empty()) {
93 Fault fault
= g_context
->m_faults
.back();
94 if (fault
.m_faultType
== Fault::Type::UserException
) {
95 frameinfo
.set(s_exception
, fault
.m_userException
);
97 } else if (!ar
->m_func
->isCPPBuiltin() &&
99 // TODO (#1131400) This is wrong for builtins
100 frameinfo
.set(s_return
, tvAsCVarRef(g_context
->m_stack
.topTV()));
104 params
.append(VarNR(ar
->m_func
->fullName()));
105 params
.append(frameinfo
);
107 vm_call_user_func(g_context
->m_setprofileCallback
, params
);
110 static Array
get_frame_args_with_ref(const ActRec
* ar
) {
111 int numParams
= ar
->m_func
->numParams();
112 int numArgs
= ar
->numArgs();
114 PackedArrayInit
retArray(numArgs
);
116 auto local
= reinterpret_cast<TypedValue
*>(
117 uintptr_t(ar
) - sizeof(TypedValue
)
119 for (int i
= 0; i
< numArgs
; ++i
) {
121 // This corresponds to one of the function's formal parameters, so it's
123 retArray
.appendWithRef(tvAsCVarRef(local
));
126 // This is not a formal parameter, so it's in the ExtraArgs.
127 retArray
.appendWithRef(tvAsCVarRef(ar
->getExtraArg(i
- numParams
)));
131 return retArray
.toArray();
134 bool EventHook::RunInterceptHandler(ActRec
* ar
) {
135 const Func
* func
= ar
->m_func
;
136 if (LIKELY(func
->maybeIntercepted() == 0)) return true;
138 Variant
*h
= get_intercept_handler(func
->fullNameRef(),
139 &func
->maybeIntercepted());
144 PC savePc
= g_context
->m_pc
;
146 Variant doneFlag
= true;
150 called_on
= Variant(ar
->getThis());
151 } else if (ar
->hasClass()) {
152 // For static methods, give handler the name of called class
153 called_on
= Variant(const_cast<StringData
*>(ar
->getClass()->name()));
157 .append(ar
->m_func
->fullNameRef())
159 .append(get_frame_args_with_ref(ar
))
160 .append(h
->asCArrRef()[1])
164 Variant ret
= vm_call_user_func(h
->asCArrRef()[0], intArgs
);
165 if (doneFlag
.toBoolean()) {
167 ActRec
* outer
= g_context
->getPrevVMState(ar
, &pcOff
);
169 frame_free_locals_inl_no_hook
<true>(ar
, ar
->m_func
->numLocals());
170 Stack
& stack
= g_context
->getStack();
171 stack
.top() = (Cell
*)(ar
+ 1);
172 cellDup(*ret
.asCell(), *stack
.allocTV());
174 g_context
->m_fp
= outer
;
175 g_context
->m_pc
= outer
? outer
->m_func
->unit()->at(pcOff
) : nullptr;
179 g_context
->m_fp
= ar
;
180 g_context
->m_pc
= savePc
;
185 const char* EventHook::GetFunctionNameForProfiler(const ActRec
* ar
,
189 case EventHook::NormalFunc
:
190 name
= ar
->m_func
->fullName()->data();
191 if (name
[0] == '\0') {
192 // We're evaling some code for internal purposes, most
193 // likely getting the default value for a function parameter
197 case EventHook::PseudoMain
:
198 name
= makeStaticString(
199 std::string("run_init::") + ar
->m_func
->unit()->filepath()->data())
202 case EventHook::Eval
:
211 bool EventHook::onFunctionEnter(const ActRec
* ar
, int funcType
) {
212 ssize_t flags
= CheckSurprise();
213 if (flags
& RequestInjectionData::InterceptFlag
&&
214 !RunInterceptHandler(const_cast<ActRec
*>(ar
))) {
217 if (flags
& RequestInjectionData::EventHookFlag
) {
218 RunUserProfiler(ar
, ProfileEnter
);
220 Profiler
* profiler
= ThreadInfo::s_threadInfo
->m_profiler
;
221 if (profiler
!= nullptr) {
222 begin_profiler_frame(profiler
, GetFunctionNameForProfiler(ar
, funcType
));
229 void EventHook::onFunctionExit(const ActRec
* ar
) {
230 auto const inlinedRip
= JIT::tx
->uniqueStubs
.retInlHelper
;
231 if ((JIT::TCA
)ar
->m_savedRip
== inlinedRip
) {
232 // Inlined calls normally skip the function enter and exit events. If we
233 // side exit in an inlined callee, we want to make sure to skip the exit
234 // event to avoid unbalancing the call stack.
239 Profiler
* profiler
= ThreadInfo::s_threadInfo
->m_profiler
;
240 if (profiler
!= nullptr) {
241 // NB: we don't have a function type flag to match what we got in
242 // onFunctionEnter. That's okay, though... we tolerate this in
244 end_profiler_frame(profiler
, GetFunctionNameForProfiler(ar
, NormalFunc
));
248 // If we have a pending exception, then we're in the process of unwinding
249 // for that exception. We avoid running more PHP code (the user profiler) and
250 // also avoid raising more exceptions for surprises (including the pending
252 if (ThreadInfo::s_threadInfo
->m_pendingException
== nullptr) {
253 RunUserProfiler(ar
, ProfileExit
);
254 // XXX Disabled until t2329497 is fixed: