Commit 11c6ca71 authored by Friedemann Kleint's avatar Friedemann Kleint
Browse files

Debugger[New CDB]: Add support for "Select Widget to Watch".

in stopped state. Add helper for executing calls
to ExtensionContext including recording of output in
OutputCallback. Extend symbol resolution to return addresses
as well since QApplication::widgetAt() is ambiguous and needs
to be called by address. Add 'widgetat' extension command
to return the widget.
parent a471f542
......@@ -48,7 +48,7 @@ const char *ExtensionContext::stopReasonKeyC = "reason";
ExtensionContext::ExtensionContext() :
m_hookedClient(0),
m_oldEventCallback(0), m_oldOutputCallback(0),
m_creatorEventCallback(0), m_creatorOutputCallback(0)
m_creatorEventCallback(0), m_creatorOutputCallback(0), m_stateNotification(true)
{
}
......@@ -81,6 +81,20 @@ void ExtensionContext::hookCallbacks(CIDebugClient *client)
}
}
void ExtensionContext::startRecordingOutput()
{
if (m_creatorOutputCallback) {
m_creatorOutputCallback->startRecording();
} else {
report('X', 0, 0, "Error", "ExtensionContext::startRecordingOutput() called with no output hooked.\n");
}
}
std::wstring ExtensionContext::stopRecordingOutput()
{
return m_creatorOutputCallback ? m_creatorOutputCallback->stopRecording() : std::wstring();
}
void ExtensionContext::setStopReason(const StopReasonMap &r, const std::string &reason)
{
m_stopReason = r;
......@@ -163,37 +177,41 @@ static inline ExtensionContext::StopReasonMap
void ExtensionContext::notifyIdle()
{
discardSymbolGroup();
const StopReasonMap stopReasons = completeStopReasons(m_stopReason, executionStatus());
m_stopReason.clear();
// Format
std::ostringstream str;
formatGdbmiHash(str, stopReasons);
reportLong('E', 0, "session_idle", str.str());
if (m_stateNotification) {
const StopReasonMap stopReasons = completeStopReasons(m_stopReason, executionStatus());
// Format
std::ostringstream str;
formatGdbmiHash(str, stopReasons);
reportLong('E', 0, "session_idle", str.str());
}
m_stopReason.clear();
}
void ExtensionContext::notifyState(ULONG Notify)
{
const ULONG ex = executionStatus();
switch (Notify) {
case DEBUG_NOTIFY_SESSION_ACTIVE:
report('E', 0, 0, "session_active", "%u", ex);
break;
case DEBUG_NOTIFY_SESSION_ACCESSIBLE: // Meaning, commands accepted
report('E', 0, 0, "session_accessible", "%u", ex);
break;
case DEBUG_NOTIFY_SESSION_INACCESSIBLE:
report('E', 0, 0, "session_inaccessible", "%u", ex);
break;
case DEBUG_NOTIFY_SESSION_INACTIVE:
report('E', 0, 0, "session_inactive", "%u", ex);
if (m_stateNotification) {
switch (Notify) {
case DEBUG_NOTIFY_SESSION_ACTIVE:
report('E', 0, 0, "session_active", "%u", ex);
break;
case DEBUG_NOTIFY_SESSION_ACCESSIBLE: // Meaning, commands accepted
report('E', 0, 0, "session_accessible", "%u", ex);
break;
case DEBUG_NOTIFY_SESSION_INACCESSIBLE:
report('E', 0, 0, "session_inaccessible", "%u", ex);
break;
case DEBUG_NOTIFY_SESSION_INACTIVE:
report('E', 0, 0, "session_inactive", "%u", ex);
break;
}
}
if (Notify == DEBUG_NOTIFY_SESSION_INACTIVE) {
discardSymbolGroup();
discardWatchesSymbolGroup();
// We lost the debuggee, at this point restore output.
if (ex & DEBUG_STATUS_NO_DEBUGGEE)
unhookCallbacks();
break;
}
}
......@@ -280,6 +298,48 @@ bool ExtensionContext::reportLong(char code, int token, const char *serviceName,
return true;
}
bool ExtensionContext::call(const std::string &functionCall,
std::wstring *output,
std::string *errorMessage)
{
if (!m_creatorOutputCallback) {
*errorMessage = "Attempt to issue a call with no output hooked.";
return false;
}
// Set up arguments
const std::string call = ".call " + functionCall;
HRESULT hr = m_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, call.c_str(), DEBUG_EXECUTE_ECHO);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("Execute", hr);
return 0;
}
// Execute in current thread. TODO: This must not crash, else we are in an inconsistent state
// (need to call 'gh', etc.)
hr = m_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, "~. g", DEBUG_EXECUTE_ECHO);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("Execute", hr);
return 0;
}
// Wait until finished
startRecordingOutput();
m_stateNotification = false;
m_control->WaitForEvent(0, INFINITE);
*output = stopRecordingOutput();
m_stateNotification = true;
// Crude attempt at recovering from a crash: Issue 'gN' (go with exception not handled).
const bool crashed = output->find(L"This exception may be expected and handled.") != std::string::npos;
if (crashed) {
m_stopReason.clear();
m_stateNotification = false;
hr = m_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, "~. gN", DEBUG_EXECUTE_ECHO);
m_control->WaitForEvent(0, INFINITE);
m_stateNotification = true;
*errorMessage = "A crash occurred while calling: " + functionCall;
return false;
}
return true;
}
// Exported C-functions
extern "C" {
......
......@@ -42,6 +42,7 @@
class LocalsSymbolGroup;
class WatchesSymbolGroup;
class OutputCallback;
// Global singleton with context.
// Caches a symbolgroup per frame and thread as long as the session is accessible.
......@@ -97,6 +98,11 @@ public:
// Set a stop reason to be reported with the next idle notification (exception).
void setStopReason(const StopReasonMap &, const std::string &reason = std::string());
void startRecordingOutput();
std::wstring stopRecordingOutput();
// Execute a function call and record the output.
bool call(const std::string &functionCall, std::wstring *output, std::string *errorMessage);
private:
bool isInitialized() const;
void discardSymbolGroup();
......@@ -109,9 +115,10 @@ private:
IDebugEventCallbacks *m_oldEventCallback;
IDebugOutputCallbacksWide *m_oldOutputCallback;
IDebugEventCallbacks *m_creatorEventCallback;
IDebugOutputCallbacksWide *m_creatorOutputCallback;
OutputCallback *m_creatorOutputCallback;
StopReasonMap m_stopReason;
bool m_stateNotification;
};
// Context for extension commands to be instantiated on stack in a command handler.
......
......@@ -35,6 +35,8 @@
#include "stringutils.h"
#include "iinterfacepointer.h"
#include "base64.h"
#include "symbolgroupvalue.h"
#include "extensioncontext.h"
#include <vector>
......@@ -620,3 +622,51 @@ std::string gdbmiStack(CIDebugControl *debugControl,
str << ']';
return str.str();
}
// Find the widget of the application by calling QApplication::widgetAt().
// Return "Qualified_ClassName:Address"
static inline std::string msgWidgetParseError(std::wstring wo)
{
replace(wo, L'\n', L';');
return "Output parse error :" + wStringToString(wo);
}
std::string widgetAt(const SymbolGroupValueContext &ctx, int x, int y, std::string *errorMessage)
{
typedef SymbolGroupValue::SymbolList SymbolList;
// First, resolve symbol since there are ambiguities. Take the first one which is the
// overload for (int,int) and call by address instead off name to overcome that.
const std::string func = QtInfo::get(ctx).prependQtGuiModule("QApplication::widgetAt");
const SymbolList symbols = SymbolGroupValue::resolveSymbol(func.c_str(), ctx, errorMessage);
if (symbols.empty())
return std::string(); // Not a gui application, likely
std::ostringstream callStr;
callStr << std::showbase << std::hex << symbols.front().second
<< std::noshowbase << std::dec << '(' << x << ',' << y << ')';
std::wstring wOutput;
if (!ExtensionContext::instance().call(callStr.str(), &wOutput, errorMessage))
return std::string();
// Returns: ".call returns\nclass QWidget * 0x00000000`022bf100\nbla...".
// Chop lines in front and after 'class ...' and convert first line.
const std::wstring::size_type classPos = wOutput.find(L"class ");
if (classPos == std::wstring::npos) {
*errorMessage = msgWidgetParseError(wOutput);
return std::string();
}
wOutput.erase(0, classPos + 6);
const std::wstring::size_type nlPos = wOutput.find(L'\n');
if (nlPos != std::wstring::npos)
wOutput.erase(nlPos, wOutput.size() - nlPos);
const std::string::size_type addressPos = wOutput.find(L" * 0x");
if (addressPos == std::string::npos) {
*errorMessage = msgWidgetParseError(wOutput);
return std::string();
}
// "QWidget * 0x00000000`022bf100" -> "QWidget:0x00000000022bf100"
wOutput.replace(addressPos, 3, L":");
const std::string::size_type sepPos = wOutput.find(L'`');
if (sepPos != std::string::npos)
wOutput.erase(sepPos, 1);
return wStringToString(wOutput);
}
......@@ -37,6 +37,8 @@
#include "common.h"
#include <vector>
struct SymbolGroupValueContext;
/* Various helpers to the extension commands to retrieve debuggee information
* in suitable formats for the debugger engine. */
......@@ -159,4 +161,9 @@ std::string gdbmiStack(CIDebugControl *debugControl, CIDebugSymbols *debugSymbol
unsigned maxFrames, bool humanReadable,
std::string *errorMessage);
// Find the widget of the application at (x,y) by calling QApplication::widgetAt().
// Return a string of "Qualified_ClassName:Address"
std::string widgetAt(const SymbolGroupValueContext &ctx,
int x, int y, std::string *errorMessage);
#endif // THREADLIST_H
......@@ -38,7 +38,8 @@
#include <cstring>
OutputCallback::OutputCallback(IDebugOutputCallbacksWide *wrapped) : m_wrapped(wrapped)
OutputCallback::OutputCallback(IDebugOutputCallbacksWide *wrapped) :
m_wrapped(wrapped), m_recording(false)
{
}
......@@ -85,6 +86,9 @@ STDMETHODIMP OutputCallback::Output(
IN PCWSTR text
)
{
if (m_recording)
m_recorded.append(text);
// Do not unconditionally output ourselves here, as this causes an endless
// recursion. Suppress prompts (note that sequences of prompts may mess parsing up)
if (!m_wrapped || mask == DEBUG_OUTPUT_PROMPT)
......@@ -100,3 +104,17 @@ STDMETHODIMP OutputCallback::Output(
ExtensionContext::instance().reportLong('E', 0, "debuggee_output", str.str().c_str());
return S_OK;
}
void OutputCallback::startRecording()
{
m_recorded.clear();
m_recording = true;
}
std::wstring OutputCallback::stopRecording()
{
const std::wstring rc = m_recorded;
m_recorded.clear();
m_recording = false;
return rc;
}
......@@ -63,8 +63,13 @@ public:
IN PCWSTR text
);
void startRecording();
std::wstring stopRecording();
private:
IDebugOutputCallbacksWide *m_wrapped;
bool m_recording;
std::wstring m_recorded;
};
#endif // DEBUGEVENTOUTPUT_H
......@@ -20,4 +20,5 @@ shutdownex
test
stack
addwatch
widgetat
KnownStructOutput
......@@ -100,6 +100,7 @@ enum Command {
CmdStack,
CmdShutdownex,
CmdAddWatch,
CmdWidgetAt,
CmdTest
};
......@@ -155,6 +156,7 @@ static const CommandDescription commandDescriptions[] = {
{"stack","Prints stack in GDBMI format.","[-t token] [max-frames]"},
{"shutdownex","Unhooks output callbacks.\nNeeds to be called explicitly only in case of remote debugging.",""},
{"addwatch","Add watch expression","<iname> <expression>"},
{"widgetat","Return address of widget at position","<x> <y>"},
{"test","Testing command","-T type | -w watch-expression"}
};
......@@ -943,6 +945,38 @@ extern "C" HRESULT CALLBACK shutdownex(CIDebugClient *, PCSTR)
return S_OK;
}
extern "C" HRESULT CALLBACK widgetat(CIDebugClient *client, PCSTR argsIn)
{
ExtensionCommandContext exc(client);
int token = 0;
std::string widgetAddress;
std::string errorMessage;
do {
int x = -1;
int y = -1;
const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
if (tokens.size() != 2) {
errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]);
break;
}
if (!integerFromString(tokens.front(), &x) || !integerFromString(tokens.at(1), &y)) {
errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]);
break;
}
widgetAddress = widgetAt(SymbolGroupValueContext(exc.dataSpaces(), exc.symbols()),
x, y, &errorMessage);
} while (false);
if (widgetAddress.empty()) {
ExtensionContext::instance().report('N', token, 0, "widgetat", errorMessage.c_str());
} else {
ExtensionContext::instance().reportLong('R', token, "widgetat", widgetAddress);
}
return S_OK;
}
extern "C" HRESULT CALLBACK test(CIDebugClient *client, PCSTR argsIn)
{
enum Mode { Invalid, TestType, TestFixWatchExpression };
......
......@@ -701,7 +701,6 @@ static bool parseWatchExpression(const std::string &expression,
for ( ; pos < size ; pos++) {
const char c = expression.at(pos);
const WatchExpressionParseState nextState = nextWatchExpressionParseState(state, c, &templateLevel);
DebugPrint() << c << ' ' << pos << ' ' << state << ' ' << nextState << ' ' << templateLevel;
if (nextState == WEPS_Error)
return false;
if (nextState != state && state == WEPS_WithinType)
......@@ -767,16 +766,19 @@ bool WatchesSymbolGroup::addWatch(CIDebugSymbols *s, std::string iname, const st
return true;
}
// Compile map of current state root-iname->root-expression
// Compile map of current state root-iname->root-expression (top-level)
WatchesSymbolGroup::InameExpressionMap
WatchesSymbolGroup::currentInameExpressionMap() const
{
// Skip additional, expanded nodes
InameExpressionMap rc;
if (unsigned size = unsigned(root()->children().size()))
if (unsigned size = unsigned(root()->children().size())) {
for (unsigned i = 0; i < size; i++) {
const AbstractSymbolGroupNode *n = root()->childAt(i);
rc.insert(InameExpressionMap::value_type(n->iName(), n->name()));
if (n->testFlags(SymbolGroupNode::WatchNode))
rc.insert(InameExpressionMap::value_type(n->iName(), n->name()));
}
}
return rc;
}
......
......@@ -398,7 +398,7 @@ static inline std::string resolveQtSymbol(const char *symbolC,
std::string defaultPattern = defaultModuleNameC;
defaultPattern.push_back('!');
defaultPattern += symbolC;
const StringList defaultMatches = SymbolGroupValue::resolveSymbol(defaultPattern.c_str(), ctx);
const StringList defaultMatches = SymbolGroupValue::resolveSymbolName(defaultPattern.c_str(), ctx);
const SubStringPredicate modulePattern(modulePatternC);
const StringListConstIt defaultIt = std::find_if(defaultMatches.begin(), defaultMatches.end(), modulePattern);
if (defaultIt != defaultMatches.end())
......@@ -406,7 +406,7 @@ static inline std::string resolveQtSymbol(const char *symbolC,
// Fail, now try a search with '*qstrdup' in all modules. This might return several matches
// like 'QtCored4!qstrdup', 'QGuid4!qstrdup'
const std::string wildCardPattern = std::string(1, '*') + symbolC;
const StringList allMatches = SymbolGroupValue::resolveSymbol(wildCardPattern.c_str(), ctx);
const StringList allMatches = SymbolGroupValue::resolveSymbolName(wildCardPattern.c_str(), ctx);
const StringListConstIt allIt = std::find_if(allMatches.begin(), allMatches.end(), modulePattern);
return allIt != allMatches.end() ? *allIt : std::string();
}
......@@ -491,12 +491,29 @@ std::ostream &operator<<(std::ostream &os, const QtInfo &i)
}
std::list<std::string>
SymbolGroupValue::resolveSymbolName(const char *pattern,
const SymbolGroupValueContext &c,
std::string *errorMessage /* = 0 */)
{
// Extract the names
const SymbolList symbols = resolveSymbol(pattern, c, errorMessage);
std::list<std::string> rc;
if (!symbols.empty()) {
const SymbolList::const_iterator cend = symbols.end();
for (SymbolList::const_iterator it = symbols.begin(); it != cend; ++it)
rc.push_back(it->first);
}
return rc;
}
SymbolGroupValue::SymbolList
SymbolGroupValue::resolveSymbol(const char *pattern,
const SymbolGroupValueContext &c,
std::string *errorMessage /* = 0 */)
{
enum { bufSize = 2048 };
std::list<std::string> rc;
std::list<Symbol> rc;
if (errorMessage)
errorMessage->clear();
// Is it an incomplete symbol?
......@@ -518,12 +535,13 @@ std::list<std::string>
return rc;
}
char buf[bufSize];
ULONG64 offset;
while (true) {
hr = c.symbols->GetNextSymbolMatch(handle, buf, bufSize - 1, 0, 0);
hr = c.symbols->GetNextSymbolMatch(handle, buf, bufSize - 1, 0, &offset);
if (hr == E_NOINTERFACE)
break;
if (hr == S_OK)
rc.push_back(std::string(buf));
rc.push_back(Symbol(std::string(buf), offset));
}
c.symbols->EndSymbolMatch(handle);
return rc;
......
......@@ -70,6 +70,9 @@ class SymbolGroupValue
explicit SymbolGroupValue(const std::string &parentError);
public:
typedef std::pair<std::string, ULONG64> Symbol;
typedef std::list<Symbol> SymbolList;
explicit SymbolGroupValue(SymbolGroupNode *node, const SymbolGroupValueContext &c);
SymbolGroupValue();
......@@ -129,9 +132,13 @@ public:
const SymbolGroupValueContext &ctx,
const std::string &currentModule = std::string());
static std::list<std::string> resolveSymbol(const char *pattern,
const SymbolGroupValueContext &c,
std::string *errorMessage = 0);
static std::list<std::string> resolveSymbolName(const char *pattern,
const SymbolGroupValueContext &c,
std::string *errorMessage = 0);
static SymbolList resolveSymbol(const char *pattern,
const SymbolGroupValueContext &c,
std::string *errorMessage = 0);
static unsigned pointerSize();
static unsigned intSize();
......
......@@ -353,7 +353,9 @@ CdbEngine::CdbEngine(const DebuggerStartParameters &sp,
m_hasDebuggee(false),
m_elapsedLogTime(0),
m_sourceStepInto(false),
m_wX86BreakpointCount(0)
m_wX86BreakpointCount(0),
m_watchPointX(0),
m_watchPointY(0)
{
Utils::SavedAction *assemblerAction = theAssemblerAction();
m_operateByInstructionPending = assemblerAction->isChecked();
......@@ -1443,6 +1445,12 @@ unsigned CdbEngine::examineStopReason(const QByteArray &messageIn,
*message = tr("Malformed stop response received.");
return StopReportParseError|StopNotifyStop;
}
// Additional stop messages occurring for debuggee function calls (widgetAt, etc). Just log.
if (state() == InferiorStopOk) {
*message = QString::fromLatin1("Ignored stop notification from function call (%1).").
arg(QString::fromAscii(reason));
return StopReportLog;
}
const int threadId = stopReason.findChild("threadId").data().toInt();
if (reason == "breakpoint") {
const int number = stopReason.findChild("breakpointId").data().toInt();
......@@ -1511,6 +1519,9 @@ void CdbEngine::handleSessionIdle(const QByteArray &messageBA)
attemptBreakpointSynchronization();
doContinueInferior();
return;
case SpecialStopGetWidgetAt:
postWidgetAtCommand();
return;
case NoSpecialStop:
break;
}
......@@ -2112,5 +2123,67 @@ void CdbEngine::postCommandSequence(unsigned mask)
}
}
void CdbEngine::handleWidgetAt(const CdbExtensionCommandPtr &reply)
{
bool success = false;
QString message;
do {
if (!reply->success) {
message = QString::fromAscii(reply->errorMessage);
break;
}
// Should be "namespace::QWidget:0x555"
QString watchExp = QString::fromAscii(reply->reply);
const int sepPos = watchExp.lastIndexOf(QLatin1Char(':'));
if (sepPos == -1) {
message = QString::fromAscii("Invalid output: %1").arg(watchExp);
break;
}
// 0x000 -> nothing found
if (!watchExp.mid(sepPos + 1).toULongLong(0, 0)) {
message = QString::fromAscii("No widget could be found at %1, %2.").arg(m_watchPointX).arg(m_watchPointY);
break;
}
// Turn into watch expression: "*(namespace::QWidget*)0x555"
watchExp.replace(sepPos, 1, QLatin1String("*)"));
watchExp.insert(0, QLatin1String("*("));
watchHandler()->watchExpression(watchExp);
success = true;
} while (false);
if (!success)
showMessage(message, LogWarning);
m_watchPointX = m_watchPointY = 0;
}
void CdbEngine::watchPoint(const QPoint &p)
{
m_watchPointX = p.x();
m_watchPointY = p.y();
switch (state()) {
case InferiorStopOk:
postWidgetAtCommand();
break;
case InferiorRunOk:
// "Select Widget to Watch" from a running application is currently not
// supported. It could be implemented via SpecialStopGetWidgetAt-mode,
// but requires some work as not to confuse the engine by state-change notifications
// emitted by the debuggee function call.
showMessage(tr("\"Select Widget to Watch\": Please stop the application first."), LogWarning);
break;
default:
showMessage(tr("\"Select Widget to Watch\": Not supported in state '%1'.").
arg(QString::fromAscii(stateName(state()))), LogWarning);
break;
}
}
void CdbEngine::postWidgetAtCommand()
{
QByteArray arguments = QByteArray::number(m_watchPointX);
arguments.append(' ');
arguments.append(QByteArray::number(m_watchPointY));
postExtensionCommand("widgetat", arguments, 0, &CdbEngine::handleWidgetAt, 0);
}
} // namespace Internal
} // namespace Debugger
......@@ -89,6 +89,7 @@ public:
virtual void updateWatchData(const WatchData &data,
const WatchUpdateFlags & flags = WatchUpdateFlags());
virtual unsigned debuggerCapabilities() const;
virtual void watchPoint(const<