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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/async-flow-stepper.h"
19 #include "hphp/runtime/base/tv-type.h"
20 #include "hphp/runtime/ext/asio/ext_asio.h"
21 #include "hphp/runtime/ext/asio/ext_async-function-wait-handle.h"
22 #include "hphp/runtime/vm/debugger-hook.h"
23 #include "hphp/runtime/vm/vm-regs.h"
27 TRACE_SET_MOD(debuggerflow
);
29 c_WaitableWaitHandle
*objToWaitableWaitHandle(const Object
& o
) {
30 assertx(o
->instanceof(c_WaitableWaitHandle::classof()));
31 return static_cast<c_WaitableWaitHandle
*>(o
.get());
34 bool AsyncFlowStepper::isActRecOnAsyncStack(const ActRec
* target
) {
35 auto currentWaitHandle
= HHVM_FN(asio_get_running
)();
36 if (currentWaitHandle
.isNull()) {
40 objToWaitableWaitHandle(currentWaitHandle
)->getDependencyStack();
41 if (depStack
.empty()) {
44 ArrayIter
iter(depStack
);
45 ++iter
; // Skip the top frame.
46 for (; iter
; ++iter
) {
47 auto const rval
= iter
.secondRval().unboxed();
48 if (isNullType(rval
.type())) {
51 auto wh
= objToWaitableWaitHandle(tvCastToObject(rval
.tv()));
52 if (wh
->getKind() == c_Awaitable::Kind::AsyncFunction
&&
53 target
== wh
->asAsyncFunction()->actRec()) {
60 // Setup async stepping if needed.
61 void AsyncFlowStepper::setup() {
62 auto const func
= vmfp()->func();
63 auto const unit
= func
!= nullptr ? func
->unit() : nullptr;
64 if (unit
== nullptr) {
67 if (!func
->isAsyncFunction()) {
68 // Only perform async stepping inside async function.
72 auto const pc
= vmpc();
74 if (!unit
->getSourceLoc(unit
->offsetOf(pc
), source_loc
)) {
75 TRACE(5, "Could not grab the current line number\n");
78 auto const line
= source_loc
.line1
;
80 OffsetRangeVec curLineRanges
;
81 if (!unit
->getOffsetRanges(line
, curLineRanges
)) {
82 curLineRanges
.clear();
85 auto const awaitOpcodeFilter
= [](Op op
) { return op
== OpAwait
; };
86 m_stepRangeFlowFilter
.addRanges(
90 m_awaitOpcodeBreakpointFilter
.addRanges(
95 // So that we can get notified when hitting await instruction.
96 auto bpFilter
= getBreakPointFilter();
102 updateStepStartStackDepth();
103 m_stage
= AsyncStepperStage::StepOver
;
106 AsyncStepHandleOpcodeResult
AsyncFlowStepper::handleOpcode(PC pc
) {
107 auto ret
= AsyncStepHandleOpcodeResult::Unhandled
;
109 case AsyncStepperStage::Disabled
:
112 case AsyncStepperStage::StepOver
:
114 // Check if we are executing "await" instruction.
115 if (m_awaitOpcodeBreakpointFilter
.checkPC(pc
)) {
116 auto wh
= c_Awaitable::fromCell(*vmsp());
117 // Is "await" blocked?
118 if (wh
&& !wh
->isFinished()) {
119 handleBlockedAwaitOpcode(pc
);
121 // else { no special action for non-blocked await. }
122 ret
= AsyncStepHandleOpcodeResult::Handled
;
123 } else if (isCompleted(pc
)) {
125 ret
= AsyncStepHandleOpcodeResult::Completed
;
127 // else { let normal step-over logic to handle. }
131 case AsyncStepperStage::StepOverAwait
:
133 captureResumeIdAfterAwait();
134 // We have set internal breakpoint in resume block
135 // and captured the resumeId,
136 // do not need interrupt anymore.
137 RID().setDebuggerIntr(false);
138 m_stage
= AsyncStepperStage::WaitResume
;
139 ret
= AsyncStepHandleOpcodeResult::Handled
;
143 case AsyncStepperStage::WaitResume
:
145 // We are waiting for resume internal breakpoint to
146 // trigger do not let other steppers handle.
147 ret
= AsyncStepHandleOpcodeResult::Handled
;
148 if (didResumeBreakpointTrigger(pc
)) {
149 // Finished await resume, start normal step-over again.
150 updateStepStartStackDepth();
151 m_stage
= AsyncStepperStage::StepOver
;
163 void AsyncFlowStepper::handleExceptionThrown() {
164 m_isCurrentAsyncStepException
= isActRecOnAsyncStack(m_asyncResumableId
);
167 bool AsyncFlowStepper::handleExceptionHandler() {
168 // Check if the throwing exception will break out of
169 // current async stepping or not; if true we need
170 // finish the async stepping in the exception handler.
171 if (m_stage
== AsyncStepperStage::WaitResume
&&
172 m_isCurrentAsyncStepException
&&
173 !isActRecOnAsyncStack(m_asyncResumableId
)) {
180 // Called when hitting a blocked "await" opcode.
181 // Async stepping resume internal breakpoint needs
182 // two parts to check triggering:
183 // 1. m_asyncResumableId matches.
184 // 2. breakpoint pc matches.
185 // #1 is required because this async function may be called multiple times
186 // and we need m_asyncResumableId to distinguish if the triggered resume
187 // breakpoint is the one we are stepping.
188 // m_asyncResumableId uses ActRec address as id so for eager execution mode
189 // we need step over "await" opcode to get the final ActRec address on heap.
190 void AsyncFlowStepper::handleBlockedAwaitOpcode(PC pc
) {
191 TRACE(2, "AsyncFlowStepper: encountered blocking await\n");
192 setResumeInternalBreakpoint(pc
);
195 // Already in resumed execution mode.
196 m_asyncResumableId
= fp
;
197 m_stage
= AsyncStepperStage::WaitResume
;
199 // In eager execution non-resumed mode we need to step over "await" opcode
200 // then grab async function's migrated ActRec on heap as m_asyncResumableId.
201 stepOverAwaitOpcode();
202 m_stage
= AsyncStepperStage::StepOverAwait
;
206 // Used by eager execution mode to step over "await" opcode
207 // so that the async frame's ActRec is migrated to heap.
208 // This is required because async frame ActRec's address
209 // is used for m_asyncResumableId.
210 void AsyncFlowStepper::stepOverAwaitOpcode() {
211 assertx(vmfp()->func()->isAsyncFunction());
212 m_stage
= AsyncStepperStage::StepOverAwait
;
214 // Request interrupt opcode callback after "await" instruction
215 // so that we can increase to next stage.
216 RID().setDebuggerIntr(true);
219 // Should only be called immediately after "await" opcode
220 // to capture m_asyncResumableId.
221 void AsyncFlowStepper::captureResumeIdAfterAwait() {
222 auto topObj
= vmsp()->m_data
.pobj
;
223 auto wh
= static_cast<c_AsyncFunctionWaitHandle
*>(topObj
);
224 m_asyncResumableId
= wh
->actRec();
227 // Set guard internal breakpoint at async operation
228 // resume point to continue stepping.
229 void AsyncFlowStepper::setResumeInternalBreakpoint(PC pc
) {
230 assertx(decode_op(pc
) == Op::Await
);
232 auto resumeInstPc
= pc
+ instrLen(pc
);
233 assertx(vmfp()->func()->unit()->offsetOf(resumeInstPc
)
234 != InvalidAbsoluteOffset
);
236 TRACE(2, "Setup internal breakpoint after await at '%s' offset %d\n",
237 vmfp()->func()->fullName()->data(),
238 vmfp()->func()->unit()->offsetOf(resumeInstPc
));
239 m_resumeBreakpointFilter
.addPC(resumeInstPc
);
241 auto bpFilter
= getBreakPointFilter();
242 bpFilter
->addPC(resumeInstPc
);
244 m_stage
= AsyncStepperStage::WaitResume
;
247 bool AsyncFlowStepper::didResumeBreakpointTrigger(PC pc
) {
248 if (!UNLIKELY(m_resumeBreakpointFilter
.checkPC(pc
))) {
251 return m_asyncResumableId
== getAsyncResumableId(vmfp());
254 const ActRec
* AsyncFlowStepper::getAsyncResumableId(const ActRec
* fp
) {
255 assertx(fp
->resumed());
256 assertx(fp
->func()->isResumable());
260 void AsyncFlowStepper::updateStepStartStackDepth() {
261 auto& req_data
= RID();
262 int stackDepth
= req_data
.getDebuggerStackDepth();
263 if (stackDepth
== 0) {
266 m_stepStartStackDepth
= stackDepth
;
269 // Return 'true' if stepping has completed.
270 bool AsyncFlowStepper::isCompleted(PC pc
) {
271 const auto stackDisp
= getStackDisposition(m_stepStartStackDepth
);
272 // Step completes when pc is outside of step range and
273 // is not in deeper frames(e.g. step-into).
274 return stackDisp
!= StackDepthDisposition::Deeper
&&
275 !m_stepRangeFlowFilter
.checkPC(pc
);
278 void AsyncFlowStepper::reset() {
279 m_stage
= AsyncStepperStage::Disabled
;
280 m_asyncResumableId
= nullptr;
281 m_stepRangeFlowFilter
.clear();
282 m_awaitOpcodeBreakpointFilter
.clear();
283 m_resumeBreakpointFilter
.clear();