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/debugger/cmd/cmd_flow_control.h"
21 #include "hphp/runtime/debugger/debugger_client.h"
22 #include "hphp/runtime/vm/debugger-hook.h"
23 #include "hphp/runtime/vm/hhbc-codec.h"
24 #include "hphp/runtime/vm/vm-regs.h"
26 namespace HPHP
{ namespace Eval
{
27 ///////////////////////////////////////////////////////////////////////////////
29 TRACE_SET_MOD(debuggerflow
);
31 CmdFlowControl::~CmdFlowControl() {
32 // Remove any location filter that may have been setup.
33 removeLocationFilter();
36 void CmdFlowControl::sendImpl(DebuggerThriftBuffer
&thrift
) {
37 DebuggerCommand::sendImpl(thrift
);
38 thrift
.write(m_count
);
39 thrift
.write(m_smallStep
);
42 void CmdFlowControl::recvImpl(DebuggerThriftBuffer
&thrift
) {
43 DebuggerCommand::recvImpl(thrift
);
45 thrift
.read(m_smallStep
);
48 void CmdFlowControl::onClient(DebuggerClient
&client
) {
49 if (DebuggerCommand::displayedHelp(client
)) return;
53 if (client
.argCount() > 1) {
58 if (client
.argCount() == 1) {
59 std::string snum
= client
.argValue(1);
60 if (!DebuggerClient::IsValidNumber(snum
)) {
61 client
.error("Count needs to be a number.");
65 m_count
= atoi(snum
.c_str());
67 client
.error("Count needs to be a positive number.");
71 m_smallStep
= client
.getDebuggerClientSmallStep();
72 client
.sendToServer(this);
73 throw DebuggerConsoleExitException();
76 bool CmdFlowControl::onServer(DebuggerProxy
& /*proxy*/) {
77 // Flow control cmds do their work in onSetup() and onBeginInterrupt(), so
78 // there is no real work to do in here.
82 // Setup the flow filter on the VM context for all offsets covered by
83 // the current source line. This will short-circuit the work done in
84 // phpDebuggerOpcodeHook() and ensure we don't interrupt on this source line.
85 // We exclude continuation opcodes which transfer control out of the function,
86 // which allows cmds to get a chance to alter their behavior when those opcodes
88 void CmdFlowControl::installLocationFilterForLine(InterruptSite
*site
) {
89 // We may be stopped at a place with no source info.
90 if (!site
|| !site
->valid()) return;
91 RequestInjectionData
&rid
= ThreadInfo::s_threadInfo
->m_reqInjectionData
;
92 rid
.m_flowFilter
.clear();
93 TRACE(3, "Prepare location filter for %s:%d, unit %p:\n",
94 site
->getFile(), site
->getLine0(), site
->getUnit());
95 OffsetRangeVec ranges
;
96 const auto unit
= site
->getUnit();
98 // Get offset range for the pc only.
100 if (unit
->getOffsetRange(site
->getCurOffset(), range
)) {
101 ranges
.push_back(range
);
104 // Get offset ranges for the whole line.
105 // We use line1 here because it seems to be working better than line0
106 // in a handful of cases for our bytecode-source mapping.
107 if (!unit
->getOffsetRanges(site
->getLine1(), ranges
)) {
111 auto func
= unit
->getFunc(site
->getCurOffset());
112 if (func
->isResumable()) {
113 auto excludeResumableReturns
= [] (Op op
) {
114 return (op
!= OpYield
) &&
119 rid
.m_flowFilter
.addRanges(unit
, ranges
,
120 excludeResumableReturns
);
122 rid
.m_flowFilter
.addRanges(unit
, ranges
);
126 void CmdFlowControl::removeLocationFilter() {
127 ThreadInfo::s_threadInfo
->m_reqInjectionData
.m_flowFilter
.clear();
130 bool CmdFlowControl::hasStepOuts() {
131 return m_stepOut1
.valid() || m_stepOut2
.valid();
134 bool CmdFlowControl::atStepOutOffset(Unit
* unit
, Offset o
) {
135 return m_stepOut1
.at(unit
, o
) || m_stepOut2
.at(unit
, o
);
138 // Place internal breakpoints to get out of the current function. This may place
139 // multiple internal breakpoints, and it may place them more than one frame up.
140 // Some instructions can cause PHP to be invoked without an explicit call. A set
141 // which causes a destructor to run, a iteration init which causes an object's
142 // next() method to run, a RetC which causes destructors to run, etc. This
143 // recgonizes such cases and ensures we have internal breakpoints to cover the
144 // destination(s) of such instructions.
145 void CmdFlowControl::setupStepOuts() {
146 // Existing step outs should be cleaned up before making new ones.
147 assertx(!hasStepOuts());
149 if (!fp
) return; // No place to step out to!
152 while (!hasStepOuts()) {
153 fp
= g_context
->getPrevVMState(fp
, &returnOffset
, nullptr, &fromVMEntry
);
154 // If we've run off the top of the stack, just return having setup no
155 // step outs. This will cause cmds like Next and Out to just let the program
156 // run, which is appropriate.
158 Unit
* returnUnit
= fp
->m_func
->unit();
159 PC returnPC
= returnUnit
->at(returnOffset
);
160 TRACE(2, "CmdFlowControl::setupStepOuts: at '%s' offset %d opcode %s\n",
161 fp
->m_func
->fullName()->data(), returnOffset
,
162 opcodeToName(peek_op(returnPC
)));
163 // Don't step out to generated or builtin functions, keep looking.
164 if (fp
->m_func
->line1() == 0) continue;
165 if (fp
->m_func
->isBuiltin()) continue;
167 TRACE(2, "CmdFlowControl::setupStepOuts: VM entry\n");
168 // We only execute this for opcodes which invoke more PHP, and that does
169 // not include switches. Thus, we'll have at most two destinations.
170 auto const retOp
= peek_op(returnPC
);
171 assertx(!isSwitch(retOp
) && numSuccs(returnPC
) <= 2);
172 // Set an internal breakpoint after the instruction if it can fall thru.
173 if (instrAllowsFallThru(retOp
)) {
174 Offset nextOffset
= returnOffset
+ instrLen(returnPC
);
175 TRACE(2, "CmdFlowControl: step out to '%s' offset %d (fall-thru)\n",
176 fp
->m_func
->fullName()->data(), nextOffset
);
177 m_stepOut1
= StepDestination(returnUnit
, nextOffset
);
179 // Set an internal breakpoint at the target of a control flow instruction.
180 // A good example of a control flow op that invokes PHP is IterNext.
181 if (instrIsControlFlow(retOp
)) {
182 Offset target
= instrJumpTarget(returnPC
, 0);
183 if (target
!= InvalidAbsoluteOffset
) {
184 Offset targetOffset
= returnOffset
+ target
;
185 TRACE(2, "CmdFlowControl: step out to '%s' offset %d (jump target)\n",
186 fp
->m_func
->fullName()->data(), targetOffset
);
187 m_stepOut2
= StepDestination(returnUnit
, targetOffset
);
190 // If we have no place to step out to, then unwind another frame and try
191 // again. The most common case that leads here is Ret*, which does not
192 // fall-thru and has no encoded target.
194 TRACE(2, "CmdFlowControl: step out to '%s' offset %d\n",
195 fp
->m_func
->fullName()->data(), returnOffset
);
196 m_stepOut1
= StepDestination(returnUnit
, returnOffset
);
201 void CmdFlowControl::cleanupStepOuts() {
202 if (m_stepOut1
.valid()) {
203 m_stepOut1
= StepDestination();
205 if (m_stepOut2
.valid()) {
206 m_stepOut2
= StepDestination();
210 ///////////////////////////////////////////////////////////////////////////////
213 // NB: a StepDestination also manages an internal breakpoint at the
214 // given location. If there is already an internal breakpoint set when
215 // a StepDestination is constructed then it will not remove the
216 // breakpoint when it is destructed. The move assignment operator
217 // handles the transfer of ownership, and we delete the copy
218 // constructor/assignment operators explicitly to ensure no two
219 // StepDestinations believe they can remove the same internal
222 // We can manage internal breakpoints without a true refcount like
223 // this because no other operation can manipulate the breakpoint
224 // filter while a single flow control command is active, and because
225 // the lifetime of a StepDestination is scoped by the lifetime of its
226 // flow control command.
228 CmdFlowControl::StepDestination::StepDestination() :
229 m_unit(nullptr), m_offset(InvalidAbsoluteOffset
),
230 m_ownsInternalBreakpoint(false)
234 CmdFlowControl::StepDestination::StepDestination(const Unit
* unit
,
236 m_unit(unit
), m_offset(offset
)
238 m_ownsInternalBreakpoint
= !phpHasBreakpoint(m_unit
, m_offset
);
239 if (m_ownsInternalBreakpoint
) phpAddBreakPoint(m_unit
, m_offset
);
242 CmdFlowControl::StepDestination::StepDestination(StepDestination
&& other
) {
243 *this = std::move(other
);
246 CmdFlowControl::StepDestination
&
247 CmdFlowControl::StepDestination::operator=(StepDestination
&& other
) {
248 if (this != &other
) {
249 if (m_ownsInternalBreakpoint
) phpRemoveBreakPoint(m_unit
, m_offset
);
250 m_unit
= other
.m_unit
;
251 m_offset
= other
.m_offset
;
252 m_ownsInternalBreakpoint
= other
.m_ownsInternalBreakpoint
;
253 other
.m_unit
= nullptr;
254 other
.m_offset
= InvalidAbsoluteOffset
;
255 other
.m_ownsInternalBreakpoint
= false;
260 CmdFlowControl::StepDestination::~StepDestination() {
261 if (m_ownsInternalBreakpoint
) phpRemoveBreakPoint(m_unit
, m_offset
);
264 ///////////////////////////////////////////////////////////////////////////////