22f8788e2f
Cleanup a lot of hangs with either the debugger client or server in a variety of error conditions, mostly related to communication errors or the client or server exiting unexpectedly. One of the biggest fixes is that all cases where the client was left in a state where Ctrl-C wouldn't work have been fixed. Remove lots of little snippets of dead code. If you see a function (or small set of functions/fields) deleted then it was actually dead. I debated whether to keep throwing DebuggerClientExitException on the server, and I decided to keep it. I think it's reasonable that if you've got the server stopped and you quit the debugger that the request gets terminated rather than continuing to run. I also considered a big change to the way Ctrl-C works, but ended up staying with what was there with just a bit of cleanup. We need to guard against people banging on Ctrl-C, which is a reasonable behavior, and I think it feels pretty reasonable with the updated message. Finally, added many comments about how this stuff works.
221 linhas
7.0 KiB
C++
221 linhas
7.0 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/debugger/dummy_sandbox.h"
|
|
|
|
#include <boost/noncopyable.hpp>
|
|
|
|
#include "hphp/runtime/debugger/debugger.h"
|
|
#include "hphp/runtime/debugger/cmd/cmd_signal.h"
|
|
#include "hphp/runtime/base/program_functions.h"
|
|
#include "hphp/runtime/base/server/source_root_info.h"
|
|
#include "hphp/runtime/base/externals.h"
|
|
#include "hphp/runtime/base/hphp_system.h"
|
|
#include "hphp/util/logger.h"
|
|
#include "hphp/util/process.h"
|
|
|
|
namespace HPHP { namespace Eval {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
TRACE_SET_MOD(debugger);
|
|
|
|
DummySandbox::DummySandbox(DebuggerProxy *proxy,
|
|
const std::string &defaultPath,
|
|
const std::string &startupFile)
|
|
: m_proxy(proxy), m_defaultPath(defaultPath), m_startupFile(startupFile),
|
|
m_stopped(false),
|
|
m_signum(CmdSignal::SignalNone) {
|
|
TRACE(2, "DummySandbox::DummySandbox\n");
|
|
m_thread = new AsyncFunc<DummySandbox>(this, &DummySandbox::run);
|
|
}
|
|
|
|
bool DummySandbox::waitForEnd(int seconds) {
|
|
TRACE(2, "DummySandbox::waitForEnd\n");
|
|
bool ret = m_thread->waitForEnd(seconds);
|
|
if (ret) {
|
|
delete m_thread;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void DummySandbox::start() {
|
|
TRACE(2, "DummySandbox::start\n");
|
|
m_thread->start();
|
|
}
|
|
|
|
void DummySandbox::stop() {
|
|
TRACE(2, "DummySandbox::stop\n");
|
|
m_stopped = true;
|
|
ThreadInfo *ti = ThreadInfo::s_threadInfo.getNoCheck();
|
|
if (ti->m_reqInjectionData.getDummySandbox()) {
|
|
// called from dummy sandbox thread itself, schedule retirement
|
|
Debugger::RetireDummySandboxThread(this);
|
|
} else {
|
|
// called from worker thread, we wait for the dummySandbox to end
|
|
m_thread->waitForEnd();
|
|
// we are sure it's always created by new and this is the last thing
|
|
// on this object
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct CLISession : private boost::noncopyable {
|
|
CLISession() {
|
|
TRACE(2, "CLISession::CLISession\n");
|
|
char *argv[] = {"", nullptr};
|
|
execute_command_line_begin(1, argv, 0);
|
|
}
|
|
~CLISession() {
|
|
TRACE(2, "CLISession::~CLISession\n");
|
|
Debugger::UnregisterSandbox(g_context->getSandboxId());
|
|
ThreadInfo::s_threadInfo.getNoCheck()->
|
|
m_reqInjectionData.setDebugger(false);
|
|
execute_command_line_end(0, false, nullptr);
|
|
Eval::DebuggerClient::Shutdown();
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
static const StaticString s__SERVER("_SERVER");
|
|
|
|
void DummySandbox::run() {
|
|
TRACE(2, "DummySandbox::run\n");
|
|
ThreadInfo *ti = ThreadInfo::s_threadInfo.getNoCheck();
|
|
Debugger::RegisterThread();
|
|
ti->m_reqInjectionData.setDummySandbox(true);
|
|
while (!m_stopped) {
|
|
try {
|
|
CLISession hphpSession;
|
|
|
|
DSandboxInfo sandbox = m_proxy->getSandbox();
|
|
string msg;
|
|
if (sandbox.valid()) {
|
|
GlobalVariables *g = get_global_variables();
|
|
SourceRootInfo sri(sandbox.m_user, sandbox.m_name);
|
|
if (sandbox.m_path.empty()) {
|
|
sandbox.m_path = sri.path();
|
|
}
|
|
if (!sri.sandboxOn()) {
|
|
msg = "Invalid sandbox was specified. "
|
|
"PHP files may not be loaded properly.\n";
|
|
} else {
|
|
sri.setServerVariables(g->getRef(s__SERVER));
|
|
}
|
|
Debugger::RegisterSandbox(sandbox);
|
|
g_context->setSandboxId(sandbox.id());
|
|
|
|
char cwd[PATH_MAX];
|
|
getcwd(cwd, sizeof(cwd));
|
|
std::string doc = getStartupDoc(sandbox);
|
|
Logger::Info("Start loading startup doc '%s', pwd = '%s'",
|
|
doc.c_str(), cwd);
|
|
bool error; string errorMsg;
|
|
bool ret = hphp_invoke(g_context.getNoCheck(), doc, false, null_array,
|
|
uninit_null(), "", "", error, errorMsg, true, false,
|
|
true);
|
|
if (!ret || error) {
|
|
msg += "Unable to pre-load " + doc;
|
|
if (!errorMsg.empty()) {
|
|
msg += ": " + errorMsg;
|
|
}
|
|
}
|
|
Logger::Info("Startup doc " + doc + " loaded");
|
|
} else {
|
|
g_context->setSandboxId(m_proxy->getDummyInfo().id());
|
|
}
|
|
|
|
ti->m_reqInjectionData.setDebugger(true);
|
|
{
|
|
DebuggerDummyEnv dde;
|
|
// This is really the entire point of having the dummy sandbox. This
|
|
// fires the initial session started interrupt to the client after
|
|
// it first attaches.
|
|
Debugger::InterruptSessionStarted(nullptr, msg.c_str());
|
|
}
|
|
|
|
// Blocking until Ctrl-C is issued by end user and DebuggerProxy cannot
|
|
// find a real sandbox thread to handle it.
|
|
{
|
|
Lock lock(this);
|
|
while (!m_stopped && m_signum != CmdSignal::SignalBreak) {
|
|
wait(1);
|
|
}
|
|
if (m_stopped) {
|
|
// stopped by worker thread
|
|
break;
|
|
}
|
|
m_signum = CmdSignal::SignalNone;
|
|
}
|
|
} catch (const DebuggerClientExitException &e) {
|
|
// stopped by the dummy sandbox thread itself
|
|
break;
|
|
} catch (const DebuggerException &e) {
|
|
}
|
|
}
|
|
}
|
|
|
|
void DummySandbox::notifySignal(int signum) {
|
|
TRACE(2, "DummySandbox::notifySignal\n");
|
|
Lock lock(this);
|
|
m_signum = signum;
|
|
notify();
|
|
}
|
|
|
|
std::string DummySandbox::getStartupDoc(const DSandboxInfo &sandbox) {
|
|
TRACE(2, "DummySandbox::getStartupDoc\n");
|
|
string path;
|
|
if (!m_startupFile.empty()) {
|
|
// if relative path, prepend directory
|
|
if (m_startupFile[0] != '/' && m_startupFile[0] != '~') {
|
|
path = sandbox.m_path;
|
|
if (path.empty()) {
|
|
path = m_defaultPath;
|
|
}
|
|
}
|
|
if (!path.empty() && path[path.size() - 1] != '/') {
|
|
path += '/';
|
|
}
|
|
path += m_startupFile;
|
|
|
|
// resolving home directory
|
|
if (path[0] == '~') {
|
|
string user, home;
|
|
size_t pos = path.find('/');
|
|
if (pos == string::npos) pos = path.size();
|
|
if (pos > 1) {
|
|
user = path.substr(1, pos - 1);
|
|
}
|
|
if (user.empty()) user = sandbox.m_user;
|
|
if (user.empty() || user == Process::GetCurrentUser()) {
|
|
home = Process::GetHomeDirectory();
|
|
} else {
|
|
home = "/home/" + user + "/";
|
|
}
|
|
if (pos + 1 < path.size()) {
|
|
path = home + path.substr(pos + 1);
|
|
} else {
|
|
path = home;
|
|
}
|
|
}
|
|
}
|
|
return path;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}}
|