std::pair<bool,Variant> DebuggerProxy::ExecutePHP(const std::string &php, String &output, int frame, int flags) { TRACE(2, "DebuggerProxy::ExecutePHP\n"); // Wire up stdout and stderr to our own string buffer so we can pass // any output back to the client. StringBuffer sb; StringBuffer *save = g_context->swapOutputBuffer(nullptr); DebuggerStdoutHook stdout_hook(sb); DebuggerLoggerHook stderr_hook(sb); auto const previousEvalOutputHook = m_evalOutputHook; if (previousEvalOutputHook != nullptr) { g_context->removeStdoutHook(previousEvalOutputHook); } m_evalOutputHook = &stdout_hook; g_context->addStdoutHook(&stdout_hook); if (flags & ExecutePHPFlagsLog) { Logger::SetThreadHook(&stderr_hook); } SCOPE_EXIT { g_context->removeStdoutHook(&stdout_hook); g_context->swapOutputBuffer(save); if (flags & ExecutePHPFlagsLog) { Logger::SetThreadHook(nullptr); } if (previousEvalOutputHook != nullptr) { g_context->addStdoutHook(previousEvalOutputHook); } m_evalOutputHook = previousEvalOutputHook; }; String code(php.c_str(), php.size(), CopyString); // We're about to start executing more PHP. This is typically done // in response to commands from the client, and the client expects // those commands to send more interrupts since, of course, the // user might want to debug the code we're about to run. If we're // already processing an interrupt, enable signal polling around // the execution of the new PHP to ensure that we can handle // signals while doing so. // // Note: we must switch the thread mode to Sticky so we block // other threads which may hit interrupts while we're running, // since nested processInterrupt() calls would normally release // other threads on the way out. assertx(m_thread == (int64_t)Process::GetThreadId()); ThreadMode origThreadMode = m_threadMode; switchThreadMode(Sticky, m_thread); if (flags & ExecutePHPFlagsAtInterrupt) enableSignalPolling(); SCOPE_EXIT { if (flags & ExecutePHPFlagsAtInterrupt) disableSignalPolling(); switchThreadMode(origThreadMode, m_thread); }; auto const ret = g_context->evalPHPDebugger(code.get(), frame); output = sb.detach(); return {ret.failed, ret.result}; }
// Handle an interrupt from the VM. void DebuggerProxy::interrupt(CmdInterrupt &cmd) { TRACE_RB(2, "DebuggerProxy::interrupt\n"); // Make any breakpoints that have passed breakable again. setBreakableForBreakpointsNotMatching(cmd); // At this point we have an interrupt, but we don't know if we're on the // thread the proxy considers "current". // NB: BreakPointReached really means we've got control of a VM thread from // the opcode hook. This could be for a breakpoint, stepping, etc. // Wait until this thread is the one this proxy wants to debug. if (!blockUntilOwn(cmd, true)) return; // We know we're on the "current" thread, so we can process any active flow // command, stop if we're at a breakpoint, handle other interrupts, etc. if (checkFlowBreak(cmd)) { // We've hit a breakpoint and now need to make sure that breakpoints // won't be hit again for this site until control leaves this site. // (Breakpoints can still get hit if control reaches this site during // a call that is part of this site because the flags are stacked.) unsetBreakableForBreakpointsMatching(cmd); while (true) { try { // We're about to send the client an interrupt and start // waiting for commands back from it. Disable signal polling // during this time, since our protocol requires that only one // thread talk to the client at a time. disableSignalPolling(); SCOPE_EXIT { enableSignalPolling(); }; processInterrupt(cmd); } catch (const DebuggerException &e) { TRACE(2, "DebuggerException from processInterrupt!\n"); switchThreadMode(Normal); throw; } catch (...) { TRACE(2, "Unknown exception from processInterrupt!\n"); assertx(false); // no other exceptions should be seen here switchThreadMode(Normal); throw; } if (cmd.getInterruptType() == PSPEnded) break; if (!m_newThread) break; // we're not switching threads switchThreadMode(Normal, m_newThread->m_id); m_newThread.reset(); blockUntilOwn(cmd, false); } } if ((m_threadMode == Normal) || (cmd.getInterruptType() == PSPEnded)) { // If the thread mode is Normal we let other threads with // interrupts go ahead and process them. We also do this when the // thread is at PSPEnded because the thread is done. switchThreadMode(Normal); } }
Variant DebuggerProxy::ExecutePHP(const std::string &php, String &output, int frame, int flags) { TRACE(2, "DebuggerProxy::ExecutePHP\n"); Variant ret; // Wire up stdout and stderr to our own string buffer so we can pass // any output back to the client. StringBuffer sb; StringBuffer *save = g_context->swapOutputBuffer(nullptr); g_context->setStdout(append_stdout, &sb); if (flags & ExecutePHPFlagsLog) { Logger::SetThreadHook(append_stderr, &sb); } try { String code(php.c_str(), php.size(), CopyString); // @TODO: enable this once task #2608250 is completed. #if 0 // We're about to start executing more PHP. This is typically done // in response to commands from the client, and the client expects // those commands to send more interrupts since, of course, the // user might want to debug the code we're about to run. If we're // already processing an interrupt, enable signal polling around // the execution of the new PHP to ensure that we can handle // signals while doing so. if (flags & ExecutePHPFlagsAtInterrupt) enableSignalPolling(); SCOPE_EXIT { if (flags & ExecutePHPFlagsAtInterrupt) disableSignalPolling(); }; #endif g_vmContext->evalPHPDebugger((TypedValue*)&ret, code.get(), frame); } catch (InvalidFunctionCallException &e) { sb.append(Debugger::ColorStderr(String(e.what()))); sb.append(Debugger::ColorStderr( "You may also need to connect to a host " "(e.g., machine connect localhost).")); } catch (Exception &e) { sb.append(Debugger::ColorStderr(String(e.what()))); } catch (Object &e) { try { sb.append(Debugger::ColorStderr(e.toString())); } catch (BadTypeConversionException &e) { sb.append(Debugger::ColorStderr (String("(object without __toString() is thrown)"))); } } catch (...) { sb.append(Debugger::ColorStderr(String("(unknown exception was thrown)"))); } g_context->setStdout(nullptr, nullptr); g_context->swapOutputBuffer(save); if (flags & ExecutePHPFlagsLog) { Logger::SetThreadHook(nullptr, nullptr); } output = sb.detach(); return ret; }
Variant DebuggerProxy::ExecutePHP(const std::string &php, String &output, int frame, bool &failed, int flags) { TRACE(2, "DebuggerProxy::ExecutePHP\n"); Variant ret; // Wire up stdout and stderr to our own string buffer so we can pass // any output back to the client. StringBuffer sb; StringBuffer *save = g_context->swapOutputBuffer(nullptr); g_context->setStdout(append_stdout, &sb); if (flags & ExecutePHPFlagsLog) { Logger::SetThreadHook(append_stderr, &sb); } String code(php.c_str(), php.size(), CopyString); // We're about to start executing more PHP. This is typically done // in response to commands from the client, and the client expects // those commands to send more interrupts since, of course, the // user might want to debug the code we're about to run. If we're // already processing an interrupt, enable signal polling around // the execution of the new PHP to ensure that we can handle // signals while doing so. // // Note: we must switch the thread mode to Sticky so we block // other threads which may hit interrupts while we're running, // since nested processInterrupt() calls would normally release // other threads on the way out. assert(m_thread == (int64_t)Process::GetThreadId()); ThreadMode origThreadMode = m_threadMode; switchThreadMode(Sticky, m_thread); if (flags & ExecutePHPFlagsAtInterrupt) enableSignalPolling(); SCOPE_EXIT { if (flags & ExecutePHPFlagsAtInterrupt) disableSignalPolling(); switchThreadMode(origThreadMode, m_thread); }; failed = g_vmContext->evalPHPDebugger((TypedValue*)&ret, code.get(), frame); g_context->setStdout(nullptr, nullptr); g_context->swapOutputBuffer(save); if (flags & ExecutePHPFlagsLog) { Logger::SetThreadHook(nullptr, nullptr); } output = sb.detach(); return ret; }