Cleanup client and server communication, events loops, and error handling.
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.
Esse commit está contido em:
@@ -265,7 +265,7 @@ will not be affected by this setting. This directory will be stored in
|
||||
configuration file for future sessions as well.
|
||||
|
||||
|
||||
-------------------------- Machine Command --------------------------
|
||||
────────────────────────── Machine Command ──────────────────────────
|
||||
|
||||
[m]achine debugging remote server natively
|
||||
[c]onnect {host}
|
||||
@@ -276,7 +276,7 @@ configuration file for future sessions as well.
|
||||
{host}
|
||||
[m]achine [r]pc debugging remote server with RPC
|
||||
{host}:{port}
|
||||
[m]achine debugging local script
|
||||
[m]achine disconnect, debug only local script
|
||||
[d]isconnect
|
||||
[m]achine [l]ist list all sandboxes
|
||||
[m]achine attach to a sandbox
|
||||
@@ -287,6 +287,10 @@ configuration file for future sessions as well.
|
||||
[m]achine attach to a sandbox by user and name
|
||||
[a]ttach {user}
|
||||
{sandbox}
|
||||
[m]achine force attach to a sandbox (see below)
|
||||
[a]ttach [f]orce
|
||||
{index|sandbox|us
|
||||
er sandbox}
|
||||
|
||||
|
||||
Use this command to switch between different machines or sandboxes.
|
||||
@@ -308,6 +312,10 @@ attach to it.
|
||||
When your sandbox is not available, please hit it at least once from
|
||||
your browser. Then run '[m]achine [l]ist' command again.
|
||||
|
||||
If another debugger client is already attached to your sandbox you can
|
||||
use the '[f]orce' option to '[m]achine [a]ttach'. This will disconnect
|
||||
the other client and force your client to connect.
|
||||
|
||||
If a HipHop server has RPC port open, one can also debug the server in a
|
||||
very special RPC mode. In this mode, one can type in PHP scripts to run,
|
||||
but all functions will be executed on server through RPC. Because states
|
||||
|
||||
@@ -1070,13 +1070,13 @@ static int execute_program_impl(int argc, char **argv) {
|
||||
if (po.mode == "debug") {
|
||||
StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
|
||||
RuntimeOption::EnableDebugger = true;
|
||||
Eval::DebuggerProxyPtr proxy =
|
||||
Eval::DebuggerProxyPtr localProxy =
|
||||
Eval::Debugger::StartClient(po.debugger_options);
|
||||
if (!proxy) {
|
||||
if (!localProxy) {
|
||||
Logger::Error("Failed to start debugger client\n\n");
|
||||
return 1;
|
||||
}
|
||||
Eval::Debugger::RegisterSandbox(proxy->getDummyInfo());
|
||||
Eval::Debugger::RegisterSandbox(localProxy->getDummyInfo());
|
||||
Eval::Debugger::RegisterThread();
|
||||
StringVecPtr client_args;
|
||||
bool restart = false;
|
||||
@@ -1084,7 +1084,12 @@ static int execute_program_impl(int argc, char **argv) {
|
||||
while (true) {
|
||||
try {
|
||||
execute_command_line_begin(new_argc, new_argv, po.xhprofFlags);
|
||||
g_context->setSandboxId(proxy->getDummyInfo().id());
|
||||
// Set the proxy for this thread to be the localProxy we just
|
||||
// created. If we're script debugging, this will be the proxy that
|
||||
// does all of our work. If we're remote debugging, this proxy will
|
||||
// go unused until we finally stop it when the user quits the
|
||||
// debugger.
|
||||
g_context->setSandboxId(localProxy->getDummyInfo().id());
|
||||
Eval::Debugger::DebuggerSession(po.debugger_options, file, restart);
|
||||
restart = false;
|
||||
execute_command_line_end(po.xhprofFlags, true, file.c_str());
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include "hphp/runtime/debugger/cmd/cmd_macro.h"
|
||||
#include "hphp/runtime/debugger/cmd/cmd_config.h"
|
||||
#include "hphp/runtime/debugger/cmd/cmd_complete.h"
|
||||
#include "hphp/runtime/debugger/cmd/cmd_internal_testing.h"
|
||||
//tag: new_cmd.php inserts new command here, do NOT remove/modify this line
|
||||
|
||||
#endif // incl_HPHP_EVAL_DEBUGGER_CMD_ALL_H_
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| 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/cmd/cmd_internal_testing.h"
|
||||
|
||||
namespace HPHP { namespace Eval {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TRACE_SET_MOD(debugger);
|
||||
|
||||
void CmdInternalTesting::sendImpl(DebuggerThriftBuffer &thrift) {
|
||||
DebuggerCommand::sendImpl(thrift);
|
||||
thrift.write(m_arg);
|
||||
// Write less data on purpose, to get the server to choke on deserialization
|
||||
if ((m_arg != "shortcmd")) {
|
||||
thrift.write(m_unused);
|
||||
}
|
||||
}
|
||||
|
||||
void CmdInternalTesting::recvImpl(DebuggerThriftBuffer &thrift) {
|
||||
DebuggerCommand::recvImpl(thrift);
|
||||
thrift.read(m_arg);
|
||||
thrift.read(m_unused);
|
||||
}
|
||||
|
||||
void CmdInternalTesting::help(DebuggerClient &client) {
|
||||
TRACE(2, "CmdInternalTesting::help\n");
|
||||
client.helpTitle("Internal Testing Command");
|
||||
client.helpCmds(
|
||||
"badcmdtypesend", "Send a bad command type to the proxy",
|
||||
"badcmdtypereceive", "Receive a bad command type from the proxy",
|
||||
"shortcmdsend", "Send less data that the proxy expects",
|
||||
"shortcmdreceive", "Receive less data than the client expects",
|
||||
"segfaultClient", "Segfault on the client",
|
||||
"segfaultServer", "Segfault on the server",
|
||||
nullptr
|
||||
);
|
||||
client.helpBody(
|
||||
"This command is only for internal testing of the debugger, both client "
|
||||
"and server. If you're using this command and you're not trying to test "
|
||||
"the debugger, then you're making a really big mistake."
|
||||
);
|
||||
}
|
||||
|
||||
void CmdInternalTesting::onClientImpl(DebuggerClient &client) {
|
||||
TRACE(2, "CmdInternalTesting::onClientImpl\n");
|
||||
if (DebuggerCommand::displayedHelp(client)) return;
|
||||
if (client.argCount() == 0) {
|
||||
help(client);
|
||||
return;
|
||||
}
|
||||
|
||||
client.info("Executing internal test...");
|
||||
m_arg = client.argValue(1);
|
||||
|
||||
if (client.arg(1, "badcmdtypesend")) {
|
||||
// Give the cmd a bad type and send it over. This should cause the proxy to
|
||||
// disconnect from us.
|
||||
m_type = KindOfInternalTestingBad;
|
||||
client.sendToServer(this);
|
||||
throw DebuggerConsoleExitException(); // Expect no response
|
||||
} else if (client.arg(1, "badcmdtypereceive")) {
|
||||
client.xend<CmdInternalTesting>(this);
|
||||
return;
|
||||
} else if (client.arg(1, "shortcmdsend")) {
|
||||
m_arg = "shortcmd"; // Force send to drop a field.
|
||||
client.sendToServer(this);
|
||||
throw DebuggerConsoleExitException(); // Expect no response
|
||||
} else if (client.arg(1, "shortcmdreceive")) {
|
||||
client.xend<CmdInternalTesting>(this);
|
||||
return;
|
||||
} else if (client.arg(1, "segfaultClient")) {
|
||||
int *px = nullptr;
|
||||
*px = 42;
|
||||
} else if (client.arg(1, "segfaultServer")) {
|
||||
client.xend<CmdInternalTesting>(this);
|
||||
return;
|
||||
}
|
||||
|
||||
help(client);
|
||||
}
|
||||
|
||||
bool CmdInternalTesting::onServer(DebuggerProxy &proxy) {
|
||||
TRACE(2, "CmdInternalTesting::onServer\n");
|
||||
if (m_arg == "badcmdtypereceive") {
|
||||
m_type = KindOfInternalTestingBad; // Send back a bad cmd.
|
||||
} else if (m_arg == "shortcmdreceive") {
|
||||
m_arg = "shortcmd"; // Force send to drop a field
|
||||
} else if (m_arg == "segfaultServer") {
|
||||
int *px = nullptr;
|
||||
*px = 42;
|
||||
}
|
||||
return proxy.sendToClient(this);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| 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. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_HPHP_DEBUGGER_CMD_INTERNAL_TESTING_H_
|
||||
#define incl_HPHP_DEBUGGER_CMD_INTERNAL_TESTING_H_
|
||||
|
||||
#include "hphp/runtime/debugger/debugger_command.h"
|
||||
|
||||
namespace HPHP { namespace Eval {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
DECLARE_BOOST_TYPES(CmdInternalTesting);
|
||||
class CmdInternalTesting : public DebuggerCommand {
|
||||
public:
|
||||
CmdInternalTesting() : DebuggerCommand(KindOfInternalTesting),
|
||||
m_unused(false) {}
|
||||
|
||||
virtual void help(DebuggerClient &client);
|
||||
|
||||
protected:
|
||||
virtual void sendImpl(DebuggerThriftBuffer &thrift);
|
||||
virtual void recvImpl(DebuggerThriftBuffer &thrift);
|
||||
virtual void onClientImpl(DebuggerClient &client);
|
||||
virtual bool onServer(DebuggerProxy &proxy);
|
||||
|
||||
private:
|
||||
std::string m_arg;
|
||||
bool m_unused;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}}
|
||||
|
||||
#endif // incl_HPHP_DEBUGGER_CMD_INTERNAL_TESTING_H_
|
||||
@@ -58,12 +58,14 @@ void CmdMachine::help(DebuggerClient &client) {
|
||||
"[m]achine [c]onnect {host}:{port}", "debugging remote server natively",
|
||||
"[m]achine [r]pc {host}", "debugging remote server with RPC",
|
||||
"[m]achine [r]pc {host}:{port}", "debugging remote server with RPC",
|
||||
"[m]achine [d]isconnect", "debugging local script",
|
||||
"[m]achine [d]isconnect", "disconnect, debugging local script",
|
||||
"[m]achine [l]ist", "list all sandboxes",
|
||||
"[m]achine [a]ttach {index}", "attach to a sandbox",
|
||||
"[m]achine [a]ttach {sandbox}", "attach to my sandbox by name",
|
||||
"[m]achine [a]ttach {user} {sandbox}",
|
||||
"attach to a sandbox by user and name",
|
||||
"[m]achine [a]ttach [f]orce {index|sandbox|user sandbox}",
|
||||
"force attach to a sandbox (see below)",
|
||||
nullptr
|
||||
);
|
||||
client.helpBody(
|
||||
@@ -87,6 +89,10 @@ void CmdMachine::help(DebuggerClient &client) {
|
||||
"When your sandbox is not available, please hit it at least once "
|
||||
"from your browser. Then run '[m]achine [l]ist' command again.\n"
|
||||
"\n"
|
||||
"If another debugger client is already attached to your sandbox you can "
|
||||
"use the '[f]orce' option to '[m]achine [a]ttach'. This will disconnect "
|
||||
"the other client and force your client to connect.\n"
|
||||
"\n"
|
||||
"If a HipHop server has RPC port open, one can also debug the server in "
|
||||
"a very special RPC mode. In this mode, one can type in PHP scripts to "
|
||||
"run, but all functions will be executed on server through RPC. Because "
|
||||
@@ -134,8 +140,7 @@ bool CmdMachine::AttachSandbox(DebuggerClient &client,
|
||||
bool force /* = false */) {
|
||||
string login;
|
||||
if (user == nullptr) {
|
||||
login = client.getCurrentUser();
|
||||
user = login.c_str();
|
||||
user = client.getCurrentUser().c_str();
|
||||
}
|
||||
if (client.isApiMode()) {
|
||||
force = true;
|
||||
@@ -166,11 +171,19 @@ bool CmdMachine::AttachSandbox(DebuggerClient &client,
|
||||
if (cmdMachine->m_succeed) {
|
||||
client.playMacro("startup");
|
||||
} else {
|
||||
client.error("failed to attach to sandbox, maybe another client is "
|
||||
"debugging, \nattach to another sandbox, exit the "
|
||||
"attached hphpd client, or try \n"
|
||||
"[m]achine [a]ttach [f]orce [%s] [%s]",
|
||||
sandbox->m_user.c_str(), sandbox->m_name.c_str());
|
||||
// Note: it would be nice to give them more info about the process we think
|
||||
// is debugging this sandbox: what machine it's on, what it's pid is, etc.
|
||||
// Unfortunately, we don't have any of that data. We'd need a protocol
|
||||
// change to have the client give us more info when it attaches.
|
||||
client.error(
|
||||
"Failed to attach to the sandbox. Maybe another client is debugging, \n"
|
||||
"or a client failed to detach cleanly.\n"
|
||||
"You can attach to another sandbox, or exit the other attached client, \n"
|
||||
"or force this client to take over the sandbox with: \n"
|
||||
"\n"
|
||||
"\t[m]achine [a]ttach [f]orce %s %s"
|
||||
"\n",
|
||||
sandbox->m_user.c_str(), sandbox->m_name.c_str());
|
||||
}
|
||||
return cmdMachine->m_succeed;
|
||||
}
|
||||
@@ -227,7 +240,9 @@ void CmdMachine::onClientImpl(DebuggerClient &client) {
|
||||
throw DebuggerConsoleExitException();
|
||||
}
|
||||
}
|
||||
client.initializeMachine();
|
||||
if (!client.initializeMachine()) {
|
||||
throw DebuggerConsoleExitException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,7 +250,9 @@ void CmdMachine::onClientImpl(DebuggerClient &client) {
|
||||
if (client.disconnect()) {
|
||||
throw DebuggerConsoleExitException();
|
||||
}
|
||||
client.initializeMachine();
|
||||
if (!client.initializeMachine()) {
|
||||
throw DebuggerConsoleExitException();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,9 +27,6 @@ class CmdMachine : public DebuggerCommand {
|
||||
public:
|
||||
static bool AttachSandbox(DebuggerClient &client, const char *user = nullptr,
|
||||
const char *name = nullptr, bool force = false);
|
||||
static bool AttachSandbox(DebuggerClient &client,
|
||||
DSandboxInfoPtr sandbox,
|
||||
bool force = false);
|
||||
static void UpdateIntercept(DebuggerClient &client,
|
||||
const std::string &host, int port);
|
||||
|
||||
@@ -48,6 +45,10 @@ protected:
|
||||
virtual void recvImpl(DebuggerThriftBuffer &thrift);
|
||||
|
||||
private:
|
||||
static bool AttachSandbox(DebuggerClient &client,
|
||||
DSandboxInfoPtr sandbox,
|
||||
bool force = false);
|
||||
|
||||
DSandboxInfoPtrVec m_sandboxes;
|
||||
Array m_rpcConfig;
|
||||
bool m_force;
|
||||
|
||||
@@ -86,7 +86,7 @@ void Debugger::RemoveProxy(DebuggerProxyPtr proxy) {
|
||||
}
|
||||
|
||||
int Debugger::CountConnectedProxy() {
|
||||
TRACE(2, "Debugger::CountConnectedProxy\n");
|
||||
TRACE(7, "Debugger::CountConnectedProxy\n");
|
||||
return s_debugger.countConnectedProxy();
|
||||
}
|
||||
|
||||
@@ -157,8 +157,7 @@ void Debugger::LogShutdown(ShutdownKind shutdownKind) {
|
||||
if (proxyCount > 0) {
|
||||
Logger::Warning(DEBUGGER_LOG_TAG "%s with connected debuggers!",
|
||||
shutdownKind == ShutdownKind::Normal ?
|
||||
"Normal shutdown" : "Unexpected crash",
|
||||
proxyCount);
|
||||
"Normal shutdown" : "Unexpected crash");
|
||||
|
||||
for (const auto& proxyEntry: s_debugger.m_proxyMap) {
|
||||
auto sid = proxyEntry.first;
|
||||
@@ -257,8 +256,8 @@ void Debugger::Interrupt(int type, const char *program,
|
||||
// Debugger clients are disconnected abnormally, or this sandbox is not
|
||||
// being debugged.
|
||||
if (type == SessionStarted || type == SessionEnded) {
|
||||
// for command line programs, we need this exception to exit from
|
||||
// the infinite execution loop
|
||||
// For command line programs, we need this exception to exit from
|
||||
// the infinite execution loop.
|
||||
throw DebuggerClientExitException();
|
||||
}
|
||||
}
|
||||
@@ -334,8 +333,8 @@ void Debugger::registerThread() {
|
||||
acc->second = ti;
|
||||
}
|
||||
|
||||
void Debugger::updateSandbox(const DSandboxInfo &sandbox) {
|
||||
TRACE(2, "Debugger::updateSandbox\n");
|
||||
void Debugger::addOrUpdateSandbox(const DSandboxInfo &sandbox) {
|
||||
TRACE(2, "Debugger::addOrUpdateSandbox\n");
|
||||
const StringData* sd = StringData::GetStaticString(sandbox.id());
|
||||
SandboxMap::accessor acc;
|
||||
if (m_sandboxMap.insert(acc, sd)) {
|
||||
@@ -360,12 +359,15 @@ void Debugger::getSandboxes(DSandboxInfoPtrVec &sandboxes) {
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the debugger that this thread is executing a request in the given
|
||||
// sandbox. This ensures that the debugger knows about the sandbox, and adds
|
||||
// the thread to the set of threads currently active in the sandbox.
|
||||
void Debugger::registerSandbox(const DSandboxInfo &sandbox) {
|
||||
TRACE(2, "Debugger::registerSandbox\n");
|
||||
// update sandbox first
|
||||
updateSandbox(sandbox);
|
||||
addOrUpdateSandbox(sandbox);
|
||||
|
||||
// add thread do m_sandboxThreadInfoMap
|
||||
// add thread to m_sandboxThreadInfoMap
|
||||
const StringData* sid = StringData::GetStaticString(sandbox.id());
|
||||
ThreadInfo* ti = ThreadInfo::s_threadInfo.getNoCheck();
|
||||
{
|
||||
@@ -435,9 +437,13 @@ DebuggerProxyPtr Debugger::createProxy(SmartPtr<Socket> socket, bool local) {
|
||||
// Creates a proxy and threads needed to handle it. At this point, there is
|
||||
// not enough information to attach a sandbox.
|
||||
DebuggerProxyPtr proxy(new DebuggerProxy(socket, local));
|
||||
const StringData* sid =
|
||||
StringData::GetStaticString(proxy->getDummyInfo().id());
|
||||
{
|
||||
// Place this new proxy into the proxy map keyed on the dummy sandbox id.
|
||||
// This keeps the proxy alive in the server case, which drops the result of
|
||||
// this function on the floor. It also makes the proxy findable when we a
|
||||
// dummy sandbox thread needs to interrupt.
|
||||
const StringData* sid =
|
||||
StringData::GetStaticString(proxy->getDummyInfo().id());
|
||||
assert(sid);
|
||||
ProxyMap::accessor acc;
|
||||
m_proxyMap.insert(acc, sid);
|
||||
@@ -473,6 +479,9 @@ void Debugger::cleanupDummySandboxThreads() {
|
||||
}
|
||||
}
|
||||
|
||||
// NB: when this returns, the Debugger class no longer has any references to the
|
||||
// given proxy. It will likely be destroyed when the caller's reference goes out
|
||||
// of scope.
|
||||
void Debugger::removeProxy(DebuggerProxyPtr proxy) {
|
||||
TRACE(2, "Debugger::removeProxy\n");
|
||||
if (proxy->getSandbox().valid()) {
|
||||
@@ -523,7 +532,7 @@ bool Debugger::switchSandboxImpl(DebuggerProxyPtr proxy,
|
||||
bool force) {
|
||||
TRACE(2, "Debugger::switchSandboxImpln");
|
||||
// Take the new sandbox
|
||||
DebuggerProxyPtr dpp;
|
||||
DebuggerProxyPtr otherProxy;
|
||||
{
|
||||
ProxyMap::accessor acc;
|
||||
if (m_proxyMap.insert(acc, newSid)) {
|
||||
@@ -536,7 +545,7 @@ bool Debugger::switchSandboxImpl(DebuggerProxyPtr proxy,
|
||||
// Delay the destruction of the proxy originally attached to the sandbox
|
||||
// to avoid calling a DebuggerProxy destructor (which can sleep) while
|
||||
// holding m_mutex.
|
||||
dpp = acc->second;
|
||||
otherProxy = acc->second;
|
||||
acc->second = proxy;
|
||||
}
|
||||
}
|
||||
@@ -550,8 +559,8 @@ bool Debugger::switchSandboxImpl(DebuggerProxyPtr proxy,
|
||||
}
|
||||
setDebuggerFlag(newSid, true);
|
||||
|
||||
if (dpp) {
|
||||
dpp->forceQuit();
|
||||
if (otherProxy) {
|
||||
otherProxy->forceQuit();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -114,28 +114,43 @@ private:
|
||||
|
||||
static const char *InterruptTypeName(CmdInterrupt &cmd);
|
||||
|
||||
// The following maps use a "sandbox id", or sid, which is a string containing
|
||||
// the user's name, a tab, and the sandbox name. I.e., "mikemag\tdefault".
|
||||
|
||||
// Map of "sandbox id"->proxy. The map contains an entry for every active
|
||||
// proxy attached to this process, keyed by the sandbox being debugged. It
|
||||
// also contains an entry for the proxy's dummy sandbox, keyed by the dummy
|
||||
// sandbox id which is basically the proxy pointer rendered as a string.
|
||||
typedef tbb::concurrent_hash_map<const StringData*, DebuggerProxyPtr,
|
||||
StringDataHashCompare> ProxyMap;
|
||||
ProxyMap m_proxyMap;
|
||||
|
||||
// Map of "sandbox id"->"sandbox info" for any sandbox the process has seen.
|
||||
// Entries are made when a request is received for a sandbox, or when a switch
|
||||
// to a new sandbox id is made via CmdMachine. The latter alows a debugger to
|
||||
// "pre-attach" to a sandbox before the first request is every received.
|
||||
// Entries are never removed.
|
||||
typedef tbb::concurrent_hash_map<const StringData*, DSandboxInfoPtr,
|
||||
StringDataHashCompare> SandboxMap;
|
||||
SandboxMap m_sandboxMap;
|
||||
|
||||
// Map of "sandbox id"->"set of threads executing requests in the sandbox".
|
||||
typedef std::set<ThreadInfo*> ThreadInfoSet;
|
||||
typedef tbb::concurrent_hash_map<const StringData*, ThreadInfoSet,
|
||||
StringDataHashCompare> SandboxThreadInfoMap;
|
||||
SandboxThreadInfoMap m_sandboxThreadInfoMap;
|
||||
|
||||
// "thread id"->"thread info". Each thread which is being debugged is
|
||||
// added to this map.
|
||||
typedef tbb::concurrent_hash_map<int64_t, ThreadInfo*> ThreadInfoMap;
|
||||
ThreadInfoMap m_threadInfos; // tid => ThreadInfo*
|
||||
ThreadInfoMap m_threadInfos;
|
||||
|
||||
typedef tbb::concurrent_queue<DummySandbox*> DummySandboxQ;
|
||||
DummySandboxQ m_cleanupDummySandboxQ;
|
||||
|
||||
bool isThreadDebugging(int64_t id);
|
||||
void registerThread();
|
||||
void updateSandbox(const DSandboxInfo &sandbox);
|
||||
void addOrUpdateSandbox(const DSandboxInfo &sandbox);
|
||||
DSandboxInfoPtr getSandbox(const StringData* sid);
|
||||
void getSandboxes(DSandboxInfoPtrVec &sandboxes);
|
||||
void registerSandbox(const DSandboxInfo &sandbox);
|
||||
|
||||
@@ -42,22 +42,46 @@ struct DebuggerClientOptions {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// exceptions
|
||||
|
||||
// client side exception
|
||||
class DebuggerClientException : public Exception {};
|
||||
class DebuggerConsoleExitException : public DebuggerClientException {};
|
||||
class DebuggerProtocolException : public DebuggerClientException {};
|
||||
class DebuggerServerLostException : public DebuggerClientException {};
|
||||
// Client-side exceptions
|
||||
class DebuggerClientException : public Exception {};
|
||||
|
||||
// both client and server side exception
|
||||
class DebuggerException : public Exception {
|
||||
// Exception used to force the debugger client to exit the command prompt loop
|
||||
// implemented in DebuggerClient::console(). Commands throw this when they do
|
||||
// something that causes the server to start running code again, so we pop out
|
||||
// of the command prompt loop and go back to waiting for interrupt messages from
|
||||
// the server.
|
||||
class DebuggerConsoleExitException : public DebuggerClientException {};
|
||||
|
||||
// Exception thrown when the client detects an error in the communication
|
||||
// protocol with the server, but believes the connection is still alive.
|
||||
// I.e., bad command type back, missing fields, etc.
|
||||
class DebuggerProtocolException : public DebuggerClientException {};
|
||||
|
||||
// Exception thrown when the client loses its connection to the server.
|
||||
class DebuggerServerLostException : public DebuggerClientException {};
|
||||
|
||||
// Both client- and server-side exceptions
|
||||
class DebuggerException : public Exception {
|
||||
EXCEPTION_COMMON_IMPL(DebuggerException);
|
||||
};
|
||||
|
||||
// Exception thrown in two cases:
|
||||
//
|
||||
// Client-side: thrown in cases where the client should completely exit. This
|
||||
// will pop us out of both the command prompt loop and the message loop for the
|
||||
// server, and cause the client to quit.
|
||||
//
|
||||
// Server-side: thrown when the server detects that the client is exiting. This
|
||||
// causes a thread which is currently interrupted to terminate it's request with
|
||||
// this exception. The message attempts to reflect that a request which was
|
||||
// being debugged has been terminated.
|
||||
class DebuggerClientExitException : public DebuggerException {
|
||||
virtual const char *what() const throw() {
|
||||
return "Debugger client has just quit.";
|
||||
return "Debugger client has just quit, request (if any) terminated.";
|
||||
}
|
||||
EXCEPTION_COMMON_IMPL(DebuggerClientExitException);
|
||||
};
|
||||
|
||||
class DebuggerRestartException : public DebuggerException {
|
||||
public:
|
||||
explicit DebuggerRestartException(StringVecPtr args) : m_args(args) {}
|
||||
@@ -114,9 +138,9 @@ public:
|
||||
int m_port;
|
||||
DebuggerThriftBuffer m_thrift;
|
||||
|
||||
bool m_interrupting;
|
||||
bool m_interrupting; // True if the machine is paused at an interrupt
|
||||
bool m_sandboxAttached;
|
||||
bool m_initialized;
|
||||
bool m_initialized; // True if the initial connection protocol is complete
|
||||
std::string m_rpcHost;
|
||||
int m_rpcPort;
|
||||
};
|
||||
|
||||
@@ -93,14 +93,15 @@ void DebuggerClient::onSignal(int sig) {
|
||||
|
||||
if (m_sigTime) {
|
||||
int secWait = 10;
|
||||
if (now - m_sigTime > secWait) {
|
||||
int secLeft = secWait - (now - m_sigTime);
|
||||
if (secLeft <= 0) {
|
||||
error("Program is not responding. Please restart debugger to get a "
|
||||
"new connection.");
|
||||
quit();
|
||||
quit(); // NB: the machine is running, so can't send a real CmdQuit.
|
||||
return;
|
||||
}
|
||||
info("Please wait. If not responding in %d seconds, "
|
||||
"press Ctrl-C again to quit.", secWait);
|
||||
info("Please wait. If not responding in %d second%s, "
|
||||
"press Ctrl-C again to quit.", secLeft, secLeft > 1 ? "s" : "");
|
||||
} else {
|
||||
info("Pausing program execution, please wait...");
|
||||
usageLog("signal");
|
||||
@@ -402,8 +403,7 @@ DebuggerClient::DebuggerClient(std::string name /* = "" */)
|
||||
: m_tutorial(0), m_printFunction(""),
|
||||
m_logFile(""), m_logFileHandler(nullptr),
|
||||
m_mainThread(this, &DebuggerClient::run), m_stopped(false),
|
||||
m_quitting(false),
|
||||
m_inputState(TakingCommand), m_runState(NotYet),
|
||||
m_inputState(TakingCommand),
|
||||
m_signum(CmdSignal::SignalNone), m_sigTime(0),
|
||||
m_acLen(0), m_acIndex(0), m_acPos(0), m_acLiveListsDirty(true),
|
||||
m_threadId(0), m_listLine(0), m_listLineFocus(0), m_frame(0),
|
||||
@@ -424,17 +424,11 @@ DebuggerClient::~DebuggerClient() {
|
||||
}
|
||||
}
|
||||
|
||||
void DebuggerClient::reset() {
|
||||
TRACE(2, "DebuggerClient::reset\n");
|
||||
void DebuggerClient::closeAllConnections() {
|
||||
TRACE(2, "DebuggerClient::closeAllConnections\n");
|
||||
for (unsigned int i = 0; i < m_machines.size(); i++) {
|
||||
m_machines[i]->m_thrift.close();
|
||||
}
|
||||
|
||||
m_stacktrace.reset();
|
||||
m_acItems.clear();
|
||||
m_acLiveLists.reset();
|
||||
m_machines.clear();
|
||||
m_machine.reset();
|
||||
}
|
||||
|
||||
bool DebuggerClient::isLocal() {
|
||||
@@ -446,6 +440,7 @@ bool DebuggerClient::connect(const std::string &host, int port) {
|
||||
TRACE(2, "DebuggerClient::connect\n");
|
||||
assert(isApiMode() ||
|
||||
(!m_machines.empty() && m_machines[0]->m_name == LocalPrompt));
|
||||
// First check for an existing connect, and reuse that.
|
||||
for (unsigned int i = 1; i < m_machines.size(); i++) {
|
||||
if (f_gethostbyname(m_machines[i]->m_name) ==
|
||||
f_gethostbyname(host)) {
|
||||
@@ -532,7 +527,7 @@ bool DebuggerClient::connectRemote(const std::string &host, int port) {
|
||||
// API mode, and in client mode we expect to destruct it ourselves
|
||||
// when ~DebuggerClient runs.
|
||||
sock->unregister();
|
||||
Object obj(sock);
|
||||
Object obj(sock); // Destroy sock if we don't connect.
|
||||
if (f_socket_connect(sock, String(host), port)) {
|
||||
DMachineInfoPtr machine(new DMachineInfo());
|
||||
machine->m_name = host;
|
||||
@@ -553,10 +548,11 @@ bool DebuggerClient::reconnect() {
|
||||
int port = m_machine->m_port;
|
||||
if (port) {
|
||||
info("Re-connecting to %s:%d...", host.c_str(), port);
|
||||
m_machine->m_thrift.close(); // Close the old socket, it may still be open.
|
||||
Socket *sock = new Socket(socket(PF_INET, SOCK_STREAM, 0), PF_INET,
|
||||
host.c_str(), port);
|
||||
sock->unregister();
|
||||
Object obj(sock);
|
||||
Object obj(sock); // Destroy sock if we don't connect.
|
||||
if (f_socket_connect(sock, String(host), port)) {
|
||||
for (unsigned int i = 0; i < m_machines.size(); i++) {
|
||||
if (m_machines[i] == m_machine) {
|
||||
@@ -568,6 +564,7 @@ bool DebuggerClient::reconnect() {
|
||||
machine->m_name = host;
|
||||
machine->m_port = port;
|
||||
machine->m_thrift.create(SmartPtr<Socket>(sock));
|
||||
m_machines.push_back(machine);
|
||||
switchMachine(machine);
|
||||
return true;
|
||||
}
|
||||
@@ -656,6 +653,7 @@ void DebuggerClient::stop() {
|
||||
m_mainThread.waitForEnd();
|
||||
}
|
||||
|
||||
// Executed by m_mainThread to run the command-line debugger.
|
||||
void DebuggerClient::run() {
|
||||
TRACE(2, "DebuggerClient::run\n");
|
||||
StackTraceNoHeap::AddExtraLogging("IsDebugger", "True");
|
||||
@@ -681,24 +679,35 @@ void DebuggerClient::run() {
|
||||
hphp_invoke_simple(m_options.extension);
|
||||
}
|
||||
while (true) {
|
||||
bool reconnect = false;
|
||||
try {
|
||||
runImpl();
|
||||
} catch (DebuggerServerLostException &e) {
|
||||
if (reconnect()) {
|
||||
continue;
|
||||
}
|
||||
// Loss of connection
|
||||
TRACE_RB(1, "DebuggerClient::run: server lost exception\n");
|
||||
usageLog(m_commandCanonical, "DebuggerServerLostException");
|
||||
reconnect = true;
|
||||
} catch (DebuggerProtocolException &e) {
|
||||
// Bad or unexpected data. Give reconnect a shot, it could help...
|
||||
TRACE_RB(1, "DebuggerClient::run: protocol exception\n");
|
||||
usageLog(m_commandCanonical, "DebuggerProtocolException");
|
||||
reconnect = true;
|
||||
} catch (...) {
|
||||
Logger::Error("Unhandled exception from DebuggerClient::runImpl().");
|
||||
TRACE_RB(1, "DebuggerClient::run: unknown exception\n");
|
||||
usageLog(m_commandCanonical, "UnknownException");
|
||||
Logger::Error("Unhandled exception, exiting.");
|
||||
}
|
||||
// Note: it's silly to try to reconnect when stopping, or if we have a
|
||||
// problem while quitting.
|
||||
if (reconnect && !m_stopped && (m_commandCanonical != "quit")) {
|
||||
if (DebuggerClient::reconnect()) continue;
|
||||
Logger::Error("Unable to reconnect to server, exiting.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
// We are about to exit from client and ideally we should cleanup,
|
||||
// but we the reset() where will try to cleanup Socket under DMachineInfo,
|
||||
// which is created by another thread. If it's cleaned up here, later we'll
|
||||
// have a SEGV when trying to sweep the object from the other thread.
|
||||
// We should refactor the code to avoid Sweepable object being passed across
|
||||
// threads later.
|
||||
// reset();
|
||||
// Closing all proxy connections will force the local proxy to pop out of
|
||||
// it's wait, and eventually exit the main thread.
|
||||
closeAllConnections();
|
||||
hphp_context_exit(context, false);
|
||||
hphp_session_exit();
|
||||
}
|
||||
@@ -939,45 +948,55 @@ char *DebuggerClient::getCompletion(const char *text, int state) {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// main
|
||||
|
||||
// Execute the initial connection protocol with a machine. A connection has been
|
||||
// established, and the proxy has responded with an interrupt giving us initial
|
||||
// control. Send breakpoints to the server, and then attach to the sandbox
|
||||
// if necessary. If we attach to a sandbox, then the process is off and running
|
||||
// again (CmdMachine continues execution on a successful attach) so return false
|
||||
// to indicate that a client should wait for another interrupt before attempting
|
||||
// further communication. Returns true if the protocol is complete and the
|
||||
// machine is at an interrupt.
|
||||
bool DebuggerClient::initializeMachine() {
|
||||
TRACE(2, "DebuggerClient::initializeMachine\n");
|
||||
if (!m_machine->m_initialized) {
|
||||
// set/clear intercept for RPC thread
|
||||
if (!m_machines.empty() && m_machine == m_machines[0]) {
|
||||
CmdMachine::UpdateIntercept(*this, m_machine->m_rpcHost,
|
||||
m_machine->m_rpcPort);
|
||||
}
|
||||
// set/clear intercept for RPC thread
|
||||
if (!m_machines.empty() && m_machine == m_machines[0]) {
|
||||
CmdMachine::UpdateIntercept(*this, m_machine->m_rpcHost,
|
||||
m_machine->m_rpcPort);
|
||||
}
|
||||
|
||||
// upload breakpoints
|
||||
if (!m_breakpoints.empty()) {
|
||||
info("Updating breakpoints...");
|
||||
CmdBreak::SendClientBreakpointListToServer(*this);
|
||||
}
|
||||
// upload breakpoints
|
||||
if (!m_breakpoints.empty()) {
|
||||
info("Updating breakpoints...");
|
||||
CmdBreak::SendClientBreakpointListToServer(*this);
|
||||
}
|
||||
|
||||
// attaching to default sandbox
|
||||
int waitForgSandbox = false;
|
||||
// attaching to default sandbox
|
||||
int waitForSandbox = false;
|
||||
if (!m_machine->m_sandboxAttached) {
|
||||
const char *user = m_options.user.empty() ?
|
||||
nullptr : m_options.user.c_str();
|
||||
m_machine->m_sandboxAttached = (waitForSandbox =
|
||||
CmdMachine::AttachSandbox(*this, user, m_options.sandbox.c_str()));
|
||||
if (!m_machine->m_sandboxAttached) {
|
||||
const char *user = m_options.user.empty() ?
|
||||
nullptr : m_options.user.c_str();
|
||||
m_machine->m_sandboxAttached = (waitForgSandbox =
|
||||
CmdMachine::AttachSandbox(*this, user, m_options.sandbox.c_str()));
|
||||
if (!m_machine->m_sandboxAttached) {
|
||||
Logger::Error("Unable to communicate with default sandbox.");
|
||||
}
|
||||
Logger::Error("Unable to communicate with default sandbox.");
|
||||
}
|
||||
}
|
||||
|
||||
m_machine->m_initialized = true;
|
||||
if (!isApiMode() && waitForgSandbox) {
|
||||
// Throw exception here to wait for next interrupt from server
|
||||
throw DebuggerConsoleExitException();
|
||||
}
|
||||
m_machine->m_initialized = true;
|
||||
if (!isApiMode() && waitForSandbox) {
|
||||
// Return false to wait for next interrupt from server
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Only used in API mode, to listen for another interupt from the machine.
|
||||
DebuggerCommandPtr DebuggerClient::waitForNextInterrupt() {
|
||||
TRACE(2, "DebuggerClient::waitForNextInterrupt\n");
|
||||
assert(isApiMode());
|
||||
const char *func = "DebuggerClient::waitForNextInterrupt()";
|
||||
m_machine->m_interrupting = false; // Machine is running again.
|
||||
m_inputState = TakingInterrupt;
|
||||
while (!m_stopped) {
|
||||
DebuggerCommandPtr cmd;
|
||||
if (DebuggerCommand::Receive(m_machine->m_thrift, cmd, func)) {
|
||||
@@ -1014,13 +1033,22 @@ DebuggerCommandPtr DebuggerClient::waitForNextInterrupt() {
|
||||
return DebuggerCommandPtr();
|
||||
}
|
||||
|
||||
// The main execution loop of DebuggerClient. This waits for interrupts from
|
||||
// the server (and responds to polls for signals). On interrupt, it presents
|
||||
// a command prompt, and continues pumping interrupts when a command lets the
|
||||
// machine run again.
|
||||
// When this returns, the command line client will exit.
|
||||
void DebuggerClient::runImpl() {
|
||||
TRACE(2, "DebuggerClient::runImpl\n");
|
||||
const char *func = "DebuggerClient::runImpl()";
|
||||
assert(!isApiMode());
|
||||
const char *func = "Main client loop";
|
||||
|
||||
DebuggerCommandPtr cmd;
|
||||
try {
|
||||
while (!m_stopped) {
|
||||
DebuggerCommandPtr cmd;
|
||||
// The machine should be running at this point, otherwise it will never
|
||||
// send us anything and we'll wait forever.
|
||||
assert(!m_machine->m_interrupting);
|
||||
if (DebuggerCommand::Receive(m_machine->m_thrift, cmd, func)) {
|
||||
if (!cmd) {
|
||||
Logger::Error("Unable to communicate with server. Server's down?");
|
||||
@@ -1031,38 +1059,47 @@ void DebuggerClient::runImpl() {
|
||||
continue;
|
||||
}
|
||||
if (!cmd->is(DebuggerCommand::KindOfInterrupt)) {
|
||||
Logger::Error("%s: bad cmd type: %d", func, cmd->getType());
|
||||
return;
|
||||
Logger::Error("Received bad cmd type %d, unable to communicate "
|
||||
"with server.", cmd->getType());
|
||||
throw DebuggerServerLostException();
|
||||
}
|
||||
m_sigTime = 0;
|
||||
CmdInterruptPtr intr = dynamic_pointer_cast<CmdInterrupt>(cmd);
|
||||
Debugger::UsageLogInterrupt(getUsageMode(), *intr.get());
|
||||
{
|
||||
cmd->onClient(*this);
|
||||
}
|
||||
m_machine->m_interrupting = true;
|
||||
setClientState(StateReadyForCommand);
|
||||
m_inputState = TakingCommand;
|
||||
cmd->onClient(*this);
|
||||
|
||||
// When we make a new connection to a machine, we have to wait for it
|
||||
// to interrupt us before we can send it any messages. This is our
|
||||
// opportunity to complete the connection and make it ready to use.
|
||||
if (!m_machine->m_initialized) {
|
||||
try {
|
||||
if (!initializeMachine()) {
|
||||
return;
|
||||
}
|
||||
} catch (DebuggerConsoleExitException &e) {
|
||||
if (!initializeMachine()) {
|
||||
// False means the machine is running and we need to wait for
|
||||
// another interrupt.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!console()) {
|
||||
return;
|
||||
}
|
||||
setClientState(StateBusy);
|
||||
// Execution has been interrupted, so go ahead and give the user
|
||||
// the prompt back.
|
||||
m_machine->m_interrupting = true; // Machine is stopped
|
||||
m_inputState = TakingCommand;
|
||||
console(); // Prompt loop
|
||||
m_inputState = TakingInterrupt;
|
||||
m_machine->m_interrupting = false; // Machine is running again.
|
||||
}
|
||||
}
|
||||
} catch (DebuggerClientExitException &e) { /* normal exit */ }
|
||||
}
|
||||
|
||||
bool DebuggerClient::console() {
|
||||
// Execute the interactive command loop for the debugger client. This will
|
||||
// present the prompt, wait for user input, and execute commands, then rinse
|
||||
// and repeat. The loop terminates when a command is executed that causes the
|
||||
// machine to resume execution, or which should cause the client to exit.
|
||||
// This function is only entered when the machine being debugged is paused.
|
||||
//
|
||||
// If this function returns it means the process is running again.
|
||||
// NB: exceptions derrived from DebuggerException or DebuggerClientExeption
|
||||
// indicate the machine remains paused.
|
||||
void DebuggerClient::console() {
|
||||
TRACE(2, "DebuggerClient::console\n");
|
||||
while (true) {
|
||||
const char *line = nullptr;
|
||||
@@ -1080,12 +1117,8 @@ bool DebuggerClient::console() {
|
||||
line = readline(getPrompt().c_str());
|
||||
if (line == nullptr) {
|
||||
// treat ^D as quit
|
||||
try {
|
||||
print("quit");
|
||||
quit();
|
||||
} catch (DebuggerClientExitException &e) {
|
||||
return false;
|
||||
}
|
||||
print("quit");
|
||||
line = "quit";
|
||||
}
|
||||
} else if (!NoPrompt && RuntimeOption::EnableDebuggerPrompt) {
|
||||
print("%s%s", getPrompt().c_str(), line);
|
||||
@@ -1107,12 +1140,8 @@ bool DebuggerClient::console() {
|
||||
error("command \"" + m_command + "\" not found");
|
||||
m_command.clear();
|
||||
}
|
||||
} catch (DebuggerClientExitException &e) {
|
||||
return false;
|
||||
} catch (DebuggerConsoleExitException &e) {
|
||||
return true;
|
||||
} catch (DebuggerProtocolException &e) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (m_inputState == TakingCommand) {
|
||||
@@ -1129,16 +1158,13 @@ bool DebuggerClient::console() {
|
||||
m_command = m_prevCmd;
|
||||
process(); // replay the same command
|
||||
} catch (DebuggerConsoleExitException &e) {
|
||||
return true;
|
||||
} catch (DebuggerProtocolException &e) {
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
not_reached();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1568,6 +1594,11 @@ do { \
|
||||
if (m_command == "set") NEW_CMD_NAME("config", CmdConfig);
|
||||
if (m_command == "complete") NEW_CMD_NAME("complete", CmdComplete);
|
||||
|
||||
// Internal testing
|
||||
if (m_command == "internaltesting") {
|
||||
NEW_CMD_NAME("internaltesting", CmdInternalTesting);
|
||||
}
|
||||
|
||||
switch (tolower(m_command[0])) {
|
||||
case 'a': MATCH_CMD("abort" , CmdAbort );
|
||||
case 'b': MATCH_CMD("break" , CmdBreak );
|
||||
@@ -1606,6 +1637,8 @@ do { \
|
||||
|
||||
// Parses the current command string. If invalid return false.
|
||||
// Otherwise, carry out the command and return true.
|
||||
// NB: the command may throw a variety of exceptions derrived from
|
||||
// DebuggerClientException.
|
||||
bool DebuggerClient::process() {
|
||||
TRACE(2, "DebuggerClient::process\n");
|
||||
clearCachedLocal();
|
||||
@@ -1812,13 +1845,14 @@ DebuggerCommandPtr DebuggerClient::xend(DebuggerCommand *cmd) {
|
||||
void DebuggerClient::sendToServer(DebuggerCommand *cmd) {
|
||||
TRACE(2, "DebuggerClient::sendToServer\n");
|
||||
if (!cmd->send(m_machine->m_thrift)) {
|
||||
Logger::Error("Send command: unable to communicate with server.");
|
||||
throw DebuggerProtocolException();
|
||||
}
|
||||
}
|
||||
|
||||
DebuggerCommandPtr DebuggerClient::recvFromServer(int expected) {
|
||||
TRACE(2, "DebuggerClient::recvFromServer\n");
|
||||
const char *func = "DebuggerClient::recvFromServer ()";
|
||||
const char *func = "Receive result";
|
||||
|
||||
DebuggerCommandPtr res;
|
||||
while (true) {
|
||||
@@ -1836,7 +1870,6 @@ DebuggerCommandPtr DebuggerClient::recvFromServer(int expected) {
|
||||
usageLog("done", boost::lexical_cast<string>(expected));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!res->is(DebuggerCommand::KindOfInterrupt)) {
|
||||
Logger::Error("%s: unexpected return: %d", func, res->getType());
|
||||
throw DebuggerProtocolException();
|
||||
@@ -1861,14 +1894,7 @@ DebuggerCommandPtr DebuggerClient::recvFromServer(int expected) {
|
||||
|
||||
// eval() can cause more breakpoints
|
||||
res->onClient(*this);
|
||||
if (!console()) {
|
||||
if (m_quitting) {
|
||||
throw DebuggerClientExitException();
|
||||
} else {
|
||||
Logger::Error("%s: unable to process %d", func, res->getType());
|
||||
throw DebuggerProtocolException();
|
||||
}
|
||||
}
|
||||
console();
|
||||
}
|
||||
|
||||
return res;
|
||||
@@ -1937,7 +1963,6 @@ void DebuggerClient::processTakeCode() {
|
||||
|
||||
void DebuggerClient::processEval() {
|
||||
TRACE(2, "DebuggerClient::processEval\n");
|
||||
m_runState = Running;
|
||||
m_inputState = TakingCommand;
|
||||
m_acLiveListsDirty = true;
|
||||
CmdEval().onClient(*this);
|
||||
@@ -1952,10 +1977,7 @@ void DebuggerClient::swapHelp() {
|
||||
|
||||
void DebuggerClient::quit() {
|
||||
TRACE(2, "DebuggerClient::quit\n");
|
||||
m_quitting = true;
|
||||
for (unsigned int i = 0; i < m_machines.size(); i++) {
|
||||
m_machines[i]->m_thrift.close();
|
||||
}
|
||||
closeAllConnections();
|
||||
throw DebuggerClientExitException();
|
||||
}
|
||||
|
||||
|
||||
@@ -115,7 +115,6 @@ public:
|
||||
public:
|
||||
explicit DebuggerClient(std::string name = ""); // name only for api usage
|
||||
~DebuggerClient();
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Thread functions.
|
||||
@@ -127,7 +126,7 @@ public:
|
||||
/**
|
||||
* Main processing functions.
|
||||
*/
|
||||
bool console();
|
||||
void console();
|
||||
// Carries out the current command and returns true if the command completed.
|
||||
bool process();
|
||||
void quit();
|
||||
@@ -299,8 +298,6 @@ public:
|
||||
void setClientState(ClientState state) { m_clientState = state; }
|
||||
void init(const DebuggerClientOptions &options);
|
||||
DebuggerCommandPtr waitForNextInterrupt();
|
||||
bool isTakingCommand() const { return m_inputState == TakingCommand; }
|
||||
void setTakingInterrupt() { m_inputState = TakingInterrupt; }
|
||||
String getPrintString();
|
||||
Array getOutputArray();
|
||||
void setOutputType(OutputType type) { m_outputType = type; }
|
||||
@@ -346,11 +343,6 @@ private:
|
||||
TakingCode,
|
||||
TakingInterrupt
|
||||
};
|
||||
enum RunState {
|
||||
NotYet,
|
||||
Running,
|
||||
Stopped
|
||||
};
|
||||
|
||||
/*
|
||||
* NOTE: be careful about the use of smart-allocated data members
|
||||
@@ -374,12 +366,10 @@ private:
|
||||
DebuggerClientOptions m_options;
|
||||
AsyncFunc<DebuggerClient> m_mainThread;
|
||||
bool m_stopped;
|
||||
bool m_quitting;
|
||||
|
||||
InputState m_inputState;
|
||||
RunState m_runState;
|
||||
int m_signum;
|
||||
int m_sigTime;
|
||||
int m_signum; // Set when ctrl-c is pressed, used by signal polling
|
||||
int m_sigTime; // The last time ctrl-c was recgonized
|
||||
|
||||
// auto-completion states
|
||||
int m_acLen;
|
||||
@@ -406,9 +396,9 @@ private:
|
||||
MacroPtr m_macroRecording;
|
||||
MacroPtr m_macroPlaying;
|
||||
|
||||
DMachineInfoPtrVec m_machines; // all connected ones
|
||||
DMachineInfoPtr m_machine; // current
|
||||
std::string m_rpcHost; // current RPC host
|
||||
DMachineInfoPtrVec m_machines; // All connected machines. 0th is local.
|
||||
DMachineInfoPtr m_machine; // Current machine
|
||||
std::string m_rpcHost; // Current RPC host
|
||||
|
||||
DSandboxInfoPtrVec m_sandboxes;
|
||||
DThreadInfoPtrVec m_threads;
|
||||
@@ -465,6 +455,7 @@ private:
|
||||
void record(const char *line);
|
||||
|
||||
// connections
|
||||
void closeAllConnections();
|
||||
void switchMachine(DMachineInfoPtr machine);
|
||||
SmartPtr<Socket> connectLocal();
|
||||
bool connectRemote(const std::string &host, int port);
|
||||
|
||||
@@ -70,6 +70,8 @@ void DebuggerCommand::recvImpl(DebuggerThriftBuffer &thrift) {
|
||||
thrift.read(m_version);
|
||||
}
|
||||
|
||||
// Returns false on timeout, true when data has been read even if that data
|
||||
// didn't form a usable command. Is there is no usable command, cmd is null.
|
||||
bool DebuggerCommand::Receive(DebuggerThriftBuffer &thrift,
|
||||
DebuggerCommandPtr &cmd, const char *caller) {
|
||||
TRACE(5, "DebuggerCommand::Receive\n");
|
||||
@@ -79,12 +81,16 @@ bool DebuggerCommand::Receive(DebuggerThriftBuffer &thrift,
|
||||
fds[0].fd = thrift.getSocket()->fd();
|
||||
fds[0].events = POLLIN|POLLERR|POLLHUP;
|
||||
int ret = poll(fds, 1, POLLING_SECONDS * 1000);
|
||||
if (ret == 0) {
|
||||
return false;
|
||||
if (ret == 0) return false; // Timeout
|
||||
if (ret == -1) {
|
||||
auto errorNumber = errno; // Just in case TRACE_RB changes errno
|
||||
TRACE_RB(1, "DebuggerCommand::Receive: error %d\n", errorNumber);
|
||||
return errorNumber != EINTR; // Treat signals as timeouts
|
||||
}
|
||||
// Any error bits set indicate that we have nothing to read, so bail early.
|
||||
if ((ret == -1) || (fds[0].revents != POLLIN)) {
|
||||
return errno != EINTR; // treat signals as timeouts
|
||||
// Any error bits set indicate that we have nothing to read, so fail.
|
||||
if (fds[0].revents != POLLIN) {
|
||||
TRACE_RB(1, "DebuggerCommand::Receive: revents %d\n", fds[0].revents);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t type;
|
||||
@@ -94,7 +100,10 @@ bool DebuggerCommand::Receive(DebuggerThriftBuffer &thrift,
|
||||
thrift.read(type);
|
||||
thrift.read(clsname);
|
||||
} catch (...) {
|
||||
Logger::Error("%s => DebuggerCommand::Receive(): socket error", caller);
|
||||
// Note: this error case is difficult to test. But, it's exactly the same
|
||||
// as the error noted below. Make sure to keep handling of both of these
|
||||
// errors in sync.
|
||||
Logger::Error("%s: socket error receiving command", caller);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -127,6 +136,8 @@ bool DebuggerCommand::Receive(DebuggerThriftBuffer &thrift,
|
||||
case KindOfInterrupt: cmd = DebuggerCommandPtr(new CmdInterrupt()); break;
|
||||
case KindOfSignal : cmd = DebuggerCommandPtr(new CmdSignal ()); break;
|
||||
case KindOfShell : cmd = DebuggerCommandPtr(new CmdShell ()); break;
|
||||
case KindOfInternalTesting :
|
||||
cmd = DebuggerCommandPtr(new CmdInternalTesting()); break;
|
||||
|
||||
case KindOfExtended: {
|
||||
assert(!clsname.empty());
|
||||
@@ -136,13 +147,15 @@ bool DebuggerCommand::Receive(DebuggerThriftBuffer &thrift,
|
||||
}
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
Logger::Error("%s => DebuggerCommand::Receive(): bad cmd type: %d",
|
||||
caller, type);
|
||||
Logger::Error("%s: received bad cmd type: %d", caller, type);
|
||||
cmd.reset();
|
||||
return true;
|
||||
}
|
||||
if (!cmd->recv(thrift)) {
|
||||
Logger::Error("%s => DebuggerCommand::Receive(): socket error", caller);
|
||||
// Note: this error case is easily tested, and we have a test for it. But
|
||||
// the error case noted above is quite difficult to test. Keep these two
|
||||
// in sync.
|
||||
Logger::Error("%s: socket error receiving command", caller);
|
||||
cmd.reset();
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -79,6 +79,10 @@ public:
|
||||
// DebuggerProxy -> DebuggerClient
|
||||
KindOfInterrupt = 10000,
|
||||
KindOfSignal = 10001,
|
||||
|
||||
// Internal testing only
|
||||
KindOfInternalTesting = 20000, // The real test command
|
||||
KindOfInternalTestingBad = 20001, // A command type we never recgonize
|
||||
};
|
||||
|
||||
static bool Receive(DebuggerThriftBuffer &thrift, DebuggerCommandPtr &cmd,
|
||||
|
||||
@@ -42,7 +42,7 @@ DebuggerProxy::DebuggerProxy(SmartPtr<Socket> socket, bool local)
|
||||
|
||||
DebuggerProxy::~DebuggerProxy() {
|
||||
TRACE_RB(2, "DebuggerProxy::~DebuggerProxy starting\n");
|
||||
m_stopped = true;
|
||||
stop();
|
||||
m_signalThread.waitForEnd();
|
||||
|
||||
if (m_dummySandbox) {
|
||||
@@ -91,11 +91,15 @@ void DebuggerProxy::getThreads(DThreadInfoPtrVec &threads) {
|
||||
}
|
||||
}
|
||||
|
||||
// Switch this proxy to debug the given sandbox.
|
||||
bool DebuggerProxy::switchSandbox(const std::string &newId, bool force) {
|
||||
TRACE(2, "DebuggerProxy::switchSandbox\n");
|
||||
return Debugger::SwitchSandbox(shared_from_this(), newId, force);
|
||||
}
|
||||
|
||||
// Callback made by Debugger::SwitchSandbox() when the switch is successful.
|
||||
// NB: this is called with a read lock on the corresponding entry in the sandbox
|
||||
// map.
|
||||
void DebuggerProxy::updateSandbox(DSandboxInfoPtr sandbox) {
|
||||
TRACE(2, "DebuggerProxy::updateSandbox\n");
|
||||
Lock lock(m_mutex);
|
||||
@@ -192,14 +196,14 @@ void DebuggerProxy::getBreakPoints(BreakPointInfoPtrVec &breakpoints) {
|
||||
}
|
||||
|
||||
bool DebuggerProxy::couldBreakEnterClsMethod(const StringData* className) {
|
||||
TRACE(2, "DebuggerProxy::couldBreakEnterClsMethod\n");
|
||||
TRACE(5, "DebuggerProxy::couldBreakEnterClsMethod\n");
|
||||
ReadLock lock(m_breakMutex);
|
||||
StringDataMap::const_accessor acc;
|
||||
return m_breaksEnterClsMethod.find(acc, className);
|
||||
}
|
||||
|
||||
bool DebuggerProxy::couldBreakEnterFunc(const StringData* funcFullName) {
|
||||
TRACE(2, "DebuggerProxy::couldBreakEnterFunc\n");
|
||||
TRACE(5, "DebuggerProxy::couldBreakEnterFunc\n");
|
||||
ReadLock lock(m_breakMutex);
|
||||
StringDataMap::const_accessor acc;
|
||||
return m_breaksEnterFunc.find(acc, funcFullName);
|
||||
@@ -393,15 +397,20 @@ void DebuggerProxy::pollSignal() {
|
||||
}
|
||||
}
|
||||
if (!m_stopped) {
|
||||
// We've noticed that the socket has closed. If there is a thread stopped at
|
||||
// an interrrupt, stop() will help pop it out and cause the proxy to throw
|
||||
// the proper exception to terminate the request.
|
||||
TRACE_RB(2, "DebuggerProxy::pollSignal: "
|
||||
"lost communication with the client, stopping proxy\n");
|
||||
forceQuit();
|
||||
stop();
|
||||
}
|
||||
TRACE_RB(2, "DebuggerProxy::pollSignal: ended\n");
|
||||
}
|
||||
|
||||
void DebuggerProxy::forceQuit() {
|
||||
TRACE_RB(2, "DebuggerProxy::forceQuit\n");
|
||||
// Ask this proxy to stop running and exit cleanly. Used during proxy cleanup,
|
||||
// and from the signal polling thread.
|
||||
void DebuggerProxy::stop() {
|
||||
TRACE_RB(2, "DebuggerProxy::stop\n");
|
||||
DSandboxInfo invalid;
|
||||
Lock l(this);
|
||||
m_sandbox = invalid;
|
||||
@@ -409,6 +418,18 @@ void DebuggerProxy::forceQuit() {
|
||||
// the flag will take care of the rest
|
||||
}
|
||||
|
||||
// Used to quit this proxy, typcially in response to either a quit command from
|
||||
// the client or loss of communication with the client. The proxy is removed
|
||||
// from the proxy map, ensuring no other threads can use the proxy. It stops
|
||||
// the proxy, and then tosses the client exit exception to ensure the current
|
||||
// request is terminated with a nice message.
|
||||
void DebuggerProxy::forceQuit() {
|
||||
TRACE_RB(2, "DebuggerProxy::forceQuit\n");
|
||||
Debugger::RemoveProxy(shared_from_this());
|
||||
stop();
|
||||
throw DebuggerClientExitException();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// helpers
|
||||
|
||||
@@ -573,10 +594,7 @@ bool DebuggerProxy::checkFlowBreak(CmdInterrupt &cmd) {
|
||||
|
||||
void DebuggerProxy::checkStop() {
|
||||
TRACE(5, "DebuggerProxy::checkStop\n");
|
||||
if (m_stopped) {
|
||||
Debugger::RemoveProxy(shared_from_this());
|
||||
throw DebuggerClientExitException();
|
||||
}
|
||||
if (m_stopped) forceQuit();
|
||||
}
|
||||
|
||||
void DebuggerProxy::processInterrupt(CmdInterrupt &cmd) {
|
||||
@@ -624,9 +642,7 @@ void DebuggerProxy::processInterrupt(CmdInterrupt &cmd) {
|
||||
if (res->is(DebuggerCommand::KindOfQuit)) {
|
||||
TRACE_RB(2, "Received quit command\n");
|
||||
res->onServer(*this); // acknowledge receipt so that client can quit.
|
||||
Debugger::RemoveProxy(shared_from_this());
|
||||
forceQuit();
|
||||
throw DebuggerClientExitException();
|
||||
}
|
||||
}
|
||||
bool cmdFailure = false;
|
||||
@@ -648,13 +664,8 @@ void DebuggerProxy::processInterrupt(CmdInterrupt &cmd) {
|
||||
res->getType());
|
||||
cmdFailure = true;
|
||||
}
|
||||
if (cmdFailure) {
|
||||
Debugger::RemoveProxy(shared_from_this());
|
||||
return;
|
||||
}
|
||||
if (res->shouldExitInterrupt()) {
|
||||
return;
|
||||
}
|
||||
if (cmdFailure) forceQuit();
|
||||
if (res->shouldExitInterrupt()) return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ private:
|
||||
|
||||
void changeBreakPointDepth(CmdInterrupt& cmd);
|
||||
|
||||
void stop();
|
||||
|
||||
bool m_stopped;
|
||||
|
||||
bool m_local;
|
||||
|
||||
@@ -142,6 +142,9 @@ void DummySandbox::run() {
|
||||
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());
|
||||
}
|
||||
|
||||
|
||||
@@ -513,10 +513,7 @@ Variant c_DebuggerClient::t_init(CVarRef options) {
|
||||
}
|
||||
|
||||
ret = m_client->initializeMachine();
|
||||
if (!ret) {
|
||||
raise_warning("failed to initialize machine info");
|
||||
return false;
|
||||
}
|
||||
assert(ret); // Always returns true in API mode.
|
||||
|
||||
// To wait for the machine loading sandbox
|
||||
cmd = m_client->waitForNextInterrupt();
|
||||
@@ -591,7 +588,6 @@ Variant c_DebuggerClient::t_processcmd(CVarRef cmdName, CVarRef args) {
|
||||
TRACE(4, "Command raised DebuggerConsoleExitException\n");
|
||||
// Flow-control command goes here
|
||||
Logger::Info("wait for debugger client to stop");
|
||||
m_client->setTakingInterrupt();
|
||||
m_client->setClientState(DebuggerClient::StateBusy);
|
||||
DebuggerCommandPtr cmd = m_client->waitForNextInterrupt();
|
||||
TRACE(4, "waitForNextInterrupt() came back as ");
|
||||
|
||||
@@ -95,11 +95,7 @@ void phpDebuggerErrorHook(const std::string& message) {
|
||||
TRACE(5, "NoBreak flag is on\n");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Eval::Debugger::InterruptVMHook(Eval::ExceptionThrown, String(message));
|
||||
} catch (const Eval::DebuggerClientExitException &e) {
|
||||
// Don't disturb the error handler just because a debugger quits.
|
||||
}
|
||||
Eval::Debugger::InterruptVMHook(Eval::ExceptionThrown, String(message));
|
||||
TRACE(5, "out phpDebuggerErrorHook()\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
error_log('error_bad_cmd_type_receive.php loaded');
|
||||
@@ -0,0 +1,14 @@
|
||||
Program %s/error_bad_cmd_type_receive.php loaded. Type '[r]un' or '[c]ontinue' to go.
|
||||
break error_bad_cmd_type_receive.php:3
|
||||
Breakpoint 1 set on line 3 of error_bad_cmd_type_receive.php
|
||||
run
|
||||
Breakpoint 1 reached on line 3 of %s/error_bad_cmd_type_receive.php
|
||||
2
|
||||
3 error_log('error_bad_cmd_type_receive.php loaded');
|
||||
4 (END)
|
||||
|
||||
internaltesting badcmdtypereceive
|
||||
Executing internal test...
|
||||
Receive result: received bad cmd type: 20001
|
||||
Unable to communicate with server. Server's down?
|
||||
Unable to reconnect to server, exiting.
|
||||
@@ -0,0 +1,3 @@
|
||||
break error_bad_cmd_type_receive.php:3
|
||||
run
|
||||
internaltesting badcmdtypereceive
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
error_log('error_bad_cmd_type_send.php loaded');
|
||||
@@ -0,0 +1,13 @@
|
||||
Program %s/error_bad_cmd_type_send.php loaded. Type '[r]un' or '[c]ontinue' to go.
|
||||
break error_bad_cmd_type_send.php:3
|
||||
Breakpoint 1 set on line 3 of error_bad_cmd_type_send.php
|
||||
run
|
||||
Breakpoint 1 reached on line 3 of %s/error_bad_cmd_type_send.php
|
||||
2
|
||||
3 error_log('error_bad_cmd_type_send.php loaded');
|
||||
4 (END)
|
||||
|
||||
internaltesting badcmdtypesend
|
||||
Executing internal test...
|
||||
DebuggerProxy::processInterrupt(): received bad cmd type: 20001
|
||||
Unable to communicate with server. Server's down?
|
||||
@@ -0,0 +1,3 @@
|
||||
break error_bad_cmd_type_send.php:3
|
||||
run
|
||||
internaltesting badcmdtypesend
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
error_log('error_short_cmd_receive.php loaded');
|
||||
@@ -0,0 +1,15 @@
|
||||
Program %s/error_short_cmd_receive.php loaded. Type '[r]un' or '[c]ontinue' to go.
|
||||
break error_short_cmd_receive.php:3
|
||||
Breakpoint 1 set on line 3 of error_short_cmd_receive.php
|
||||
run
|
||||
Breakpoint 1 reached on line 3 of %s/error_short_cmd_receive.php
|
||||
2
|
||||
3 error_log('error_short_cmd_receive.php loaded');
|
||||
4 (END)
|
||||
|
||||
internaltesting shortcmdreceive
|
||||
Executing internal test...
|
||||
DebuggerCommand::recv(): a socket error has happened
|
||||
Receive result: socket error receiving command
|
||||
Unable to communicate with server. Server's down?
|
||||
Unable to reconnect to server, exiting.
|
||||
@@ -0,0 +1,3 @@
|
||||
break error_short_cmd_receive.php:3
|
||||
run
|
||||
internaltesting shortcmdreceive
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
error_log('error_short_cmd_send.php loaded');
|
||||
@@ -0,0 +1,14 @@
|
||||
Program %s/error_short_cmd_send.php loaded. Type '[r]un' or '[c]ontinue' to go.
|
||||
break error_short_cmd_send.php:3
|
||||
Breakpoint 1 set on line 3 of error_short_cmd_send.php
|
||||
run
|
||||
Breakpoint 1 reached on line 3 of %s/error_short_cmd_send.php
|
||||
2
|
||||
3 error_log('error_short_cmd_send.php loaded');
|
||||
4 (END)
|
||||
|
||||
internaltesting shortcmdsend
|
||||
Executing internal test...
|
||||
DebuggerCommand::recv(): a socket error has happened
|
||||
DebuggerProxy::processInterrupt(): socket error receiving command
|
||||
Unable to communicate with server. Server's down?
|
||||
@@ -0,0 +1,3 @@
|
||||
break error_short_cmd_send.php:3
|
||||
run
|
||||
internaltesting shortcmdsend
|
||||
Referência em uma Nova Issue
Bloquear um usuário