/* +----------------------------------------------------------------------+ | 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, 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_okayToPoll(true), 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 &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::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 { // We're about to send the client an interrupt and start // waiting for commands back from it. Disable signal polling // during this time, since our protocol requires that only one // thread talk to the client at a time. disableSignalPolling(); SCOPE_EXIT { enableSignalPolling(); }; processInterrupt(cmd); // 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); } // Allow the signal polling thread to send CmdSignal messages to the // client to see if it there is a signal to repond to. void DebuggerProxy::enableSignalPolling() { Lock lock(m_signalMutex); m_okayToPoll = true; } // Prevent the signal polling thread from sending CmdSignal messages // to the client. Polling is disabled while a thread is stopped at an // interrupt and responding to messages from the client. void DebuggerProxy::disableSignalPolling() { Lock lock(m_signalMutex); m_okayToPoll = false; // Drop any pending signal since we're about to start processing // interrupts again anyway. m_signum = CmdSignal::SignalNone; } 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); // Don't actually poll if another thread is already in a command // processing loop with the client. if (!m_okayToPoll) continue; // 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(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, sandbox switching, and from the signal polling // thread. This can be used from a thread which is not stopped at the // given proxy's interrupt. 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. // NB: 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 "append(s_stdout_color); } sb->append(s, len); if (s_stdout_color) { sb->append(ANSI_COLOR_END); } } // Passed to the ExecutionContext during Eval to add writes to stderr // to the output buffer string. 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(*this, 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(res->getType())); // Any control flow command gets installed here and we continue execution. m_flow = dynamic_pointer_cast(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, int frame, int flags) { TRACE(2, "DebuggerProxy::ExecutePHP\n"); Variant ret; // Wire up stdout and stderr to our own string buffer so we can pass // any output back to the client. StringBuffer sb; StringBuffer *save = g_context->swapOutputBuffer(nullptr); g_context->setStdout(append_stdout, &sb); if (flags & ExecutePHPFlagsLog) { Logger::SetThreadHook(append_stderr, &sb); } try { String code(php.c_str(), php.size(), CopyString); // @TODO: enable this once task #2608250 is completed. #if 0 // We're about to start executing more PHP. This is typcially done // in response to commands from the client, and the client expects // those commands to send more interrupts since, of course, the // user might want to debug the code we're about to run. If we're // already processing an interrupt, enable signal polling around // the execution fo the new PHP to ensure that we can handle // signals while doing so. if (flags & ExecutePHPFlagsAtInterrupt) enableSignalPolling(); SCOPE_EXIT { if (flags & ExecutePHPFlagsAtInterrupt) disableSignalPolling(); }; #endif g_vmContext->evalPHPDebugger((TypedValue*)&ret, code.get(), frame); } catch (InvalidFunctionCallException &e) { sb.append(Debugger::ColorStderr(String(e.what()))); sb.append(Debugger::ColorStderr( "You may also need to connect to a host " "(e.g., machine connect localhost).")); } catch (Exception &e) { sb.append(Debugger::ColorStderr(String(e.what()))); } catch (Object &e) { try { sb.append(Debugger::ColorStderr(e.toString())); } catch (BadTypeConversionException &e) { sb.append(Debugger::ColorStderr (String("(object without __toString() is thrown)"))); } } catch (...) { sb.append(Debugger::ColorStderr(String("(unknown exception was thrown)"))); } g_context->setStdout(nullptr, nullptr); g_context->swapOutputBuffer(save); if (flags & ExecutePHPFlagsLog) { Logger::SetThreadHook(nullptr, nullptr); } output = sb.detach(); return ret; } // 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(*this, 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(*this, cmd.getInterruptType(), *cmd.getSite())) { m_breakpoints[i]->changeBreakPointDepth(getRealStackDepth()); } } } /////////////////////////////////////////////////////////////////////////////// }}