Commit 3f3ad561 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Debugger[CDB]: Reduce roundtrips when stepping.

Report threads and frames along with stopped notification.
Change special logic for step into and switching threads
on interrupt accordingly.
parent 0851ca19
......@@ -36,6 +36,7 @@
#include "eventcallback.h"
#include "outputcallback.h"
#include "stringutils.h"
#include "gdbmihelpers.h"
#include <algorithm>
......@@ -154,19 +155,16 @@ ULONG ExtensionContext::executionStatus() const
// Complete stop parameters with common parameters and report
static inline ExtensionContext::StopReasonMap
completeStopReasons(ExtensionContext::StopReasonMap stopReasons, ULONG ex)
completeStopReasons(CIDebugClient *client, ExtensionContext::StopReasonMap stopReasons, ULONG ex)
{
typedef ExtensionContext::StopReasonMap::value_type StopReasonMapValue;
stopReasons.insert(StopReasonMapValue(std::string("executionStatus"), toString(ex)));
IInterfacePointer<CIDebugClient> client;
if (client.create()) {
if (const ULONG processId = currentProcessId(client.data()))
stopReasons.insert(StopReasonMapValue(std::string("processId"), toString(processId)));
const ULONG threadId = currentThreadId(client.data());
stopReasons.insert(StopReasonMapValue(std::string("threadId"), toString(threadId)));
}
if (const ULONG processId = currentProcessId(client))
stopReasons.insert(StopReasonMapValue(std::string("processId"), toString(processId)));
const ULONG threadId = currentThreadId(client);
stopReasons.insert(StopReasonMapValue(std::string("threadId"), toString(threadId)));
// Any reason?
const std::string reasonKey = std::string(ExtensionContext::stopReasonKeyC);
if (stopReasons.find(reasonKey) == stopReasons.end())
......@@ -174,14 +172,32 @@ static inline ExtensionContext::StopReasonMap
return stopReasons;
}
void ExtensionContext::notifyIdle()
void ExtensionContext::notifyIdleCommand(CIDebugClient *client)
{
discardSymbolGroup();
if (m_stateNotification) {
const StopReasonMap stopReasons = completeStopReasons(m_stopReason, executionStatus());
// Format full thread and stack info along with completed stop reasons.
std::string errorMessage;
ExtensionCommandContext exc(client);
const StopReasonMap stopReasons = completeStopReasons(client, m_stopReason, executionStatus());
// Format
std::ostringstream str;
formatGdbmiHash(str, stopReasons);
formatGdbmiHash(str, stopReasons, false);
const std::string threadInfo = gdbmiThreadList(exc.systemObjects(), exc.symbols(),
exc.control(), exc.advanced(), &errorMessage);
if (threadInfo.empty()) {
str << ",threaderror=" << gdbmiStringFormat(errorMessage);
} else {
str << ",threads=" << threadInfo;
}
const std::string stackInfo = gdbmiStack(exc.control(), exc.symbols(),
maxStackFrames, false, &errorMessage);
if (stackInfo.empty()) {
str << ",stackerror=" << gdbmiStringFormat(errorMessage);
} else {
str << ",stack=" << stackInfo;
}
str << '}';
reportLong('E', 0, "session_idle", str.str());
}
m_stopReason.clear();
......
......@@ -52,6 +52,8 @@ class ExtensionContext {
ExtensionContext();
public:
enum { maxStackFrames = 200 };
// Key used to report stop reason in StopReasonMap
static const char *stopReasonKeyC;
// Map of parameters reported with the next stop as GDBMI
......@@ -85,7 +87,7 @@ public:
// Call from notify handler, tell engine about state.
void notifyState(ULONG Notify);
// register as '.idle_cmd' to notify creator about stop
void notifyIdle();
void notifyIdleCommand(CIDebugClient *client);
// Return symbol group for frame (cached as long as frame/thread do not change).
LocalsSymbolGroup *symbolGroup(CIDebugSymbols *symbols, ULONG threadId, int frame, std::string *errorMessage);
......
......@@ -859,9 +859,9 @@ extern "C" HRESULT CALLBACK modules(CIDebugClient *Client, PCSTR argsIn)
// Report stop of debuggee to Creator. This is hooked up as .idle_cmd command
// by the Creator engine to reliably get notified about stops.
extern "C" HRESULT CALLBACK idle(CIDebugClient *, PCSTR)
extern "C" HRESULT CALLBACK idle(CIDebugClient *client, PCSTR)
{
ExtensionContext::instance().notifyIdle();
ExtensionContext::instance().notifyIdleCommand(client);
return S_OK;
}
......@@ -922,7 +922,7 @@ extern "C" HRESULT CALLBACK stack(CIDebugClient *Client, PCSTR argsIn)
int token;
bool humanReadable = false;
unsigned maxFrames = 1000;
unsigned maxFrames = ExtensionContext::maxStackFrames;
StringList tokens = commandTokens<StringList>(argsIn, &token);
if (!tokens.empty() && tokens.front() == "-h") {
......
......@@ -256,7 +256,7 @@ std::wstring dataToReadableHexW(const unsigned char *begin, const unsigned char
}
// Format a map as a GDBMI hash {key="value",..}
void formatGdbmiHash(std::ostream &os, const std::map<std::string, std::string> &m)
void formatGdbmiHash(std::ostream &os, const std::map<std::string, std::string> &m, bool closeHash)
{
typedef std::map<std::string, std::string>::const_iterator It;
const It begin = m.begin();
......@@ -267,5 +267,6 @@ void formatGdbmiHash(std::ostream &os, const std::map<std::string, std::string>
os << ',';
os << it->first << "=\"" << gdbmiStringFormat(it->second) << '"';
}
os << '}';
if (closeHash)
os << '}';
}
......@@ -186,6 +186,6 @@ std::wstring dataToHexW(const unsigned char *begin, const unsigned char *end);
std::wstring dataToReadableHexW(const unsigned char *begin, const unsigned char *end);
// Format a map as a GDBMI hash {key="value",..}
void formatGdbmiHash(std::ostream &os, const std::map<std::string, std::string> &);
void formatGdbmiHash(std::ostream &os, const std::map<std::string, std::string> &, bool closeHash = true);
#endif // SPLIT_H
......@@ -929,9 +929,9 @@ unsigned CdbEngine::debuggerCapabilities() const
void CdbEngine::executeStep()
{
postCommand(QByteArray("t"), 0); // Step into-> t (trace)
if (!m_operateByInstruction)
m_sourceStepInto = true; // See explanation at handleStackTrace().
postCommand(QByteArray("t"), 0); // Step into-> t (trace)
STATE_DEBUG(state(), Q_FUNC_INFO, __LINE__, "notifyInferiorRunRequested")
notifyInferiorRunRequested();
}
......@@ -1090,6 +1090,15 @@ void CdbEngine::assignValueInDebugger(const WatchData *w, const QString &expr, c
updateLocals();
}
void CdbEngine::parseThreads(const GdbMi &data, int forceCurrentThreadId /* = -1 */)
{
int currentThreadId;
Threads threads = ThreadsHandler::parseGdbmiThreads(data, &currentThreadId);
threadsHandler()->setThreads(threads);
threadsHandler()->setCurrentThreadId(forceCurrentThreadId >= 0 ?
forceCurrentThreadId : currentThreadId);
}
void CdbEngine::handleThreads(const CdbExtensionCommandPtr &reply)
{
if (debug)
......@@ -1097,10 +1106,7 @@ void CdbEngine::handleThreads(const CdbExtensionCommandPtr &reply)
if (reply->success) {
GdbMi data;
data.fromString(reply->reply);
int currentThreadId;
Threads threads = ThreadsHandler::parseGdbmiThreads(data, &currentThreadId);
threadsHandler()->setThreads(threads);
threadsHandler()->setCurrentThreadId(currentThreadId);
parseThreads(data);
// Continue sequence
postCommandSequence(reply->commandSequence);
} else {
......@@ -1572,13 +1578,11 @@ enum StopActionFlags
StopInArtificialThread = 0x40
};
unsigned CdbEngine::examineStopReason(const QByteArray &messageIn,
unsigned CdbEngine::examineStopReason(const GdbMi &stopReason,
QString *message,
QString *exceptionBoxMessage)
{
// Report stop reason (GDBMI)
GdbMi stopReason;
stopReason.fromString(messageIn);
if (debug)
qDebug("%s", stopReason.toString(true, 4).constData());
const QByteArray reason = stopReason.findChild("reason").data();
......@@ -1685,7 +1689,10 @@ void CdbEngine::handleSessionIdle(const QByteArray &messageBA)
// Further examine stop and report to user
QString message;
QString exceptionBoxMessage;
const unsigned stopFlags = examineStopReason(messageBA, &message, &exceptionBoxMessage);
GdbMi stopReason;
stopReason.fromString(messageBA);
int forcedThreadId = -1;
const unsigned stopFlags = examineStopReason(stopReason, &message, &exceptionBoxMessage);
// Do the non-blocking log reporting
if (stopFlags & StopReportLog)
showMessage(message, LogMisc);
......@@ -1707,17 +1714,37 @@ void CdbEngine::handleSessionIdle(const QByteArray &messageBA)
STATE_DEBUG(state(), Q_FUNC_INFO, __LINE__, "notifyInferiorSpontaneousStop")
notifyInferiorSpontaneousStop();
}
const bool sourceStepInto = m_sourceStepInto;
m_sourceStepInto = false;
// Start sequence to get all relevant data.
if (stopFlags & StopInArtificialThread) {
showMessage(tr("Switching to main thread..."), LogMisc);
postCommand("~0 s", 0);
forcedThreadId = 0;
// Re-fetch stack again.
postCommandSequence(CommandListStack);
} else {
const GdbMi stack = stopReason.findChild("stack");
if (stack.isValid()) {
if (parseStackTrace(stack, sourceStepInto) & ParseStackStepInto) {
executeStep(); // Hit on a frame while step into, see parseStackTrace().
return;
}
} else {
showMessage(QString::fromAscii(stopReason.findChild("stackerror").data()), LogError);
}
}
unsigned sequence = CommandListStack|CommandListThreads;
const GdbMi threads = stopReason.findChild("threads");
if (threads.isValid()) {
parseThreads(threads, forcedThreadId);
} else {
showMessage(QString::fromAscii(stopReason.findChild("threaderror").data()), LogError);
}
// Fire off remaining commands asynchronously
if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_REGISTER)))
sequence |= CommandListRegisters;
postCommandSequence(CommandListRegisters);
if (debuggerCore()->isDockVisible(QLatin1String(Constants::DOCKWIDGET_MODULES)))
sequence |= CommandListModules;
postCommandSequence(sequence);
postCommandSequence(CommandListModules);
}
// After the sequence has been sent off and CDB is pondering the commands,
// pop up a message box for exceptions.
......@@ -2176,13 +2203,8 @@ QString CdbEngine::normalizeFileName(const QString &f)
// Parse frame from GDBMI. Duplicate of the gdb code, but that
// has more processing.
static StackFrames parseFrames(const QByteArray &data)
static StackFrames parseFrames(const GdbMi &gdbmi)
{
GdbMi gdbmi;
gdbmi.fromString(data);
if (!gdbmi.isValid())
return StackFrames();
StackFrames rc;
const int count = gdbmi.childCount();
rc.reserve(count);
......@@ -2204,38 +2226,43 @@ static StackFrames parseFrames(const QByteArray &data)
return rc;
}
void CdbEngine::handleStackTrace(const CdbExtensionCommandPtr &command)
unsigned CdbEngine::parseStackTrace(const GdbMi &data, bool sourceStepInto)
{
// Parse frames, find current. Special handling for step into:
// When stepping into on an actual function (source mode) by executing 't', an assembler
// frame pointing at the jmp instruction is hit (noticeable by top function being
// 'ILT+'). If that is the case, execute another 't' to step into the actual function. .
// Note that executing 't 2' does not work since it steps 2 instructions on a non-call code line.
const bool sourceStepInto = m_sourceStepInto;
m_sourceStepInto = false;
if (command->success) {
int current = -1;
StackFrames frames = parseFrames(command->reply);
const int count = frames.size();
for (int i = 0; i < count; i++) {
const bool hasFile = !frames.at(i).file.isEmpty();
// jmp-frame hit by step into, do another 't' and abort sequence.
if (!hasFile && i == 0 && sourceStepInto && frames.at(i).function.contains(QLatin1String("ILT+"))) {
showMessage(QString::fromAscii("Step into: Call instruction hit, performing additional step..."), LogMisc);
executeStep();
return;
}
if (hasFile) {
frames[i].file = QDir::cleanPath(normalizeFileName(frames.at(i).file));
if (current == -1 && frames[i].usable)
current = i;
}
int current = -1;
StackFrames frames = parseFrames(data);
const int count = frames.size();
for (int i = 0; i < count; i++) {
const bool hasFile = !frames.at(i).file.isEmpty();
// jmp-frame hit by step into, do another 't' and abort sequence.
if (!hasFile && i == 0 && sourceStepInto && frames.at(i).function.contains(QLatin1String("ILT+"))) {
showMessage(QString::fromAscii("Step into: Call instruction hit, performing additional step..."), LogMisc);
return ParseStackStepInto;
}
if (count && current == -1) // No usable frame, use assembly.
current = 0;
// Set
stackHandler()->setFrames(frames);
activateFrame(current);
if (hasFile) {
frames[i].file = QDir::cleanPath(normalizeFileName(frames.at(i).file));
if (current == -1 && frames[i].usable)
current = i;
}
}
if (count && current == -1) // No usable frame, use assembly.
current = 0;
// Set
stackHandler()->setFrames(frames);
activateFrame(current);
return 0;
}
void CdbEngine::handleStackTrace(const CdbExtensionCommandPtr &command)
{
if (command->success) {
GdbMi data;
data.fromString(command->reply);
parseStackTrace(data, false);
postCommandSequence(command->commandSequence);
} else {
showMessage(command->errorMessage, LogError);
......
......@@ -53,6 +53,7 @@ struct CdbBuiltinCommand;
struct CdbExtensionCommand;
struct CdbOptions;
class ByteArrayInputStream;
class GdbMi;
class CdbEngine : public Debugger::DebuggerEngine
{
......@@ -164,9 +165,14 @@ private:
SpecialStopSynchronizeBreakpoints,
SpecialStopGetWidgetAt
};
enum ParseStackResultFlags // Flags returned by parseStackTrace
{
ParseStackStepInto = 1 // Need to execute a step, hit on a call frame in "Step into"
};
bool startConsole(const DebuggerStartParameters &sp, QString *errorMessage);
unsigned examineStopReason(const QByteArray &messageIn, QString *message,
unsigned examineStopReason(const GdbMi &stopReason, QString *message,
QString *exceptionBoxMessage);
bool commandsPending() const;
void handleExtensionMessage(char t, int token, const QByteArray &what, const QByteArray &message);
......@@ -205,6 +211,8 @@ private:
void updateLocals();
int elapsedLogTime() const;
void addLocalsOptions(ByteArrayInputStream &s) const;
unsigned parseStackTrace(const GdbMi &data, bool sourceStepInto);
void parseThreads(const GdbMi &, int forceCurrentThreadId = -1);
const QByteArray m_creatorExtPrefix;
const QByteArray m_tokenPrefix;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment