Arquivos
hhvm/hphp/runtime/debugger/debugger_proxy.cpp
T
Mike Magruder 3a750ae5ac Add client connect info to the debugger usage log.
Update the connect usage log message to include details of the client that is connecting to a proxy, or to state if it's a connection due to script debugging.
2013-07-11 15:11:12 -07:00

741 linhas
25 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/debugger_proxy.h"
#include "hphp/runtime/debugger/cmd/cmd_interrupt.h"
#include "hphp/runtime/debugger/cmd/cmd_flow_control.h"
#include "hphp/runtime/debugger/cmd/cmd_signal.h"
#include "hphp/runtime/debugger/cmd/cmd_machine.h"
#include "hphp/runtime/debugger/debugger.h"
#include "hphp/runtime/base/runtime_option.h"
#include "hphp/runtime/ext/ext_socket.h"
#include "hphp/runtime/vm/debugger_hook.h"
#include "hphp/util/process.h"
#include "hphp/util/logger.h"
namespace HPHP { namespace Eval {
///////////////////////////////////////////////////////////////////////////////
TRACE_SET_MOD(debugger);
DebuggerProxy::DebuggerProxy(SmartPtr<Socket> socket, bool local)
: m_stopped(false), m_local(local), m_dummySandbox(nullptr),
m_hasBreakPoints(false), m_threadMode(Normal), m_thread(0),
m_signalThread(this, &DebuggerProxy::pollSignal),
m_signum(CmdSignal::SignalNone) {
TRACE(2, "DebuggerProxy::DebuggerProxy\n");
m_thrift.create(socket);
m_dummyInfo = DSandboxInfo::CreateDummyInfo((int64_t)this);
Variant address;
Variant port;
std::string clientDetails;
if (!local) {
if (getClientConnectionInfo(ref(address), ref(port))) {
clientDetails = folly::stringPrintf("From %s:%d",
address.toString().data(),
port.toInt32());
} else {
clientDetails = "Failed to get client connection details";
}
} else {
clientDetails = "Local script connection";
}
Debugger::UsageLog("server", m_dummyInfo.id(), "connect", clientDetails);
}
DebuggerProxy::~DebuggerProxy() {
TRACE_RB(2, "DebuggerProxy::~DebuggerProxy starting\n");
stop();
m_signalThread.waitForEnd();
if (m_dummySandbox) {
m_dummySandbox->stop();
}
Debugger::UsageLog("server", getSandboxId(), "disconnect");
TRACE_RB(2, "DebuggerProxy::~DebuggerProxy complete\n");
}
const char *DebuggerProxy::getThreadType() const {
TRACE(2, "DebuggerProxy::getThreadType\n");
return isLocal() ? "Command Line Script" : "Dummy Sandbox";
}
DSandboxInfo DebuggerProxy::getSandbox() const {
TRACE(2, "DebuggerProxy::getSandbox\n");
Lock lock(m_mutex);
return m_sandbox;
}
std::string DebuggerProxy::getSandboxId() const {
TRACE(2, "DebuggerProxy::getSandboxId\n");
Lock lock(m_mutex);
return m_sandbox.id();
}
void DebuggerProxy::getThreads(DThreadInfoPtrVec &threads) {
TRACE(2, "DebuggerProxy::getThreads\n");
Lock lock(this);
std::stack<void *> &interrupts =
ThreadInfo::s_threadInfo->m_reqInjectionData.interrupts;
assert(!interrupts.empty());
if (!interrupts.empty()) {
CmdInterrupt *tint = (CmdInterrupt*)interrupts.top();
assert(tint);
if (tint) {
threads.push_back(createThreadInfo(tint->desc()));
}
}
for (std::map<int64_t, DThreadInfoPtr>::const_iterator iter =
m_threads.begin(); iter != m_threads.end(); ++iter) {
DThreadInfoPtr thread(new DThreadInfo());
*thread = *iter->second;
threads.push_back(thread);
}
}
// 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);
if (sandbox) {
if (m_sandbox.id() != sandbox->id()) {
m_sandbox = *sandbox;
} else {
m_sandbox.update(*sandbox);
}
}
}
bool DebuggerProxy::switchThread(DThreadInfoPtr thread) {
TRACE(2, "DebuggerProxy::switchThread\n");
Lock lock(this);
if (m_threads.find(thread->m_id) != m_threads.end()) {
m_newThread = thread;
return true;
}
return false;
}
void DebuggerProxy::switchThreadMode(ThreadMode mode,
int64_t threadId /* = 0 */) {
TRACE(2, "DebuggerProxy::switchThreadMode\n");
Lock lock(this);
m_threadMode = mode;
if (threadId) {
m_thread = threadId;
m_newThread.reset();
notifyAll(); // since threadId != 0, we're still waking up just one thread
} else if (mode == Normal) {
m_thread = 0;
notify();
} else {
m_thread = (int64_t)Process::GetThreadId();
}
if (mode == Normal) {
m_flow.reset();
}
}
void DebuggerProxy::startDummySandbox() {
TRACE(2, "DebuggerProxy::startDummySandbox\n");
m_dummySandbox =
new DummySandbox(this, RuntimeOption::DebuggerDefaultSandboxPath,
RuntimeOption::DebuggerStartupDocument);
m_dummySandbox->start();
}
void DebuggerProxy::notifyDummySandbox() {
TRACE(2, "DebuggerProxy::notifyDummySandbox\n");
m_dummySandbox->notifySignal(CmdSignal::SignalBreak);
}
void DebuggerProxy::setBreakPoints(BreakPointInfoPtrVec &breakpoints) {
TRACE(2, "DebuggerProxy::setBreakPoints\n");
// Hold the break mutex while we update the proxy's state. There's no need
// to hold it over the longer operation to set breakpoints in each file later.
{
WriteLock lock(m_breakMutex);
m_breakpoints = breakpoints;
m_hasBreakPoints = !m_breakpoints.empty();
}
phpSetBreakPoints(this);
}
void DebuggerProxy::getBreakPoints(BreakPointInfoPtrVec &breakpoints) {
TRACE(2, "DebuggerProxy::getBreakPoints\n");
ReadLock lock(m_breakMutex);
breakpoints = m_breakpoints;
}
// Proxy needs to be interrupted because it has something setup which may need
// processing; breakpoints, flow control commands, a signal.
bool DebuggerProxy::needInterrupt() {
TRACE(2, "DebuggerProxy::needInterrupt\n");
return m_hasBreakPoints || m_flow || m_signum != CmdSignal::SignalNone;
}
// We need VM interrupts if we're in a state that requires the debugger to be
// interrupted for every opcode.
bool DebuggerProxy::needVMInterrupts() {
TRACE(2, "DebuggerProxy::needVMInterrupts\n");
bool flowNeedsInterrupt = (m_flow && m_flow->needsVMInterrupt());
return flowNeedsInterrupt || m_signum != CmdSignal::SignalNone;
}
// 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.
changeBreakPointDepth(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)) {
while (true) {
try {
Lock lock(m_signalMutex); // Block the signal polling thread.
m_signum = CmdSignal::SignalNone;
processInterrupt(cmd);
// If we've just processed a breakpoint, change it so we don't break at
// it again until we're off this site.
// Note: this feels quite out of place in here... should be part of
// processing the break point itself. Added a note to task #2361050.
if (cmd.getInterruptType() == BreakPointReached) {
BreakPointInfoPtr bp = getBreakPointAtCmd(cmd);
if (bp) {
int stackDepth = getRealStackDepth();
if (bp->breakable(stackDepth)) {
bp->unsetBreakable(stackDepth);
}
}
}
} catch (const DebuggerException &e) {
switchThreadMode(Normal);
throw;
} catch (...) {
assert(false); // no other exceptions should be seen here
switchThreadMode(Normal);
throw;
}
if (cmd.getInterruptType() == PSPEnded) {
switchThreadMode(Normal);
return; // we are done with this thread
}
if (!m_newThread) {
break; // we're not switching threads
}
switchThreadMode(Normal, m_newThread->m_id);
blockUntilOwn(cmd, false);
}
}
if (m_threadMode == Normal) {
Lock lock(this);
m_thread = 0;
notify();
} else if (cmd.getInterruptType() == PSPEnded) {
switchThreadMode(Normal); // we are done with this thread
}
}
// Sends a copy of the given command to the associated client
// using the buffer in m_thrift. Returns false if an exception
// occurs during the send (typically a socket error).
bool DebuggerProxy::sendToClient(DebuggerCommand *cmd) {
TRACE(2, "DebuggerProxy::sendToClient\n");
return cmd->send(m_thrift);
}
void DebuggerProxy::startSignalThread() {
TRACE(2, "DebuggerProxy::startSignalThread\n");
m_signalThread.start();
}
// This gets it's own thread, and polls the client once per second to see if
// there is a signal, i.e., if the user has pressed Ctrl-C, etc. If there is a
// signal, it is passed as an interrupt to the proxy in an attempt to get other
// threads in the sandbox to stop.
//
// If another thread in the sandbox fails to stop and consume the signal then
// it will be passed to the dummy sandbox instead.
void DebuggerProxy::pollSignal() {
TRACE_RB(2, "DebuggerProxy::pollSignal: starting\n");
while (!m_stopped) {
sleep(1);
// after 1 second that no active thread picks up the signal, we send it
// to dummy sandbox
if (m_signum != CmdSignal::SignalNone && m_dummySandbox) {
Lock lock(m_signumMutex);
if (m_signum != CmdSignal::SignalNone) {
TRACE_RB(2, "DebuggerProxy::pollSignal: sending to dummy sandbox\n");
m_dummySandbox->notifySignal(m_signum);
m_signum = CmdSignal::SignalNone;
}
}
// Block any threads that might be interrupting from communicating with the
// client until we're done with this poll.
Lock lock(m_signalMutex);
// Send CmdSignal over to the client and wait for a response.
CmdSignal cmd;
if (!cmd.onServer(*this)) {
TRACE_RB(2, "DebuggerProxy::pollSignal: "
"Failed to send CmdSignal to client\n");
break;
}
// We will loop forever until DebuggerClient sends us something, modulo
// transport failure or a shutdown request.
DebuggerCommandPtr res;
while (!DebuggerCommand::Receive(m_thrift, res,
"DebuggerProxy::pollSignal()")) {
if (m_stopped) {
TRACE_RB(2, "DebuggerProxy::pollSignal: "
"signal thread asked to stop while waiting "
"for CmdSignal back from the client\n");
break;
}
}
if (!res) {
if (!m_stopped) {
TRACE_RB(2, "DebuggerProxy::pollSignal: "
"Failed to get CmdSignal back from client\n");
}
break;
}
CmdSignalPtr sig = dynamic_pointer_cast<CmdSignal>(res);
if (!sig) {
TRACE_RB(2, "DebuggerProxy::pollSignal: "
"bad response from signal polling: %d", res->getType());
break;
}
m_signum = sig->getSignal();
if (m_signum != CmdSignal::SignalNone) {
TRACE_RB(2, "DebuggerProxy::pollSignal: "
"got interrupt signal from client\n");
Debugger::RequestInterrupt(shared_from_this());
}
}
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");
stop();
}
TRACE_RB(2, "DebuggerProxy::pollSignal: ended\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;
m_stopped = true;
// 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();
}
// Grab the ip address and port of the client that is connected to this proxy.
bool DebuggerProxy::getClientConnectionInfo(VRefParam address,
VRefParam port) {
Object s(getSocket().get());
return f_socket_getpeername(s, address, port);
}
///////////////////////////////////////////////////////////////////////////////
// helpers
std::string DebuggerProxy::MakePHP(const std::string &php) {
TRACE(2, "DebuggerProxy::MakePHP\n");
return "<?php " + php + ";";
}
std::string DebuggerProxy::MakePHPReturn(const std::string &php) {
TRACE(2, "DebuggerProxy::MakePHPReturn\n");
return "<?php return " + php + ";";
}
static void append_stdout(const char *s, int len, void *data) {
TRACE(2, "DebuggerProxy::append_stdout\n");
StringBuffer *sb = (StringBuffer*)data;
if (s_stdout_color) {
sb->append(s_stdout_color);
}
sb->append(s, len);
if (s_stdout_color) {
sb->append(ANSI_COLOR_END);
}
}
static void append_stderr(const char *header, const char *msg,
const char *ending, void *data) {
TRACE(2, "DebuggerProxy::append_stderr\n");
StringBuffer *sb = (StringBuffer*)data;
if (s_stderr_color) {
sb->append(s_stderr_color);
}
sb->append(msg);
sb->append(ending);
if (s_stderr_color) {
sb->append(ANSI_COLOR_END);
}
}
DThreadInfoPtr DebuggerProxy::createThreadInfo(const std::string &desc) {
TRACE(2, "DebuggerProxy::createThreadInfo\n");
DThreadInfoPtr info(new DThreadInfo());
info->m_id = (int64_t)Process::GetThreadId();
info->m_desc = desc;
Transport *transport = g_context->getTransport();
if (transport) {
info->m_type = transport->getThreadTypeName();
info->m_url = transport->getCommand();
} else {
info->m_type = getThreadType();
}
return info;
}
// Waits until this thread is the one that the proxy considers the current
// thread.
// NB: if the thread is not the current thread, and we're asked to check
// breakpoints, then if there are no breakpoints which could effect this
// thread we simply return false and stop processing the current interrupt.
bool DebuggerProxy::blockUntilOwn(CmdInterrupt &cmd, bool check) {
TRACE(2, "DebuggerProxy::blockUntilOwn\n");
int64_t self = cmd.getThreadId();
Lock lock(this);
if (m_thread && m_thread != self) {
if (check && (m_threadMode == Exclusive || !checkBreakPoints(cmd))) {
// Flow control commands only belong to sticky thread
return false;
}
m_threads[self] = createThreadInfo(cmd.desc());
while (!m_stopped && m_thread && m_thread != self) {
wait(1);
// if for whatever reason, m_thread isn't debugging anymore (for example,
// it runs in Sticky mode, but it finishes running), kick it out.
if (!Debugger::IsThreadDebugging(m_thread)) {
m_threadMode = Normal;
m_thread = self;
m_newThread.reset();
m_flow.reset();
}
}
m_threads.erase(self);
if (m_stopped) return false;
if (!checkBreakPoints(cmd)) {
// The breakpoint might have been removed while I'm waiting
return false;
}
}
// This thread is now the one the proxy considers the current thread.
if (m_thread == 0) {
m_thread = self;
}
return true;
}
// Checks whether the cmd has any breakpoints that match the current Site.
// Also returns true for cmds that should always break, like SessionStarted,
// and returns true when we have special modes setup for, say, breaking on
// RequestEnded, PSPEnded, etc.
bool DebuggerProxy::checkBreakPoints(CmdInterrupt &cmd) {
TRACE(2, "DebuggerProxy::checkBreakPoints\n");
ReadLock lock(m_breakMutex);
return cmd.shouldBreak(m_breakpoints, getRealStackDepth());
}
// Check if we should stop due to flow control, breakpoints, and signals.
bool DebuggerProxy::checkFlowBreak(CmdInterrupt &cmd) {
TRACE(2, "DebuggerProxy::checkFlowBreak\n");
// If there is an outstanding Ctrl-C from the client, go ahead and break now.
// Note: this stops any flow control command we might have in-flight.
if (m_signum == CmdSignal::SignalBreak) {
Lock lock(m_signumMutex);
if (m_signum == CmdSignal::SignalBreak) {
m_signum = CmdSignal::SignalNone;
m_flow.reset();
return true;
}
}
// Note that even if fcShouldBreak, we still need to test bpShouldBreak
// so to correctly report breakpoint reached + evaluating watchpoints;
// vice versa, so to give a flow cmd a chance to react.
// Note: a Continue cmd is a bit special. We need to process it _only_ if
// we decide to break.
bool fcShouldBreak = false; // should I break according to flow control?
bool bpShouldBreak = false; // should I break according to breakpoints?
if ((cmd.getInterruptType() == BreakPointReached ||
cmd.getInterruptType() == HardBreakPoint ||
cmd.getInterruptType() == ExceptionHandler) && m_flow) {
if (!m_flow->is(DebuggerCommand::KindOfContinue)) {
m_flow->onBeginInterrupt(*this, cmd);
if (m_flow->complete()) {
fcShouldBreak = true;
m_flow.reset();
}
}
}
// NB: this also checks whether we should be stopping at special interrupt
// sites, like SessionStarted, RequestEnded, ExceptionThrown, etc.
bpShouldBreak = checkBreakPoints(cmd);
// This is done before KindOfContinue testing.
if (!fcShouldBreak && !bpShouldBreak) {
return false;
}
if ((cmd.getInterruptType() == BreakPointReached ||
cmd.getInterruptType() == HardBreakPoint) && m_flow) {
if (m_flow->is(DebuggerCommand::KindOfContinue)) {
m_flow->onBeginInterrupt(*this, cmd);
if (m_flow->complete()) m_flow.reset();
return false;
}
}
return true;
}
void DebuggerProxy::checkStop() {
TRACE(5, "DebuggerProxy::checkStop\n");
if (m_stopped) forceQuit();
}
void DebuggerProxy::processInterrupt(CmdInterrupt &cmd) {
TRACE_RB(2, "DebuggerProxy::processInterrupt\n");
// Do the server-side work for this interrupt, which just notifies the client.
if (!cmd.onServer(*this)) {
TRACE_RB(1, "Failed to send CmdInterrupt to client\n");
Debugger::RemoveProxy(shared_from_this()); // on socket error
return;
}
Debugger::UsageLogInterrupt("server", getSandboxId(), cmd);
// Wait for commands from the debugger client and process them. We'll stay
// here until we get a command that should cause the thread to continue.
while (true) {
DebuggerCommandPtr res;
while (!DebuggerCommand::Receive(m_thrift, res,
"DebuggerProxy::processInterrupt()")) {
// we will wait forever until DebuggerClient sends us something
checkStop();
}
checkStop();
if (res) {
TRACE_RB(2, "Proxy got cmd type %d\n", res->getType());
Debugger::UsageLog("server", getSandboxId(),
boost::lexical_cast<string>(res->getType()));
// Any control flow command gets installed here and we continue execution.
m_flow = dynamic_pointer_cast<CmdFlowControl>(res);
if (m_flow) {
m_flow->onSetup(*this, cmd);
if (!m_flow->complete()) {
TRACE_RB(2, "Incomplete flow command %d remaining on proxy for "
"further processing\n", m_flow->getType());
if (m_threadMode == Normal) {
switchThreadMode(Sticky);
}
} else {
// The flow cmd has determined that it is done with its work and
// doesn't need to remain for later processing.
TRACE_RB(2, "Flow command %d completed\n", m_flow->getType());
m_flow.reset();
}
return;
}
if (res->is(DebuggerCommand::KindOfQuit)) {
TRACE_RB(2, "Received quit command\n");
res->onServer(*this); // acknowledge receipt so that client can quit.
forceQuit();
}
}
bool cmdFailure = false;
try {
// Perform the server-side work for this command.
if (res) {
if (!res->onServer(*this)) {
TRACE_RB(1, "Failed to execute cmd %d from client\n", res->getType());
cmdFailure = true;
}
} else {
TRACE_RB(1, "Failed to receive cmd from client\n");
cmdFailure = true;
}
} catch (const DebuggerException &e) {
throw;
} catch (...) {
TRACE_RB(1, "Cmd type %d onServer() threw non DebuggerException",
res->getType());
cmdFailure = true;
}
if (cmdFailure) forceQuit();
if (res->shouldExitInterrupt()) return;
}
}
///////////////////////////////////////////////////////////////////////////////
Variant DebuggerProxy::ExecutePHP(const std::string &php, String &output,
bool log, int frame) {
TRACE(2, "DebuggerProxy::ExecutePHP\n");
Variant ret;
StringBuffer sb;
StringBuffer *save = g_context->swapOutputBuffer(nullptr);
g_context->setStdout(append_stdout, &sb);
if (log) {
Logger::SetThreadHook(append_stderr, &sb);
}
try {
String code(php.c_str(), php.size(), CopyString);
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 (log) {
Logger::SetThreadHook(nullptr, nullptr);
}
output = sb.detach();
return ret;
}
// There could be multiple breakpoints at one place but we can manage this
// with only one breakpoint.
BreakPointInfoPtr DebuggerProxy::getBreakPointAtCmd(CmdInterrupt& cmd) {
TRACE(2, "DebuggerProxy::getBreakPointAtCmd\n");
for (unsigned int i = 0; i < m_breakpoints.size(); ++i) {
BreakPointInfoPtr bp = m_breakpoints[i];
if (bp->m_state != BreakPointInfo::Disabled &&
bp->match(cmd.getInterruptType(), *cmd.getSite())) {
return bp;
}
}
return BreakPointInfoPtr();
}
int DebuggerProxy::getRealStackDepth() {
TRACE(2, "DebuggerProxy::getRealStackDepth\n");
int depth = 0;
VMExecutionContext* context = g_vmContext;
ActRec *fp = context->getFP();
if (!fp) return 0;
while (fp != nullptr) {
fp = context->getPrevVMState(fp, nullptr, nullptr);
depth++;
}
return depth;
}
int DebuggerProxy::getStackDepth() {
TRACE(2, "DebuggerProxy::getStackDepth\n");
int depth = 0;
VMExecutionContext* context = g_vmContext;
ActRec *fp = context->getFP();
if (!fp) return 0;
ActRec *prev = fp->arGetSfp();
while (fp != prev) {
fp = prev;
prev = fp->arGetSfp();
depth++;
}
return depth;
}
/**
* If a breakpoint is set at that depth,
* this function clears the current depth information
* after the breakpoint has passed
*/
void DebuggerProxy::changeBreakPointDepth(CmdInterrupt& cmd) {
TRACE(2, "DebuggerProxy::changeBreakPointDepth\n");
for (unsigned int i = 0; i < m_breakpoints.size(); ++i) {
// if the site changes, then update the breakpoint depth
BreakPointInfoPtr bp = m_breakpoints[i];
if (bp->m_state != BreakPointInfo::Disabled &&
!bp->match(cmd.getInterruptType(), *cmd.getSite())) {
m_breakpoints[i]->changeBreakPointDepth(getRealStackDepth());
}
}
}
///////////////////////////////////////////////////////////////////////////////
}}