Arquivos
hhvm/hphp/runtime/debugger/cmd/cmd_where.cpp
T
Mike Magruder 95e5b48cd9 Expose basic async stack in the debugger
Expose the async stack in the debugger. This is the stack of, say, generators driven from the ASIO extension. This is a modification to the where command. The new command type accounts for back compat between old servers/clients. I've added a shortcut, "wa", which is a bit faster to type.

I also modified the normal stack trace to not print bogus lines for functions with no file/line info.

Differential Revision: D920910
2013-08-13 14:24:24 -07:00

239 linhas
7.9 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/cmd/cmd_where.h"
#include "hphp/runtime/base/array-iterator.h"
#include "hphp/runtime/base/comparisons.h"
#include "hphp/runtime/ext/ext_asio.h"
#include "hphp/runtime/ext/ext_continuation.h"
namespace HPHP { namespace Eval {
///////////////////////////////////////////////////////////////////////////////
TRACE_SET_MOD(debugger);
void CmdWhere::sendImpl(DebuggerThriftBuffer &thrift) {
DebuggerCommand::sendImpl(thrift);
{
String sdata;
DebuggerWireHelpers::WireSerialize(m_stacktrace, sdata);
thrift.write(sdata);
}
thrift.write(m_stackArgs);
}
void CmdWhere::recvImpl(DebuggerThriftBuffer &thrift) {
DebuggerCommand::recvImpl(thrift);
{
String sdata;
thrift.read(sdata);
if (DebuggerWireHelpers::WireUnserialize(sdata, m_stacktrace) !=
DebuggerWireHelpers::NoError) {
m_stacktrace = null_array;
m_wireError = sdata;
}
}
thrift.read(m_stackArgs);
}
void CmdWhere::help(DebuggerClient &client) {
client.helpTitle("Where Command");
client.helpCmds(
"[w]here", "displays current stacktrace",
"[w]here [a]sync", "displays the current async stacktrace",
"wa", "shortcut for [w]here [a]sync",
"[w]here {[a]sync]} {num}", "displays number of innermost frames",
"[w]here {[a]sync]} -{num}", "displays number of outermost frames",
nullptr
);
client.helpBody(
"Use '[u]p {num}' or '[d]own {num}' to walk up or down the stacktrace. "
"Use '[f]rame {index}' to jump to one particular frame. At any frame, "
"Use '[v]ariable' command to display all local variables.\n"
"\n"
"Use '[w]here [a]sync' from within an async method, like a generator, "
"to get the current stack of async methods."
);
}
Array CmdWhere::fetchStackTrace(DebuggerClient &client) {
Array st = client.getStackTrace();
// Only grab a new stack trace if we don't have one cached, or if
// the one cached does not match the type of stack trace being
// requested.
bool isAsync = m_type == KindOfWhereAsync;
if (st.isNull() || (isAsync != client.isStackTraceAsync())) {
m_stackArgs = client.getDebuggerStackArgs();
CmdWherePtr cmd = client.xend<CmdWhere>(this);
st = cmd->m_stacktrace;
client.setStackTrace(st, isAsync);
}
return st;
}
void CmdWhere::onClient(DebuggerClient &client) {
if (DebuggerCommand::displayedHelp(client)) return;
if (client.argCount() > 2) {
help(client);
return;
}
int argBase = 1;
if ((client.argCount() > 0) && client.arg(argBase, "async")) {
// We use a different command type for an async stack trace, so we
// can both send and receive different data and still keep the
// existing Where command unchanged. This ensures that old clients
// can still get a stack trace from a newer server, and vice
// versa.
m_type = KindOfWhereAsync;
argBase++;
client.info("Fetching async stacktrace...");
}
Array st = fetchStackTrace(client);
if (st.empty()) {
if (m_type != KindOfWhereAsync) {
client.info("(no stacktrace to display or in global scope)");
client.info("If you hit the serialization limit, try "
"\"set sa off\" to get the stack without args");
} else {
client.info("(no async stacktrace to display)");
}
return;
}
// so list command can default to current frame
client.moveToFrame(client.getFrame(), false);
if (client.argCount() < argBase) {
int i = 0;
for (ArrayIter iter(st); iter; ++iter) {
client.printFrame(i, iter.second().toArray());
++i;
if (i % DebuggerClient::ScrollBlockSize == 0 &&
client.ask("There are %zd more frames. Continue? [Y/n]",
st.size() - i) == 'n') {
break;
}
}
} else {
string snum = client.argValue(argBase);
int num = atoi(snum.c_str());
if (snum[0] == '-') {
snum = snum.substr(1);
}
if (!DebuggerClient::IsValidNumber(snum)) {
client.error("The argument, if specified, has to be numeric.");
return;
}
if (num > 0) {
for (int i = 0; i < num && i < st.size(); i++) {
client.printFrame(i, st[i].toArray());
}
} else if (num < 0) {
for (int i = st.size() + num; i < st.size(); i++) {
client.printFrame(i, st[i].toArray());
}
} else {
client.error("0 was specified for the number of frames");
client.tutorial(
"The optional argument is the number of frames to print out. "
"Use a positive number to print out innermost frames. Use a negative "
"number to print out outermost frames."
);
}
}
}
void CmdWhere::removeArgs() {
// Strip out the args from the stack
static StaticString s_args("args");
Array smallST;
for (ArrayIter iter(m_stacktrace); iter; ++iter) {
CArrRef frame(iter.secondRef().toArray());
Array smallFrame;
for (ArrayIter iter2(frame); iter2; ++iter2) {
if (equal(iter2.first(), s_args)) {
continue;
}
smallFrame.set(iter2.first(), iter2.secondRef());
}
smallST.append(smallFrame);
}
m_stacktrace = smallST;
}
c_WaitableWaitHandle *objToWaitableWaitHandle(Object o) {
assert(dynamic_cast<c_WaitableWaitHandle*>(o.get()));
return static_cast<c_WaitableWaitHandle*>(o.get());
}
const StaticString
s_function("function"),
s_id("id"),
s_file("file"),
s_line("line"),
s_ancestors("ancestors");
// Form a trace of the async stack starting with the currently running
// generator, if any. For now we just toss in the function name and
// id, as well as the pseudo-frames for context breaks at explicit
// joins. Later we'll add more, like file and line, hopefully function
// args, wait handle status, etc.
Array createAsyncStacktrace() {
Array trace;
auto currentWaitHandle = f_asio_get_running();
if (currentWaitHandle.isNull()) return trace;
Array depStack =
objToWaitableWaitHandle(currentWaitHandle)->t_getdependencystack();
for (ArrayIter iter(depStack); iter; ++iter) {
if (iter.secondRef().isNull()) {
trace.append(ArrayInit(0).toVariant());
} else {
auto wh = objToWaitableWaitHandle(iter.secondRef().toObject());
auto parents = wh->t_getparents();
Array ancestors;
for (ArrayIter piter(parents); piter; ++piter) {
// Note: the parent list contains no nulls.
auto parent = objToWaitableWaitHandle(piter.secondRef().toObject());
ancestors.append(parent->t_getname());
}
trace.append(
ArrayInit(3)
.set(s_function, wh->t_getname(), true)
.set(s_id, wh->t_getid(), true)
.set(s_ancestors, ancestors, true)
.toVariant()
);
}
}
return trace;
}
bool CmdWhere::onServer(DebuggerProxy &proxy) {
if (m_type == KindOfWhereAsync) {
m_stacktrace = createAsyncStacktrace();
} else {
m_stacktrace = g_vmContext->debugBacktrace(false, true, false);
if (!m_stackArgs) {
removeArgs();
}
}
return proxy.sendToClient(this);
}
///////////////////////////////////////////////////////////////////////////////
}}