2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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 +----------------------------------------------------------------------+
21 #include "hphp/runtime/base/request-info.h"
22 #include "hphp/runtime/base/request-injection-data.h"
23 #include "hphp/runtime/base/surprise-flags.h"
24 #include "hphp/runtime/server/cli-server.h"
25 #include "hphp/runtime/vm/event-hook.h"
26 #include "hphp/runtime/vm/hhbc.h"
27 #include "hphp/runtime/vm/unit.h" // OffsetRangeVec
29 ///////////////////////////////////////////////////////////////////////////////
30 // This is a set of functions which are primarily called from the VM to notify
31 // the debugger about various events. Some of the implementations also interact
32 // with the VM to setup further notifications, though this is not the only place
33 // the debugger interacts directly with the VM.
37 namespace Eval
{ struct HphpdHook
; }
44 // Is this thread being debugged?
45 inline bool isDebuggerAttached(RequestInfo
* ti
= nullptr) {
46 ti
= (ti
!= nullptr) ? ti
: &RI();
47 return ti
->m_reqInjectionData
.getDebuggerAttached();
50 inline bool requestHasBreakpoints(RequestInjectionData
& rid
) {
51 return !rid
.m_breakPointFilter
.isNull() ||
52 !rid
.m_lineBreakPointFilter
.isNull() ||
53 !rid
.m_callBreakPointFilter
.isNull() ||
54 !rid
.m_retBreakPointFilter
.isNull();
57 // Executes the passed code only if there is a debugger attached to the current
59 #define DEBUGGER_ATTACHED_ONLY(code) do { \
60 if (isDebuggerAttached()) { \
65 // This flag ensures two things: first, that we stay in the interpreter and
66 // out of JIT code. Second, that phpDebuggerOpcodeHook will continue to allow
67 // debugger interrupts for every opcode executed (modulo filters.)
68 #define DEBUGGER_FORCE_INTR (RID().getDebuggerForceIntr())
70 enum class StackDepthDisposition
{
72 Shallower
, // Less than baseline.
73 Deeper
, // Greater than baseline.
76 ////////////////////////////////////////////////////////////////////////////////
78 // A hook for debugger events. Any extension can subclass this class and
79 // attach it to the thread in order to receive debugging events.
82 virtual ~DebuggerHook() {}
84 // Attempts to attach an instance of the given debugger hook to the passed
85 // thread. If no thread info is passed, the current one is used. The
86 // template parameter should be an instance of DebuggerHook. Returns true on
87 // success, false on failure (for instance, if another debugger hook is
89 template<class HookClass
>
90 static bool attach(RequestInfo
* ti
= nullptr) {
91 ti
= (ti
!= nullptr) ? ti
: &RI();
93 if (isDebuggerAttached(ti
)) {
94 // Check if this debugger hook is already attached.
95 // TODO: Ideally this wouldn't be necessary here, debuggers should
96 // be well behaved and only attach once per thread. There is at least
97 // one instance where hphpd still needs this. Remove once that's
99 return dynamic_cast<HookClass
*>(ti
->m_debuggerHook
) != nullptr;
102 // Increment the number of attached hooks.
105 auto instance
= HookClass::GetInstance();
107 // Once a debugger has attached to any request, it is the only debugger
108 // allowed to attach to subsequent requests until it has detached from
110 if (s_activeHook
== nullptr) {
111 s_activeHook
= instance
;
112 } else if (s_activeHook
!= instance
) {
116 // Attach to the thread
117 ti
->m_debuggerHook
= instance
;
120 ti
->m_reqInjectionData
.setDebuggerAttached(true);
121 if (requestHasBreakpoints(ti
->m_reqInjectionData
) ||
122 !RuntimeOption::ServerExecutionMode() || is_cli_server_mode()) {
123 ti
->m_reqInjectionData
.setJittingDisabled(true);
124 rl_typeProfileLocals
->forceInterpret
= true;
125 ti
->m_reqInjectionData
.updateJit();
130 // Event hooks need to be enabled to receive function entry and exit events.
131 // This comes at the cost of a small bit of performance, however, it makes
132 // the code dealing with line breakpoints much easier. Essentially the
133 // problem is ensuring we only break on a line breakpoint once. We can't
134 // just disable the breakpoint until we leave the site because some opcode
135 // in the site could recurse to the site. So a disable must be attached to
136 // a stack depth. This will be disabled on call to detach().
137 ti
->m_reqInjectionData
.setFlag(DebuggerHookFlag
);
142 // Attempts to set or remove the specified debugger hook as the "active" hook.
143 // The active hook is the only hook that is permitted to attach to any request
144 static bool setActiveDebuggerInstance(DebuggerHook
* hook
, bool attach
) {
147 if (s_activeHook
!= nullptr) {
148 return s_activeHook
== hook
;
154 if (s_activeHook
!= hook
) {
158 s_activeHook
= nullptr;
163 // If a hook is attached to the thread, detaches it.
164 static void detach(RequestInfo
* ti
= nullptr);
166 // Debugger events. Subclasses can override these methods to receive
168 virtual void onExceptionThrown(ObjectData
* /*exception*/) {}
169 virtual void onExceptionHandle() {}
170 virtual void onError(const ExtendedException
& /*ee*/, int /*errnum*/,
171 const std::string
& /*message*/) {}
172 virtual void onEval(const Func
* /*f*/) {}
173 virtual void onFileLoad(Unit
* /*efile*/) {}
174 virtual void onDefClass(const Class
* /*cls*/) {}
175 virtual void onDefFunc(const Func
* /*func*/) {}
176 virtual void onRegisterFuncIntercept(const String
& /*name*/) {}
178 // Called whenever the program counter is at a location that could be
179 // interesting to a debugger. Such as when have hit a registered breakpoint
180 // (regardless of type), when interrupt forcing is enabled, or when the pc is
181 // over an active line breakpoint
182 virtual void onOpcode(const unsigned char* /*pc*/) {}
184 // Called right before top-level pseudo-main enters. This may be useful for
185 // debuggers to initialize separate from an extension as they can assume
186 // other extensions are initialized.
187 virtual void onRequestInit() {}
189 // Called whenever we are breaking due to completion of a step in or step out
190 virtual void onStepInBreak(const Unit
* /*unit*/, int /*line*/) {}
191 virtual void onStepOutBreak(const Unit
* /*unit*/, int /*line*/) {}
192 virtual void onNextBreak(const Unit
* /*unit*/, int /*line*/) {}
194 // Called when we have hit a registered function entry breakpoint
195 virtual void onFuncEntryBreak(const Func
* /*f*/) {}
197 // Called when we have hit a registered function exit breakpoint
198 virtual void onFuncExitBreak(const Func
* /*f*/) {}
200 // Called when we have hit a registered line breakpoint. Even though a line
201 // spans multiple opcodes, this will only be called once per hit.
202 virtual void onLineBreak(const Unit
* /*unit*/, int /*line*/) {}
204 // The number of DebuggerHooks that are currently attached to the process.
205 // The mutex is needed because we need to perform work when we are sure there
206 // are no hooks attached.
208 static int s_numAttached
;
209 static DebuggerHook
* s_activeHook
;
212 // Returns the current hook.
213 inline DebuggerHook
* getDebuggerHook() {
214 return RI().m_debuggerHook
;
217 // Is this process being debugged? Since this is across all threads, this cannot
218 // be counted on to be accurate.
219 inline bool isDebuggerAttachedProcess() {
220 return DebuggerHook::s_numAttached
> 0;
223 ////////////////////////////////////////////////////////////////////////////////
225 // Called by the VM at various points during program execution while
226 // debugging to give the debugger a chance to act. The debugger may block
227 // execution indefinitely within one of these hooks.
228 void phpDebuggerOpcodeHook(const unsigned char* pc
);
229 void phpDebuggerRequestInitHook();
230 void phpDebuggerFuncEntryHook(const ActRec
* ar
);
231 void phpDebuggerFuncExitHook(const ActRec
* ar
);
232 void phpDebuggerExceptionThrownHook(ObjectData
* exception
);
233 void phpDebuggerExceptionHandlerHook() noexcept
;
234 void phpDebuggerErrorHook(const ExtendedException
& ee
,
236 const std::string
& message
);
237 void phpDebuggerEvalHook(const Func
* f
);
238 void phpDebuggerFileLoadHook(Unit
* efile
);
239 void phpDebuggerDefClassHook(const Class
* cls
);
240 void phpDebuggerDefFuncHook(const Func
* func
);
241 void phpDebuggerInterceptRegisterHook(const String
& name
);
243 ////////////////////////////////////////////////////////////////////////////////
245 // Commands manipulating control flow. Calling any one of these short-cicruits
248 // Continues execution until the next breakpoint or program exit
249 void phpDebuggerContinue();
251 // Steps a single line, stepping into functions if necessary. If the current
252 // site is invalid, the break will occur on the next valid opcode.
253 void phpDebuggerStepIn();
255 // Steps until the current function returns. Breaks on the opcode following the
257 void phpDebuggerStepOut();
259 // Steps a single line, stepping over functions if necessary. If the current
260 // site is invalid, the break will occur on the next valid opcode.
261 void phpDebuggerNext();
263 ////////////////////////////////////////////////////////////////////////////////
264 // Breakpoint manipulation
266 // Add breakpoints of various types
267 void phpAddBreakPoint(const Func
* f
, Offset offset
);
268 void phpAddBreakPointFuncEntry(const Func
* f
);
269 void phpAddBreakPointFuncExit(const Func
* f
);
270 // Returns false if the line is invalid
271 bool phpAddBreakPointLine(const Unit
* u
, int line
);
273 // Breakpoint removal functions.
274 // FIXME Note that internally there is a global PCFilter for all breakpoints.
275 // This is checked against every opcode to determine if we should break. While
276 // good for performance, this allows no distinction between the types of
277 // breakpoints. That is, we can never remove breakpoints from the
278 // global filter because there could be overlap.
280 // If the new style of breakpoints is used (function breakpoints and lines), we
281 // can at least prevent the appropriate breakpoint hooks from being called.
282 // This means once added, we will always interrupt on that breakpoint until the
283 // hook is detached. This isn't a huge deal as it just means we can't jit those
284 // opcodes, but it should be fixed since it's just a design issue.
285 void phpRemoveBreakPoint(const Func
* f
, Offset offset
);
286 void phpRemoveBreakPointFuncEntry(const Func
* f
);
287 void phpRemoveBreakPointFuncExit(const Func
* f
);
288 void phpRemoveBreakPointLine(const Unit
* u
, int line
);
290 bool phpHasBreakpoint(const Func
* f
, Offset offset
);
292 StackDepthDisposition
getStackDisposition(int baseline
);
294 PCFilter
* getBreakPointFilter();
295 PCFilter
* getFlowFilter();
297 String
getCurrentFilePath(int* pLine
);