Debugger: Add extension library to be loaded into CDB.exe

This commit is contained in:
Friedemann Kleint
2010-11-18 13:55:52 +01:00
parent 8477c7bc4f
commit 56d3664934
24 changed files with 4026 additions and 0 deletions

View File

@@ -14,3 +14,5 @@ SUBDIRS = \
qmleditorwidgets \ qmleditorwidgets \
symbianutils \ symbianutils \
3rdparty 3rdparty
win32:SUBDIRS += qtcreatorcdbext

View File

@@ -0,0 +1,80 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "base64.h"
#include <ostream>
static void base64EncodeTriple(std::ostream &str, const unsigned char triple[3], size_t length = 3)
{
static const char base64Encoding[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
// Convert 3 bytes of triplet into 4 bytes, write out 64 bit-wise using a character mapping,
// see description of base64.
unsigned tripleValue = triple[0] * 256;
tripleValue += triple[1];
tripleValue *= 256;
tripleValue += triple[2];
char result[4]= {0, 0, 0, 0};
for (int i = 3; i >= 0 ; i--) {
result[i] = base64Encoding[tripleValue % 64];
tripleValue /= 64;
}
// Write out quad and pad if it is a padding triple.
const size_t writeLength = length + 1;
size_t i = 0;
for ( ; i < writeLength; i++)
str << result[i];
for ( ; i < 4; i++)
str << '=';
}
void base64Encode(std::ostream &str, const unsigned char *source, size_t sourcelen)
{
if (!sourcelen) {
str << "====";
return;
}
/* Encode triples */
const unsigned char *sourceEnd = source + sourcelen;
for (const unsigned char *triple = source; triple < sourceEnd; ) {
const unsigned char *nextTriple = triple + 3;
if (nextTriple <= sourceEnd) { // Encode full triple, including very last one
base64EncodeTriple(str, triple);
triple = nextTriple;
} else { // Past end and some padding required.
unsigned char paddingTriple[3] = {0, 0, 0};
std::copy(triple, sourceEnd, paddingTriple);
base64EncodeTriple(str, paddingTriple, sourceEnd - triple);
break;
}
}
}

View File

@@ -0,0 +1,38 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef BASE64_H
#define BASE64_H
#include <iosfwd>
// Base 64 encoding helper
void base64Encode(std::ostream &str, const unsigned char *source, size_t sourcelen);
#endif // BASE64_H

View File

@@ -0,0 +1,13 @@
# Detect presence of "Debugging Tools For Windows"
# in case MS VS compilers are used.
CDB_PATH=""
win32 {
contains(QMAKE_CXX, cl) {
CDB_PATH="$$(CDB_PATH)"
isEmpty(CDB_PATH):CDB_PATH="$$(ProgramFiles)/Debugging Tools For Windows/sdk"
!exists($$CDB_PATH):CDB_PATH="$$(ProgramFiles)/Debugging Tools For Windows (x86)/sdk"
!exists($$CDB_PATH):CDB_PATH="$$(ProgramFiles)/Debugging Tools For Windows (x64)/sdk"
!exists($$CDB_PATH):CDB_PATH="$$(ProgramFiles)/Debugging Tools For Windows 64-bit/sdk"
}
}

View File

@@ -0,0 +1,120 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "common.h"
#include "iinterfacepointer.h"
#include <sstream>
std::string winErrorMessage(unsigned long error)
{
char *lpMsgBuf;
const int len = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, 0, (LPSTR)&lpMsgBuf, 0, NULL);
if (len) {
const std::string rc(lpMsgBuf, len);
LocalFree(lpMsgBuf);
return rc;
}
std::ostringstream str;
str << "Unknown error " << error;
return str.str();
}
std::string winErrorMessage()
{
return winErrorMessage(GetLastError());
}
std::string msgDebugEngineComResult(HRESULT hr)
{
switch (hr) {
case S_OK:
return std::string("S_OK");
case S_FALSE:
return std::string("S_FALSE");
case E_FAIL:
break;
case E_INVALIDARG:
return std::string("E_INVALIDARG");
case E_NOINTERFACE:
return std::string("E_NOINTERFACE");
case E_OUTOFMEMORY:
return std::string("E_OUTOFMEMORY");
case E_UNEXPECTED:
return std::string("E_UNEXPECTED");
case E_NOTIMPL:
return std::string("E_NOTIMPL");
}
if (hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED))
return std::string("ERROR_ACCESS_DENIED");;
if (hr == HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT))
return std::string("STATUS_CONTROL_C_EXIT");
return std::string("E_FAIL ") + winErrorMessage(HRESULT_CODE(hr));
}
std::string msgDebugEngineComFailed(const char *func, HRESULT hr)
{
std::string rc = func;
rc += " failed: ";
rc += msgDebugEngineComResult(hr);
return rc;
}
ULONG currentThreadId(IDebugSystemObjects *sysObjects)
{
ULONG id = 0;
if (sysObjects->GetCurrentThreadId(&id) == S_OK)
return id;
return 0;
}
ULONG currentThreadId(CIDebugClient *client)
{
IInterfacePointer<IDebugSystemObjects> sysObjects(client);
if (sysObjects)
return currentThreadId(sysObjects.data());
return 0;
}
ULONG currentProcessId(IDebugSystemObjects *sysObjects)
{
ULONG64 handle = 0;
if (sysObjects->GetCurrentProcessHandle(&handle) == S_OK)
return GetProcessId((HANDLE)handle);
return 0;
}
ULONG currentProcessId(CIDebugClient *client)
{
IInterfacePointer<IDebugSystemObjects> sysObjects(client);
if (sysObjects)
return currentProcessId(sysObjects.data());
return 0;
}

View File

@@ -0,0 +1,76 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef COMMON_H
#define COMMON_H
// Define KDEXT_64BIT to make all wdbgexts APIs recognize 64 bit addresses
// It is recommended for extensions to use 64 bit headers from wdbgexts so
// the extensions could support 64 bit targets.
#include <string>
#include <sstream>
#include <windows.h>
#define KDEXT_64BIT
#include <wdbgexts.h>
#include <dbgeng.h>
static const char creatorOutputPrefixC[] = "QtCreatorExt: ";
typedef IDebugControl CIDebugControl;
typedef IDebugSymbols3 CIDebugSymbols;
typedef IDebugSymbolGroup2 CIDebugSymbolGroup;
typedef IDebugClient5 CIDebugClient;
typedef IDebugSystemObjects CIDebugSystemObjects;
typedef IDebugDataSpaces4 CIDebugDataSpaces;
typedef IDebugAdvanced2 CIDebugAdvanced;
typedef IDebugRegisters2 CIDebugRegisters;
// Utility messages
std::string winErrorMessage(unsigned long error);
std::string winErrorMessage();
std::string msgDebugEngineComResult(HRESULT hr);
std::string msgDebugEngineComFailed(const char *func, HRESULT hr);
// Debug helper for anything streamable as in 'DebugPrint() << object'
// Derives from std::ostringstream and print out everything accumulated in destructor.
struct DebugPrint : public std::ostringstream {
DebugPrint() {}
~DebugPrint() {
dprintf("DEBUG: %s\n", str().c_str());
}
};
ULONG currentThreadId(IDebugSystemObjects *sysObjects);
ULONG currentThreadId(CIDebugClient *client);
ULONG currentProcessId(IDebugSystemObjects *sysObjects);
ULONG currentProcessId(CIDebugClient *client);
#endif // COMMON_H

View File

@@ -0,0 +1,302 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "eventcallback.h"
#include "extensioncontext.h"
#include "stringutils.h"
#include "gdbmihelpers.h"
static const char eventContextC[] = "event";
static const char moduleContextC[] = "module";
// Special exception codes (see dbgwinutils.cpp).
enum { winExceptionCppException = 0xe06d7363,
winExceptionStartupCompleteTrap = 0x406d1388,
winExceptionRpcServerUnavailable = 0x6ba,
winExceptionRpcServerInvalid = 0x6a6,
winExceptionDllNotFound = 0xc0000135,
winExceptionDllEntryPointNoFound = 0xc0000139,
winExceptionDllInitFailed = 0xc0000142,
winExceptionMissingSystemFile = 0xc0000143,
winExceptionAppInitFailed = 0xc0000143
};
EventCallback::EventCallback(IDebugEventCallbacks *wrapped) :
m_wrapped(wrapped)
{
}
EventCallback::~EventCallback()
{
}
STDMETHODIMP EventCallback::QueryInterface(
THIS_
IN REFIID InterfaceId,
OUT PVOID* Interface)
{
*Interface = NULL;
if (IsEqualIID(InterfaceId, __uuidof(IUnknown)) ||
IsEqualIID(InterfaceId, __uuidof(IDebugOutputCallbacks))) {
*Interface = (IDebugOutputCallbacks *)this;
AddRef();
return S_OK;
} else {
return E_NOINTERFACE;
}
}
STDMETHODIMP_(ULONG) EventCallback::AddRef(THIS)
{
// This class is designed to be static so
// there's no true refcount.
return 1;
}
STDMETHODIMP_(ULONG) EventCallback::Release(THIS)
{
// This class is designed to be static so
// there's no true refcount.
return 0;
}
STDMETHODIMP EventCallback::GetInterestMask(THIS_ __out PULONG mask)
{
if (m_wrapped)
m_wrapped->GetInterestMask(mask);
*mask |= DEBUG_EVENT_CREATE_PROCESS | DEBUG_EVENT_EXIT_PROCESS
| DEBUG_EVENT_BREAKPOINT
| DEBUG_EVENT_EXCEPTION | DEBUG_EVENT_LOAD_MODULE | DEBUG_EVENT_UNLOAD_MODULE;
return S_OK;
}
STDMETHODIMP EventCallback::Breakpoint(THIS_ __in PDEBUG_BREAKPOINT b)
{
// Breakpoint hit - Set the stop reason parameters on the extension context.
typedef ExtensionContext::StopReasonMap StopReasonMap;
typedef ExtensionContext::StopReasonMap::value_type StopReasonMapValue;
ULONG id = 0;
ULONG64 address = 0;
b->GetId(&id);
b->GetOffset(&address);
StopReasonMap stopReason;
stopReason.insert(StopReasonMapValue(std::string("breakpointId"), toString(id)));
if (address)
stopReason.insert(StopReasonMapValue(std::string("breakpointAddress"), toString(address)));
ExtensionContext::instance().setStopReason(stopReason, "breakpoint");
return m_wrapped ? m_wrapped->Breakpoint(b) : S_OK;
}
static inline ExtensionContext::StopReasonMap exceptionParameters(const EXCEPTION_RECORD64 &e,
unsigned firstChance)
{
typedef ExtensionContext::StopReasonMap StopReasonMap;
typedef ExtensionContext::StopReasonMap::value_type StopReasonMapValue;
// Fill exception record
StopReasonMap parameters;
parameters.insert(StopReasonMapValue(std::string("firstChance"), toString(firstChance)));
parameters.insert(StopReasonMapValue(std::string("exceptionAddress"),
toString(e.ExceptionAddress)));
parameters.insert(StopReasonMapValue(std::string("exceptionCode"),
toString(e.ExceptionCode)));
parameters.insert(StopReasonMapValue(std::string("exceptionFlags"),
toString(e.ExceptionFlags)));
// Hard code some parameters (used for access violations)
if (e.NumberParameters >= 1)
parameters.insert(StopReasonMapValue(std::string("exceptionInformation0"),
toString(e.ExceptionInformation[0])));
if (e.NumberParameters >= 2)
parameters.insert(StopReasonMapValue(std::string("exceptionInformation1"),
toString(e.ExceptionInformation[1])));
// Add top stack frame if possible
StackFrame frame;
std::string errorMessage;
// If it is a C++ exception, get frame #2 (first outside MSVC runtime)
const unsigned frameNumber = e.ExceptionCode == winExceptionCppException ? 2 : 0;
if (getFrame(frameNumber, &frame, &errorMessage)) {
if (!frame.fullPathName.empty()) {
parameters.insert(StopReasonMapValue(std::string("exceptionFile"),
wStringToString(frame.fullPathName)));
parameters.insert(StopReasonMapValue(std::string("exceptionLine"),
toString(frame.line)));
}
if (!frame.function.empty())
parameters.insert(StopReasonMapValue(std::string("exceptionFunction"),
wStringToString(frame.function)));
} // getCurrentFrame
return parameters;
}
STDMETHODIMP EventCallback::Exception(
THIS_
__in PEXCEPTION_RECORD64 Ex,
__in ULONG FirstChance
)
{
// Report the exception as GBMI and set potential stop reason
const ExtensionContext::StopReasonMap parameters =
exceptionParameters(*Ex, FirstChance);
std::ostringstream str;
formatGdbmiHash(str, parameters);
ExtensionContext::instance().setStopReason(parameters, "exception");
ExtensionContext::instance().report('E', 0, "exception", "%s", str.str().c_str());
return m_wrapped ? m_wrapped->Exception(Ex, FirstChance) : S_OK;
}
STDMETHODIMP EventCallback::CreateThread(
THIS_
__in ULONG64 Handle,
__in ULONG64 DataOffset,
__in ULONG64 StartOffset
)
{
return m_wrapped ? m_wrapped->CreateThread(Handle, DataOffset, StartOffset) : S_OK;
}
STDMETHODIMP EventCallback::ExitThread(
THIS_
__in ULONG ExitCode
)
{
return m_wrapped ? m_wrapped->ExitThread(ExitCode) : S_OK;
}
STDMETHODIMP EventCallback::CreateProcess(
THIS_
__in ULONG64 ImageFileHandle,
__in ULONG64 Handle,
__in ULONG64 BaseOffset,
__in ULONG ModuleSize,
__in_opt PCSTR ModuleName,
__in_opt PCSTR ImageName,
__in ULONG CheckSum,
__in ULONG TimeDateStamp,
__in ULONG64 InitialThreadHandle,
__in ULONG64 ThreadDataOffset,
__in ULONG64 StartOffset
)
{
return m_wrapped ? m_wrapped->CreateProcess(ImageFileHandle, Handle,
BaseOffset, ModuleSize, ModuleName,
ImageName, CheckSum, TimeDateStamp,
InitialThreadHandle, ThreadDataOffset,
StartOffset) : S_OK;
}
STDMETHODIMP EventCallback::ExitProcess(
THIS_
__in ULONG ExitCode
)
{
ExtensionContext::instance().report('E', 0, eventContextC, "Process exited (%lu)",
ExitCode);
dprintf("%s ExitProcess %u\n", creatorOutputPrefixC, ExitCode);
return m_wrapped ? m_wrapped->ExitProcess(ExitCode) : S_OK;
}
STDMETHODIMP EventCallback::LoadModule(
THIS_
__in ULONG64 ImageFileHandle,
__in ULONG64 BaseOffset,
__in ULONG ModuleSize,
__in_opt PCSTR ModuleName,
__in_opt PCSTR ImageName,
__in ULONG CheckSum,
__in ULONG TimeDateStamp
)
{
ExtensionContext::instance().report('E', 0, moduleContextC, "L:%s:%s:0x%llx:0x%llx\n",
ModuleName, ImageName, BaseOffset, ModuleSize);
return m_wrapped ? m_wrapped->LoadModule(ImageFileHandle, BaseOffset,
ModuleSize, ModuleName, ImageName,
CheckSum, TimeDateStamp) : S_OK;
}
STDMETHODIMP EventCallback::UnloadModule(
THIS_
__in_opt PCSTR ImageBaseName,
__in ULONG64 BaseOffset
)
{
ExtensionContext::instance().report('U', 0, moduleContextC, "U:%s\n",
ImageBaseName);
return m_wrapped ? m_wrapped->UnloadModule(ImageBaseName, BaseOffset) : S_OK;
}
STDMETHODIMP EventCallback::SystemError(
THIS_
__in ULONG Error,
__in ULONG Level
)
{
return m_wrapped ? m_wrapped->SystemError(Error, Level) : S_OK;
}
STDMETHODIMP EventCallback::SessionStatus(
THIS_
__in ULONG Status
)
{
return m_wrapped ? m_wrapped->SessionStatus(Status) : S_OK;
}
STDMETHODIMP EventCallback::ChangeDebuggeeState(
THIS_
__in ULONG Flags,
__in ULONG64 Argument
)
{
return m_wrapped ? m_wrapped->ChangeDebuggeeState(Flags, Argument) : S_OK;
}
STDMETHODIMP EventCallback::ChangeEngineState(
THIS_
__in ULONG Flags,
__in ULONG64 Argument
)
{
return m_wrapped ? m_wrapped->ChangeEngineState(Flags, Argument) : S_OK;
}
STDMETHODIMP EventCallback::ChangeSymbolState(
THIS_
__in ULONG Flags,
__in ULONG64 Argument
)
{
return m_wrapped ? m_wrapped->ChangeSymbolState(Flags, Argument) : S_OK;
}

View File

@@ -0,0 +1,157 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef DEBUGEVENTHANDLER_H
#define DEBUGEVENTHANDLER_H
#include "common.h"
#include "extensioncontext.h"
/* IDebugEventCallbacks event handler wrapping IDebugEventCallbacks to catch some output */
class EventCallback : public IDebugEventCallbacks
{
public:
explicit EventCallback(IDebugEventCallbacks *wrapped);
virtual ~EventCallback();
// IUnknown.
STDMETHOD(QueryInterface)(
THIS_
IN REFIID InterfaceId,
OUT PVOID* Interface
);
STDMETHOD_(ULONG, AddRef)(
THIS
);
STDMETHOD_(ULONG, Release)(
THIS
);
// IDebugEventCallbacks.
STDMETHOD(GetInterestMask)(
THIS_
__out PULONG mask
);
STDMETHOD(Breakpoint)(
THIS_
__in PDEBUG_BREAKPOINT Bp
);
STDMETHOD(Exception)(
THIS_
__in PEXCEPTION_RECORD64 Exception,
__in ULONG FirstChance
);
STDMETHOD(CreateThread)(
THIS_
__in ULONG64 Handle,
__in ULONG64 DataOffset,
__in ULONG64 StartOffset
);
STDMETHOD(ExitThread)(
THIS_
__in ULONG ExitCode
);
STDMETHOD(CreateProcess)(
THIS_
__in ULONG64 ImageFileHandle,
__in ULONG64 Handle,
__in ULONG64 BaseOffset,
__in ULONG ModuleSize,
__in_opt PCSTR ModuleName,
__in_opt PCSTR ImageName,
__in ULONG CheckSum,
__in ULONG TimeDateStamp,
__in ULONG64 InitialThreadHandle,
__in ULONG64 ThreadDataOffset,
__in ULONG64 StartOffset
);
STDMETHOD(ExitProcess)(
THIS_
__in ULONG ExitCode
);
// Call handleModuleLoad() when reimplementing this
STDMETHOD(LoadModule)(
THIS_
__in ULONG64 ImageFileHandle,
__in ULONG64 BaseOffset,
__in ULONG ModuleSize,
__in_opt PCSTR ModuleName,
__in_opt PCSTR ImageName,
__in ULONG CheckSum,
__in ULONG TimeDateStamp
);
// Call handleModuleUnload() when reimplementing this
STDMETHOD(UnloadModule)(
THIS_
__in_opt PCSTR ImageBaseName,
__in ULONG64 BaseOffset
);
STDMETHOD(SystemError)(
THIS_
__in ULONG Error,
__in ULONG Level
);
STDMETHOD(SessionStatus)(
THIS_
__in ULONG Status
);
STDMETHOD(ChangeDebuggeeState)(
THIS_
__in ULONG Flags,
__in ULONG64 Argument
);
STDMETHOD(ChangeEngineState)(
THIS_
__in ULONG Flags,
__in ULONG64 Argument
);
STDMETHOD(ChangeSymbolState)(
THIS_
__in ULONG Flags,
__in ULONG64 Argument
);
private:
IDebugEventCallbacks *m_wrapped;
};
#endif // DEBUGEVENTHANDLER_H

View File

@@ -0,0 +1,319 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "extensioncontext.h"
#include "symbolgroup.h"
#include "eventcallback.h"
#include "outputcallback.h"
#include "stringutils.h"
// wdbgexts.h declares 'extern WINDBG_EXTENSION_APIS ExtensionApis;'
// and it's inline functions rely on its existence.
WINDBG_EXTENSION_APIS ExtensionApis = {sizeof(WINDBG_EXTENSION_APIS), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const char *ExtensionContext::stopReasonKeyC = "reason";
ExtensionContext::ExtensionContext() :
m_hookedClient(0),
m_oldEventCallback(0), m_oldOutputCallback(0),
m_creatorEventCallback(0), m_creatorOutputCallback(0)
{
}
ExtensionContext::~ExtensionContext()
{
unhookCallbacks();
}
ExtensionContext &ExtensionContext::instance()
{
static ExtensionContext extContext;
return extContext;
}
// Redirect the event/output callbacks
void ExtensionContext::hookCallbacks(CIDebugClient *client)
{
if (!client || m_hookedClient || m_creatorEventCallback)
return;
// Store the hooked client. Any other client obtained
// is invalid for unhooking
m_hookedClient = client;
if (client->GetEventCallbacks(&m_oldEventCallback) == S_OK) {
m_creatorEventCallback = new EventCallback(m_oldEventCallback);
client->SetEventCallbacks(m_creatorEventCallback);
}
if (client->GetOutputCallbacksWide(&m_oldOutputCallback) == S_OK) {
m_creatorOutputCallback = new OutputCallback(m_oldOutputCallback);
client->SetOutputCallbacksWide(m_creatorOutputCallback);
}
}
void ExtensionContext::setStopReason(const StopReasonMap &r, const std::string &reason)
{
m_stopReason = r;
if (!reason.empty())
m_stopReason.insert(StopReasonMap::value_type(stopReasonKeyC, reason));
}
// Restore the callbacks.
void ExtensionContext::unhookCallbacks()
{
if (!m_hookedClient || (!m_creatorEventCallback && !m_creatorOutputCallback))
return;
if (m_creatorEventCallback) {
m_hookedClient->SetEventCallbacks(m_oldEventCallback);
delete m_creatorEventCallback;
m_creatorEventCallback = 0;
m_oldEventCallback = 0;
}
if (m_creatorOutputCallback) {
m_hookedClient->SetOutputCallbacksWide(m_oldOutputCallback);
delete m_creatorOutputCallback;
m_creatorOutputCallback = 0;
m_oldOutputCallback = 0;
}
m_hookedClient = 0;
}
HRESULT ExtensionContext::initialize(PULONG Version, PULONG Flags)
{
if (isInitialized())
return S_OK;
*Version = DEBUG_EXTENSION_VERSION(1, 0);
*Flags = 0;
IInterfacePointer<CIDebugClient> client;
if (!client.create())
return client.hr();
m_control.create(client.data());
if (!m_control)
return m_control.hr();
return m_control->GetWindbgExtensionApis64(&ExtensionApis);
}
bool ExtensionContext::isInitialized() const
{
return ExtensionApis.lpOutputRoutine != 0;
}
ULONG ExtensionContext::executionStatus() const
{
ULONG ex = 0;
return (m_control && SUCCEEDED(m_control->GetExecutionStatus(&ex))) ? ex : ULONG(0);
}
// Complete stop parameters with common parameters and report
static inline ExtensionContext::StopReasonMap
completeStopReasons(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)));
}
// Any reason?
const std::string reasonKey = std::string(ExtensionContext::stopReasonKeyC);
if (stopReasons.find(reasonKey) == stopReasons.end())
stopReasons.insert(StopReasonMapValue(reasonKey, "unknown"));
return stopReasons;
}
void ExtensionContext::notifyIdle()
{
discardSymbolGroup();
const StopReasonMap stopReasons = completeStopReasons(m_stopReason, executionStatus());
m_stopReason.clear();
// Format
std::ostringstream str;
formatGdbmiHash(str, stopReasons);
report('E', 0, "session_idle", "%s", str.str().c_str());
m_stopReason.clear();
}
void ExtensionContext::notifyState(ULONG Notify)
{
const ULONG ex = executionStatus();
switch (Notify) {
case DEBUG_NOTIFY_SESSION_ACTIVE:
report('E', 0, "session_active", "%u", ex);
break;
case DEBUG_NOTIFY_SESSION_ACCESSIBLE: // Meaning, commands accepted
report('E', 0, "session_accessible", "%u", ex);
break;
case DEBUG_NOTIFY_SESSION_INACCESSIBLE:
report('E', 0, "session_inaccessible", "%u", ex);
break;
case DEBUG_NOTIFY_SESSION_INACTIVE:
report('E', 0, "session_inactive", "%u", ex);
discardSymbolGroup();
// We lost the debuggee, at this point restore output.
if (ex & DEBUG_STATUS_NO_DEBUGGEE)
unhookCallbacks();
break;
}
}
SymbolGroup *ExtensionContext::symbolGroup(CIDebugSymbols *symbols, ULONG threadId, int frame, std::string *errorMessage)
{
if (m_symbolGroup.get() && m_symbolGroup->frame() == frame && m_symbolGroup->threadId() == threadId)
return m_symbolGroup.get();
SymbolGroup *newSg = SymbolGroup::create(m_control.data(), symbols, threadId, frame, errorMessage);
if (!newSg)
return 0;
m_symbolGroup.reset(newSg);
return newSg;
}
int ExtensionContext::symbolGroupFrame() const
{
if (m_symbolGroup.get())
return m_symbolGroup->frame();
return -1;
}
void ExtensionContext::discardSymbolGroup()
{
if (m_symbolGroup.get())
m_symbolGroup.reset();
}
bool ExtensionContext::report(char code, int token, const char *serviceName, PCSTR Format, ...)
{
if (!isInitialized())
return false;
// '<qtcreatorcdbext>|R|<token>|<serviceName>|<one-line-output>'.
m_control->Output(DEBUG_OUTPUT_NORMAL, "<qtcreatorcdbext>|%c|%d|%s|", code, token, serviceName);
va_list Args;
va_start(Args, Format);
m_control->OutputVaList(DEBUG_OUTPUT_NORMAL, Format, Args);
va_end(Args);
m_control->Output(DEBUG_OUTPUT_NORMAL, "\n");
return true;
}
// Exported C-functions
extern "C" {
HRESULT CALLBACK DebugExtensionInitialize(PULONG Version, PULONG Flags)
{
return ExtensionContext::instance().initialize(Version, Flags);
}
void CALLBACK DebugExtensionUninitialize(void)
{
}
void CALLBACK DebugExtensionNotify(ULONG Notify, ULONG64)
{
ExtensionContext::instance().notifyState(Notify);
}
} // extern "C"
// -------- ExtensionCommandContext
ExtensionCommandContext *ExtensionCommandContext::m_instance = 0;
ExtensionCommandContext::ExtensionCommandContext(CIDebugClient *client) : m_client(client)
{
ExtensionCommandContext::m_instance = this;
}
ExtensionCommandContext::~ExtensionCommandContext()
{
ExtensionCommandContext::m_instance = 0;
}
CIDebugControl *ExtensionCommandContext::control()
{
if (!m_control)
m_control.create(m_client);
return m_control.data();
}
ExtensionCommandContext *ExtensionCommandContext::instance()
{
return m_instance;
}
CIDebugSymbols *ExtensionCommandContext::symbols()
{
if (!m_symbols)
m_symbols.create(m_client);
return m_symbols.data();
}
CIDebugSystemObjects *ExtensionCommandContext::systemObjects()
{
if (!m_systemObjects)
m_systemObjects.create(m_client);
return m_systemObjects.data();
}
CIDebugAdvanced *ExtensionCommandContext::advanced()
{
if (!m_advanced)
m_advanced.create(m_client);
return m_advanced.data();
}
CIDebugRegisters *ExtensionCommandContext::registers()
{
if (!m_registers)
m_registers.create(m_client);
return m_registers.data();
}
CIDebugDataSpaces *ExtensionCommandContext::dataSpaces()
{
if (!m_dataSpaces)
m_dataSpaces.create(m_client);
return m_dataSpaces.data();
}
ULONG ExtensionCommandContext::threadId()
{
if (CIDebugSystemObjects *so = systemObjects()) {
ULONG threadId = 0;
if (SUCCEEDED(so->GetCurrentThreadId(&threadId)))
return threadId;
}
return 0;
}

View File

@@ -0,0 +1,134 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef EXTENSIONCONTEXT_H
#define EXTENSIONCONTEXT_H
#include "common.h"
#include "iinterfacepointer.h"
#include <memory>
#include <map>
class SymbolGroup;
// Global singleton with context.
// Caches a symbolgroup per frame and thread as long as the session is accessible.
class ExtensionContext {
ExtensionContext(const ExtensionContext&);
ExtensionContext& operator=(const ExtensionContext&);
ExtensionContext();
public:
// Key used to report stop reason in StopReasonMap
static const char *stopReasonKeyC;
// Map of parameters reported with the next stop as GDBMI
typedef std::map<std::string, std::string> StopReasonMap;
~ExtensionContext();
static ExtensionContext &instance();
// Call from DLL initialization.
HRESULT initialize(PULONG Version, PULONG Flags);
// Hook up our Event and Output callbacks that report exceptions and events.
// Call this from the first extension command that gets a client.
// Does not work when called from initialization.
void hookCallbacks(CIDebugClient *client);
// Report output in standardized format understood by Qt Creator.
// '<qtcreatorcdbext>|R|<token>|<serviceName>|<one-line-output>'.
// Char code is 'R' command reply, 'N' command fail, 'E' event notification
bool report(char code, int token, const char *serviceName, PCSTR Format, ...);
ULONG executionStatus() const;
// Call from notify handler, tell engine about state.
void notifyState(ULONG Notify);
// register as '.idle_cmd' to notify creator about stop
void notifyIdle();
// Return symbol group for frame (cache as long as frame does not change).
SymbolGroup *symbolGroup(CIDebugSymbols *symbols, ULONG threadId, int frame, std::string *errorMessage);
int symbolGroupFrame() const;
// Stop reason is reported with the next idle notification
void setStopReason(const StopReasonMap &, const std::string &reason = std::string());
private:
void unhookCallbacks();
bool isInitialized() const;
void discardSymbolGroup();
IInterfacePointer<CIDebugControl> m_control;
std::auto_ptr<SymbolGroup> m_symbolGroup;
CIDebugClient *m_hookedClient;
IDebugEventCallbacks *m_oldEventCallback;
IDebugOutputCallbacksWide *m_oldOutputCallback;
IDebugEventCallbacks *m_creatorEventCallback;
IDebugOutputCallbacksWide *m_creatorOutputCallback;
StopReasonMap m_stopReason;
};
// Context for extension commands to be instantiated on stack in a command handler.
class ExtensionCommandContext
{
ExtensionCommandContext(const ExtensionCommandContext&);
ExtensionCommandContext &operator=(const ExtensionCommandContext&);
public:
explicit ExtensionCommandContext(CIDebugClient *Client);
~ExtensionCommandContext();
// For accessing outside commands. Might return 0, based on the
// assumption that there is only once instance active.
static ExtensionCommandContext *instance();
CIDebugControl *control();
CIDebugSymbols *symbols();
CIDebugSystemObjects *systemObjects();
CIDebugAdvanced *advanced();
CIDebugRegisters *registers();
CIDebugDataSpaces *dataSpaces();
ULONG threadId();
private:
static ExtensionCommandContext *m_instance;
CIDebugClient *m_client;
IInterfacePointer<CIDebugControl> m_control;
IInterfacePointer<CIDebugSymbols> m_symbols;
IInterfacePointer<CIDebugAdvanced> m_advanced;
IInterfacePointer<CIDebugSystemObjects> m_systemObjects;
IInterfacePointer<CIDebugRegisters> m_registers;
IInterfacePointer<CIDebugDataSpaces> m_dataSpaces;
};
#endif // EXTENSIONCONTEXT_H

View File

@@ -0,0 +1,561 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "gdbmihelpers.h"
#include "stringutils.h"
#include "iinterfacepointer.h"
#include "base64.h"
#include <vector>
StackFrame::StackFrame(ULONG64 a) : address(a), line(0) {}
std::wstring StackFrame::fileName() const
{
std::wstring::size_type lastSlash = fullPathName.rfind(L'\\');
if (lastSlash == std::wstring::npos)
return fullPathName;
return fullPathName.substr(lastSlash + 1, fullPathName.size() - lastSlash - 1);
}
void StackFrame::formatGDBMI(std::ostream &str, unsigned level) const
{
str << "frame={level=\"" << level << "\",addr=\"0x"
<< std::hex << address << std::dec << '"';
if (!function.empty())
str << ",func=\"" << gdbmiWStringFormat(function) << '"';
if (!fullPathName.empty()) { // Creator/gdbmi expects 'clean paths'
std::wstring cleanPath = fullPathName;
replace(cleanPath, L'\\', L'/');
str << ",fullname=\"" << gdbmiWStringFormat(fullPathName)
<< "\",file=\"" << gdbmiWStringFormat(fileName()) << "\",line=\"" << line << '"';
}
str << '}';
}
Thread::Thread(ULONG i, ULONG sysId) : id(i), systemId(sysId) {}
void Thread::formatGDBMI(std::ostream &str) const
{
str << "{id=\"" << id << "\",target-id=\"" << systemId << "\",";
frame.formatGDBMI(str);
if (!name.empty())
str << ",name=\"" << gdbmiWStringFormat(name) << '"';
if (!state.empty())
str << ",state=\"" << state << '"';
str << '}';
}
static inline std::string msgGetThreadsFailed(const std::string &why)
{
return std::string("Unable to determine the thread information: ") + why;
}
static inline bool setCurrentThread(CIDebugSystemObjects *debugSystemObjects,
ULONG id, std::string *errorMessage)
{
const HRESULT hr = debugSystemObjects->SetCurrentThreadId(id);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("SetCurrentThreadId", hr);
return false;
}
return true;
}
// Fill in stack frame info
void getFrame(CIDebugSymbols *debugSymbols,
const DEBUG_STACK_FRAME &s, StackFrame *f)
{
WCHAR wBuf[MAX_PATH];
f->address = s.InstructionOffset;
HRESULT hr = debugSymbols->GetNameByOffsetWide(f->address, wBuf, MAX_PATH, 0, 0);
if (SUCCEEDED(hr)) {
f->function = wBuf;
} else {
f->function.clear();
}
ULONG64 ul64Displacement = 0;
hr = debugSymbols->GetLineByOffsetWide(f->address, &(f->line), wBuf, MAX_PATH, 0, &ul64Displacement);
if (SUCCEEDED(hr)) {
f->fullPathName = wBuf;
} else {
f->fullPathName.clear();
f->line = 0;
}
}
// Determine the frames of the threads.
// Note: Current thread is switched as a side effect!
static bool getThreadFrames(CIDebugSymbols *debugSymbols,
CIDebugSystemObjects *debugSystemObjects,
CIDebugControl *debugControl,
Threads *threads, std::string *errorMessage)
{
enum { MaxFrames = 1 };
DEBUG_STACK_FRAME frames[MaxFrames];
const Threads::iterator end = threads->end();
for (Threads::iterator it = threads->begin(); it != end; ++it) {
if (!setCurrentThread(debugSystemObjects, it->id, errorMessage))
return false;
ULONG frameCount;
const HRESULT hr = debugControl->GetStackTrace(0, 0, 0, frames, MaxFrames, &frameCount);
if (SUCCEEDED(hr))
getFrame(debugSymbols, frames[0], &(it->frame));
}
return true;
}
bool getFrame(unsigned n, StackFrame *f, std::string *errorMessage)
{
IInterfacePointer<CIDebugClient> client;
if (!client.create()) {
*errorMessage = "Cannot obtain client.";
return false;
}
IInterfacePointer<CIDebugSymbols> symbols(client.data());
IInterfacePointer<CIDebugControl> control(client.data());
if (!symbols || !control) {
*errorMessage = "Cannot obtain required objects.";
return false;
}
return getFrame(symbols.data(), control.data(), n, f, errorMessage);
}
bool getFrame(CIDebugSymbols *debugSymbols,
CIDebugControl *debugControl,
unsigned n, StackFrame *f, std::string *errorMessage)
{
DEBUG_STACK_FRAME *frames = new DEBUG_STACK_FRAME[n + 1];
ULONG frameCount;
const HRESULT hr = debugControl->GetStackTrace(0, 0, 0, frames, n + 1, &frameCount);
if (FAILED(hr)) {
delete [] frames;
*errorMessage = msgDebugEngineComFailed("GetStackTrace", hr);
return false;
}
getFrame(debugSymbols, frames[n], f);
delete [] frames;
return true;
}
bool threadList(CIDebugSystemObjects *debugSystemObjects,
CIDebugSymbols *debugSymbols,
CIDebugControl *debugControl,
CIDebugAdvanced *debugAdvanced,
Threads* threads, ULONG *currentThreadId,
std::string *errorMessage)
{
threads->clear();
ULONG threadCount;
*currentThreadId = 0;
// Get count
HRESULT hr= debugSystemObjects->GetNumberThreads(&threadCount);
if (FAILED(hr)) {
*errorMessage= msgGetThreadsFailed(msgDebugEngineComFailed("GetNumberThreads", hr));
return false;
}
// Get index of current
if (!threadCount)
return true;
hr = debugSystemObjects->GetCurrentThreadId(currentThreadId);
if (FAILED(hr)) {
*errorMessage= msgGetThreadsFailed(msgDebugEngineComFailed("GetCurrentThreadId", hr));
return false;
}
// Get Identifiers
threads->reserve(threadCount);
ULONG *ids = new ULONG[threadCount];
ULONG *systemIds = new ULONG[threadCount];
hr = debugSystemObjects->GetThreadIdsByIndex(0, threadCount, ids, systemIds);
if (FAILED(hr)) {
*errorMessage= msgGetThreadsFailed(msgDebugEngineComFailed("GetThreadIdsByIndex", hr));
return false;
}
// Create entries
static WCHAR name[256];
for (ULONG i= 0; i < threadCount ; i++) {
const ULONG id = ids[i];
Thread thread(id, systemIds[i]);
// Thread name
ULONG bytesReceived = 0;
hr = debugAdvanced->GetSystemObjectInformation(DEBUG_SYSOBJINFO_THREAD_NAME_WIDE,
0, id, name,
sizeof(name), &bytesReceived);
if (SUCCEEDED(hr) && bytesReceived)
thread.name = name;
threads->push_back(thread);
}
delete [] ids;
delete [] systemIds;
// Get frames and at all events,
// restore current thread after switching frames.
const bool framesOk = getThreadFrames(debugSymbols,
debugSystemObjects,
debugControl,
threads, errorMessage);
const bool restoreOk =setCurrentThread(debugSystemObjects, *currentThreadId, errorMessage);
return framesOk && restoreOk;
}
/* Format as: \code
52^done,threads=[{id="1",target-id="Thread 4740.0xb30",
frame={level="0",addr="0x00403487",func="foo",
args=[{name="this",value="0x27fee4"}],file="mainwindow.cpp",fullname="c:\\qt\\projects\\gitguim\\app/mainwindow.cpp",line="298"},state="stopped"}],
current-thread-id="1"
*/
std::string gdbmiThreadList(CIDebugSystemObjects *debugSystemObjects,
CIDebugSymbols *debugSymbols,
CIDebugControl *debugControl,
CIDebugAdvanced *debugAdvanced,
std::string *errorMessage)
{
Threads threads;
ULONG currentThreadId;
if (!threadList(debugSystemObjects, debugSymbols, debugControl,
debugAdvanced, &threads, &currentThreadId, errorMessage))
return std::string();
std::ostringstream str;
str << "{threads=[";
const Threads::const_iterator cend = threads.end();
for (Threads::const_iterator it = threads.begin(); it != cend; ++it)
it->formatGDBMI(str);
str << "],current-thread-id=\"" << currentThreadId << "\"}";
return str.str();
}
Module::Module() : deferred(false), base(0), size(0)
{
}
Modules getModules(CIDebugSymbols *syms, std::string *errorMessage)
{
enum { BufSize = 1024 };
char nameBuf[BufSize];
char fileBuf[BufSize];
ULONG Loaded;
ULONG Unloaded;
HRESULT hr = syms->GetNumberModules(&Loaded, &Unloaded);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetNumberModules", hr);
return Modules();
}
const ULONG count = Loaded + Unloaded;
Modules rc;
rc.reserve(count);
DEBUG_MODULE_PARAMETERS *parameters = new DEBUG_MODULE_PARAMETERS[count];
hr = syms->GetModuleParameters(count, NULL, 0, parameters);
if (FAILED(hr)) {
delete [] parameters;
*errorMessage = msgDebugEngineComFailed("GetModuleParameters", hr);
return Modules();
}
for (ULONG m = 0; m < count; m++) {
Module module;
module.base = parameters[m].Base;
module.size = parameters[m].Size;
module.deferred = parameters[m].Flags == DEBUG_SYMTYPE_DEFERRED;
hr = syms->GetModuleNames(m, 0, fileBuf, BufSize, NULL,
nameBuf, BufSize, NULL, NULL, NULL, NULL);
if (FAILED(hr))
break; // Fail silently should unloaded modules not work.
module.name = nameBuf;
module.image = fileBuf;
rc.push_back(module);
}
return rc;
}
std::string gdbmiModules(CIDebugSymbols *syms, bool humanReadable, std::string *errorMessage)
{
const Modules modules = getModules(syms, errorMessage);
if (modules.empty())
return std::string();
std::ostringstream str;
str << '[' << std::hex << std::showbase;
const Modules::size_type size = modules.size();
for (Modules::size_type m = 0; m < size; m++) {
const Module &module = modules.at(m);
if (m)
str << ',';
str << "{name=\"" << module.name
<< "\",image=\"" << gdbmiStringFormat(module.image)
<< "\",start=\"" << module.base << "\",end=\""
<< (module.base + module.size - 1) << '"';
if (module.deferred)
str << "{deferred=\"true\"";
str << '}';
if (humanReadable)
str << '\n';
}
str << ']';
return str.str();
}
// Description of a DEBUG_VALUE type field
const wchar_t *valueType(ULONG type)
{
switch (type) {
case DEBUG_VALUE_INT8:
return L"I8";
case DEBUG_VALUE_INT16:
return L"I16";
case DEBUG_VALUE_INT32:
return L"I32";
case DEBUG_VALUE_INT64:
return L"I64";
case DEBUG_VALUE_FLOAT32:
return L"F32";
case DEBUG_VALUE_FLOAT64:
return L"F64";
case DEBUG_VALUE_FLOAT80:
return L"F80";
case DEBUG_VALUE_FLOAT128:
return L"F128";
case DEBUG_VALUE_VECTOR64:
return L"V64";
case DEBUG_VALUE_VECTOR128:
return L"V128";
}
return L"";
}
// Format a 128bit vector register by adding digits in reverse order
void formatVectorRegister(std::ostream &str, const unsigned char *array, int size)
{
const char oldFill = str.fill('0');
str << "0x" << std::hex;
for (int i = size - 1; i >= 0; i--) {
str.width(2);
str << unsigned(array[i]);
}
str << std::dec;
str.fill(oldFill);
}
void formatDebugValue(std::ostream &str, const DEBUG_VALUE &dv, CIDebugControl *ctl)
{
const std::ios::fmtflags savedState = str.flags();
switch (dv.Type) {
// Do not use showbase to get the hex prefix as this omits it for '0'. Grmpf.
case DEBUG_VALUE_INT8:
str << std::hex << "0x" << unsigned(dv.I8);
break;
case DEBUG_VALUE_INT16:
str << std::hex << "0x" << dv.I16;
break;
case DEBUG_VALUE_INT32:
str << std::hex << "0x" << dv.I32;
break;
case DEBUG_VALUE_INT64:
str << std::hex << "0x" << dv.I64;
break;
case DEBUG_VALUE_FLOAT32:
str << dv.F32;
break;
case DEBUG_VALUE_FLOAT64:
str << dv.F64;
break;
case DEBUG_VALUE_FLOAT80:
case DEBUG_VALUE_FLOAT128: { // Convert to double
DEBUG_VALUE doubleValue;
if (ctl && SUCCEEDED(ctl->CoerceValue(const_cast<DEBUG_VALUE*>(&dv), DEBUG_VALUE_FLOAT64, &doubleValue)))
str << dv.F64;
}
break;
case DEBUG_VALUE_VECTOR64:
formatVectorRegister(str, dv.VI8, 8);
break;
case DEBUG_VALUE_VECTOR128:
formatVectorRegister(str, dv.VI8, 16);
break;
}
str.flags(savedState);
}
Register::Register() : subRegister(false), pseudoRegister(false)
{
value.Type = DEBUG_VALUE_INT32;
value.I32 = 0;
}
static inline std::wstring registerDescription(const DEBUG_REGISTER_DESCRIPTION &d)
{
std::wostringstream str;
str << valueType(d.Type);
if (d.Flags & DEBUG_REGISTER_SUB_REGISTER)
str << ", sub-register of " << d.SubregMaster;
return str.str();
}
Registers getRegisters(CIDebugRegisters *regs,
unsigned flags,
std::string *errorMessage)
{
enum { bufSize= 128 };
WCHAR buf[bufSize];
ULONG registerCount = 0;
HRESULT hr = regs->GetNumberRegisters(&registerCount);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetNumberRegisters", hr);
return Registers();
}
ULONG pseudoRegisterCount = 0;
if (flags & IncludePseudoRegisters) {
hr = regs->GetNumberPseudoRegisters(&pseudoRegisterCount);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetNumberPseudoRegisters", hr);
return Registers();
}
}
Registers rc;
rc.reserve(registerCount + pseudoRegisterCount);
// Standard registers
DEBUG_REGISTER_DESCRIPTION description;
DEBUG_VALUE value;
for (ULONG r = 0; r < registerCount; r++) {
hr = regs->GetDescriptionWide(r, buf, bufSize, NULL, &description);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetDescription", hr);
return Registers();
}
hr = regs->GetValue(r, &value);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetValue", hr);
return Registers();
}
const bool isSubRegister = (description.Flags & DEBUG_REGISTER_SUB_REGISTER);
if (!isSubRegister || (flags & IncludeSubRegisters)) {
Register reg;
reg.name = buf;
reg.description = registerDescription(description);
reg.subRegister = isSubRegister;
reg.value = value;
rc.push_back(reg);
}
}
// Pseudo
for (ULONG r = 0; r < pseudoRegisterCount; r++) {
ULONG type;
hr = regs->GetPseudoDescriptionWide(r, buf, bufSize, NULL, NULL, &type);
if (FAILED(hr))
continue; // Fails for some pseudo registers
hr = regs->GetValue(r, &value);
if (FAILED(hr))
continue; // Fails for some pseudo registers
Register reg;
reg.pseudoRegister = true;
reg.name = buf;
reg.description = valueType(type);
reg.value = value;
rc.push_back(reg);
}
return rc;
}
std::string gdbmiRegisters(CIDebugRegisters *regs,
CIDebugControl *control,
bool humanReadable,
unsigned flags,
std::string *errorMessage)
{
if (regs == 0 || control == 0) {
*errorMessage = "Required interfaces missing for registers dump.";
return std::string();
}
const Registers registers = getRegisters(regs, flags, errorMessage);
if (registers.empty())
return std::string();
std::ostringstream str;
str << '[';
if (humanReadable)
str << '\n';
const Registers::size_type size = registers.size();
for (Registers::size_type r = 0; r < size; r++) {
const Register &reg = registers.at(r);
if (r)
str << ',';
str << "{number=\"" << r << "\",name=\"" << gdbmiWStringFormat(reg.name) << '"';
if (!reg.description.empty())
str << ",description=\"" << gdbmiWStringFormat(reg.description) << '"';
if (reg.subRegister)
str << ",subregister=\"true\"";
if (reg.pseudoRegister)
str << ",pseudoregister=\"true\"";
str << ",value=\"";
formatDebugValue(str, reg.value, control);
str << "\"}";
if (humanReadable)
str << '\n';
}
str << ']';
if (humanReadable)
str << '\n';
return str.str();
}
std::string memoryToBase64(CIDebugDataSpaces *ds, ULONG64 address, ULONG length, std::string *errorMessage)
{
unsigned char *buffer = new unsigned char[length];
std::fill(buffer, buffer + length, 0);
ULONG received = 0;
const HRESULT hr = ds->ReadVirtual(address, buffer, length, &received);
if (FAILED(hr)) {
delete [] buffer;
std::ostringstream estr;
estr << "Cannot read " << length << " bytes from " << address << ": "
<< msgDebugEngineComFailed("ReadVirtual", hr);
*errorMessage = estr.str();
return std::string();
}
if (received < length) {
std::ostringstream estr;
estr << "Warning: Received only " << received << " bytes of " << length << " requested at " << address << '.';
*errorMessage = estr.str();
}
std::ostringstream str;
base64Encode(str, buffer, length);
delete [] buffer;
return str.str();
}

View File

@@ -0,0 +1,148 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef THREADLIST_H
#define THREADLIST_H
#include "common.h"
#include <vector>
/* Various helpers to the extension commands to retrieve debuggee information
* in suitable formats for the debugger engine. */
/* Helpers to retrieve threads and their top stack frame
* in one roundtrip, conveniently in GDBMI format. */
struct StackFrame
{
StackFrame(ULONG64 a = 0);
void formatGDBMI(std::ostream &str, unsigned level = 0) const;
std::wstring fileName() const;
ULONG64 address;
std::wstring function;
std::wstring fullPathName;
ULONG line;
};
bool getFrame(unsigned n, StackFrame *f, std::string *errorMessage);
bool getFrame(CIDebugSymbols *debugSymbols, CIDebugControl *debugControl,
unsigned n, StackFrame *f, std::string *errorMessage);
struct Thread
{
Thread(ULONG i = 0, ULONG sysId = 0);
void formatGDBMI(std::ostream &str) const;
ULONG id;
ULONG systemId;
ULONG64 address;
std::string state;
std::wstring name;
StackFrame frame;
};
typedef std::vector<Thread> Threads;
// Get list of threads.
bool threadList(CIDebugSystemObjects *debugSystemObjects,
CIDebugSymbols *debugSymbols,
CIDebugControl *debugControl,
CIDebugAdvanced *debugAdvanced,
Threads* threads, ULONG *currentThreadId,
std::string *errorMessage);
// Convenience formatting as GDBMI
std::string gdbmiThreadList(CIDebugSystemObjects *debugSystemObjects,
CIDebugSymbols *debugSymbols,
CIDebugControl *debugControl,
CIDebugAdvanced *debugAdvanced,
std::string *errorMessage);
/* Helpers for retrieving module lists */
struct Module {
Module();
std::string name;
std::string image;
bool deferred;
ULONG64 base;
ULONG64 size;
};
typedef std::vector<Module> Modules;
// Retrieve modules info
Modules getModules(CIDebugSymbols *syms, std::string *errorMessage);
// Format modules as GDBMI
std::string gdbmiModules(CIDebugSymbols *syms, bool humanReadable, std::string *errorMessage);
/* Helpers for registers */
struct Register
{
Register();
std::wstring name;
std::wstring description;
bool subRegister;
bool pseudoRegister;
DEBUG_VALUE value;
};
typedef std::vector<Register> Registers;
// Description of a DEBUG_VALUE type field
const wchar_t *valueType(ULONG type);
// Helper to format a DEBUG_VALUE
void formatDebugValue(std::ostream &str, const DEBUG_VALUE &dv, CIDebugControl *ctl = 0);
enum RegisterFlags {
IncludePseudoRegisters = 0x1,
IncludeSubRegisters = 0x2
};
// Retrieve current register snapshot using RegisterFlags
Registers getRegisters(CIDebugRegisters *regs,
unsigned flags,
std::string *errorMessage);
// Format current register snapshot using RegisterFlags as GDBMI
// This is not exactly using the actual GDB formatting as this is
// to verbose (names and values separated)
std::string gdbmiRegisters(CIDebugRegisters *regs,
CIDebugControl *control,
bool humanReadable,
unsigned flags,
std::string *errorMessage);
std::string memoryToBase64(CIDebugDataSpaces *ds, ULONG64 address, ULONG length, std::string *errorMessage);
#endif // THREADLIST_H

View File

@@ -0,0 +1,88 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef IINTERFACEPOINTER_H
#define IINTERFACEPOINTER_H
#include "common.h"
/* AutoPtr for a IDebugInterface */
template <class IInterface>
class IInterfacePointer
{
explicit IInterfacePointer(const IInterfacePointer&);
IInterfacePointer& operator=(const IInterfacePointer&);
public:
inline IInterfacePointer() : m_instance(0), m_hr(S_OK) {}
// IDebugClient4 does not inherit IDebugClient.
inline explicit IInterfacePointer(IDebugClient *client) : m_instance(0), m_hr(S_OK) { create(client); }
inline explicit IInterfacePointer(CIDebugClient *client) : m_instance(0), m_hr(S_OK) { create(client); }
inline ~IInterfacePointer() { release(); }
inline IInterface *operator->() const { return m_instance; }
inline IInterface *data() const { return m_instance; }
inline bool isNull() const { return m_instance == 0; }
inline operator bool() const { return !isNull(); }
inline HRESULT hr() const { return m_hr; }
// This is for creating a IDebugClient from scratch. Not matches by a constructor,
// unfortunately
inline bool create() {
release();
m_hr = DebugCreate(__uuidof(IInterface), (void **)&m_instance);
return m_hr == S_OK;
}
inline bool create(IDebugClient *client) {
release();
m_hr = client->QueryInterface(__uuidof(IInterface), (void **)&m_instance);
return m_hr == S_OK;
}
inline bool create(CIDebugClient *client) {
release();
m_hr = client->QueryInterface(__uuidof(IInterface), (void **)&m_instance);
return m_hr == S_OK;
}
private:
void release() {
if (m_instance) {
m_instance->Release();
m_instance = 0;
m_hr = S_OK;
}
}
IInterface *m_instance;
HRESULT m_hr;
};
#endif // IINTERFACEPOINTER_H

View File

@@ -0,0 +1,98 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "outputcallback.h"
#include "stringutils.h"
#include "extensioncontext.h"
#include "base64.h"
#include <cstring>
OutputCallback::OutputCallback(IDebugOutputCallbacksWide *wrapped) : m_wrapped(wrapped)
{
}
OutputCallback::~OutputCallback() // must be present to avoid exit crashes
{
}
STDMETHODIMP OutputCallback::QueryInterface(
THIS_
IN REFIID InterfaceId,
OUT PVOID* Interface
)
{
*Interface = NULL;
if (IsEqualIID(InterfaceId, __uuidof(IUnknown)) ||
IsEqualIID(InterfaceId, __uuidof(IDebugOutputCallbacksWide)))
{
*Interface = (IDebugOutputCallbacksWide*)this;
AddRef();
return S_OK;
} else {
return E_NOINTERFACE;
}
}
STDMETHODIMP_(ULONG) OutputCallback::AddRef(THIS)
{
// This class is designed to be static so
// there's no true refcount.
return 1;
}
STDMETHODIMP_(ULONG) OutputCallback::Release(THIS)
{
// This class is designed to be static so
// there's no true refcount.
return 0;
}
STDMETHODIMP OutputCallback::Output(
THIS_
IN ULONG mask,
IN PCWSTR 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)
return S_OK;
// Wrap debuggee output in gdbmi such that creator recognizes it
if (mask != DEBUG_OUTPUT_DEBUGGEE) {
m_wrapped->Output(mask, text);
return S_OK;
}
// Base encode as GDBMI is not really made for wide chars
std::ostringstream str;
base64Encode(str, reinterpret_cast<const unsigned char *>(text), sizeof(wchar_t) * std::wcslen(text));
ExtensionContext::instance().report('E', 0, "debuggee_output", str.str().c_str());
return S_OK;
}

View File

@@ -0,0 +1,64 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef DEBUGEVENTOUTPUT_H
#define DEBUGEVENTOUTPUT_H
#include "common.h"
class OutputCallback : public IDebugOutputCallbacksWide
{
public:
explicit OutputCallback(IDebugOutputCallbacksWide *wrapped);
virtual ~OutputCallback();
// IUnknown.
STDMETHOD(QueryInterface)(
THIS_
IN REFIID InterfaceId,
OUT PVOID* Interface
);
STDMETHOD_(ULONG, AddRef)(
THIS
);
STDMETHOD_(ULONG, Release)(
THIS
);
// IDebugOutputCallbacks.
STDMETHOD(Output)(
THIS_
IN ULONG mask,
IN PCWSTR text
);
private:
IDebugOutputCallbacksWide *m_wrapped;
};
#endif // DEBUGEVENTOUTPUT_H

View File

@@ -0,0 +1,14 @@
TEMPLATE = subdirs
# Build the Qt Creator CDB extension depending on whether
# CDB is actually detected.
# In order to do detection and building in one folder,
# use a subdir profile in '.'.
include(cdb_detect.pri)
!isEmpty(CDB_PATH) {
SUBDIRS += lib_qtcreator_cdbext
lib_qtcreator_cdbext.file = qtcreatorcdbext_build.pro
}

View File

@@ -0,0 +1,67 @@
# Build the Qt Creator CDB extension
TEMPLATE = lib
include(../../../qtcreator.pri)
include(cdb_detect.pri)
CONFIG -= precompile_header
CONFIG += hide_symbols
# Switch to statically linked CRT. Note: There will be only one
# global state of the CRT, reconsider if other DLLs are required!
# TODO: No effect, currently?
QMAKE_CXXFLAGS_RELEASE -= -MD
QMAKE_CXXFLAGS_DEBUG -= -MDd
QMAKE_CXXFLAGS_RELEASE += -MT
QMAKE_CXXFLAGS_DEBUG += -MTd
BASENAME=qtcreatorcdbext
DEF_FILE=qtcreatorcdbext.def
IDE_BASE_PATH=$$dirname(IDE_APP_PATH)
# Find out 64/32bit and determine target directories accordingly.
# TODO: This is an ugly hack. Better check compiler (stderr) or something?
ENV_LIB_PATH=$$(LIBPATH)
contains(ENV_LIB_PATH, ^.*amd64.*$) {
DESTDIR=$$IDE_BASE_PATH/lib/$${BASENAME}64
CDB_PLATFORM=amd64
} else {
DESTDIR=$$IDE_BASE_PATH/lib/$${BASENAME}32
CDB_PLATFORM=i386
}
TARGET = $$BASENAME
message("Compiling Qt Creator CDB extension $$TARGET $$DESTDIR for $$CDB_PLATFORM using $$CDB_PATH")
INCLUDEPATH += $$CDB_PATH/inc
LIBS+= -L$$CDB_PATH/lib/$$CDB_PLATFORM -ldbgeng
CONFIG -= qt
QT -= gui
QT -= core
SOURCES += qtcreatorcdbextension.cpp \
extensioncontext.cpp \
eventcallback.cpp \
symbolgroup.cpp \
common.cpp \
stringutils.cpp \
gdbmihelpers.cpp \
outputcallback.cpp \
base64.cpp
HEADERS += extensioncontext.h \
common.h \
iinterfacepointer.h \
eventcallback.h \
symbolgroup.h \
stringutils.h \
gdbmihelpers.h \
outputcallback.h \
base64.h

View File

@@ -0,0 +1,368 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "extensioncontext.h"
#include "outputcallback.h"
#include "eventcallback.h"
#include "symbolgroup.h"
#include "stringutils.h"
#include "gdbmihelpers.h"
#include <cstdio>
#include <sstream>
#include <list>
#include <iterator>
/* QtCreatorCDB ext is an extension loaded into CDB.exe (see cdbengine2.cpp):
* - Notification about the state of the debugging session:
* + idle: (hooked with .idle_cmd) debuggee stopped
* + accessible: Debuggee stopped, cdb.exe accepts commands
* + inaccessible: Debuggee runs, no way to post commands
* + session active/inactive: Lost debuggee, terminating.
* - Hook up with output/event callbacks and produce formatted output
* - Provide some extension commands that produce output in a standardized (GDBMI)
* format that ends up in handleExtensionMessage().
* + pid Return debuggee pid for interrupting.
* + locals Print locals from SymbolGroup
* + expandLocals Expand locals in symbol group
* + registers, modules, threads */
typedef std::vector<std::string> StringVector;
typedef std::list<std::string> StringList;
// Split & Parse the arguments of a command and extract the
// optional first integer token argument ('command -t <number> remaining arguments')
// Each command takes the 'token' argument and includes it in its output
// so that the calling CDB engine in Qt Creator can match the callback.
template<class StringContainer>
static inline StringContainer commandTokens(PCSTR args, int *token = 0)
{
typedef StringContainer::iterator ContainerIterator;
if (token)
*token = 0;
std::string cmd(args);
simplify(cmd);
StringContainer tokens;
split(cmd, ' ', std::back_inserter(tokens));
// Check for token
ContainerIterator it = tokens.begin();
if (it != tokens.end() && *it == "-t" && ++it != tokens.end()) {
if (token)
std::istringstream(*it) >> *token;
tokens.erase(tokens.begin(), ++it);
}
return tokens;
}
// Extension command 'pid':
// Report back PID so that Qt Creator engine knows whom to DebugBreakProcess.
extern "C" HRESULT CALLBACK pid(CIDebugClient *client, PCSTR args)
{
ExtensionContext::instance().hookCallbacks(client);
int token;
commandTokens<StringList>(args, &token);
if (const ULONG pid = currentProcessId(client)) {
ExtensionContext::instance().report('R', token, "pid", "%u", pid);
} else {
ExtensionContext::instance().report('N', token, "pid", "0");
}
return S_OK;
}
// Extension command 'expandlocals':
// Expand a comma-separated iname-list of local variables.
extern "C" HRESULT CALLBACK expandlocals(CIDebugClient *client, PCSTR args)
{
ExtensionCommandContext exc(client);
unsigned frame = 0;
SymbolGroup *symGroup = 0;
std::string errorMessage;
int token;
const StringVector tokens = commandTokens<StringVector>(args, &token);
StringVector inames;
if (tokens.size() == 2u && sscanf(tokens.front().c_str(), "%u", &frame) == 1) {
symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, &errorMessage);
split(tokens.at(1), ',', std::back_inserter(inames));
} else {
std::ostringstream str;
str << "Invalid parameter: '" << args << "' (usage expand <frame> iname1,iname2..).";
errorMessage = str.str();
}
if (symGroup) {
const unsigned succeeded = symGroup->expandList(inames, &errorMessage);
ExtensionContext::instance().report('R', token, "expandlocals", "%u/%u %s",
succeeded, unsigned(inames.size()), errorMessage.c_str());
} else {
ExtensionContext::instance().report('N', token, "expandlocals", errorMessage.c_str());
}
return S_OK;
}
static inline std::string msgLocalsUsage(PCSTR args)
{
std::ostringstream str;
str << "Invalid parameter: '" << args << "' (usage: locals [-d] <frame> [iname]).";
return str.str();
}
// Extension command 'locals':
// Display local variables of symbol group in GDBMI or debug output form.
// Takes an optional iname which is expanded before displaying (for updateWatchData)
static std::string commmandLocals(ExtensionCommandContext &exc,PCSTR args, int *token, std::string *errorMessage)
{
// Parse the command
unsigned debugOutput = 0;
bool humanReadableGdbmi = false;
std::string iname;
StringList tokens = commandTokens<StringList>(args, token);
while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') {
// Parse options -d (debug)/- humanreadable GDBMI
switch (tokens.front().at(1)) {
case 'd':
debugOutput++;
break;
case 'h':
humanReadableGdbmi = true;
break;
}
tokens.pop_front();
}
unsigned frame;
if (tokens.empty() || sscanf(tokens.front().c_str(), "%u", &frame) != 1) {
*errorMessage = msgLocalsUsage(args);
return std::string();
}
tokens.pop_front();
if (!tokens.empty())
iname = tokens.front();
SymbolGroup * const symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), frame, errorMessage);
if (!symGroup)
return std::string();
// Complete dump
if (iname.empty())
return debugOutput ? symGroup->debug(debugOutput - 1) : symGroup->dump(humanReadableGdbmi);
// Look up iname
return symGroup->dump(iname, humanReadableGdbmi, errorMessage);
}
extern "C" HRESULT CALLBACK locals(CIDebugClient *client, PCSTR args)
{
ExtensionCommandContext exc(client);
std::string errorMessage;
int token;
const std::string output = commmandLocals(exc, args, &token, &errorMessage);
if (output.empty()) {
ExtensionContext::instance().report('N', token, "locals", errorMessage.c_str());
} else {
ExtensionContext::instance().report('R', token, "locals", "%s", output.c_str());
}
return S_OK;
}
// Extension command 'assign':
// Assign locals by iname: 'assign locals.x=5'
extern "C" HRESULT CALLBACK assign(CIDebugClient *client, PCSTR argsIn)
{
ExtensionCommandContext exc(client);
std::string errorMessage;
bool success = false;
int token = 0;
do {
const StringList tokens = commandTokens<StringList>(argsIn, &token);
// Parse 'assign locals.x=5'
const std::string::size_type equalsPos = tokens.size() == 1 ? tokens.front().find('=') : std::string::npos;
if (equalsPos == std::string::npos) {
errorMessage = "Syntax error, expecting 'locals.x=5'.";
break;
}
const std::string iname = tokens.front().substr(0, equalsPos);
const std::string value = tokens.front().substr(equalsPos + 1, tokens.front().size() - equalsPos - 1);
// get the symbolgroup
const int currentFrame = ExtensionContext::instance().symbolGroupFrame();
if (currentFrame < 0) {
errorMessage = "No current frame.";
break;
}
SymbolGroup *symGroup = ExtensionContext::instance().symbolGroup(exc.symbols(), exc.threadId(), currentFrame, &errorMessage);
if (!symGroup)
break;
success = symGroup->assign(iname, value, &errorMessage);
} while (false);
if (success) {
ExtensionContext::instance().report('R', token, "assign", "Ok");
} else {
ExtensionContext::instance().report('N', token, "assign", errorMessage.c_str());
}
return S_OK;
}
// Extension command 'threads':
// List all thread info in GDBMI syntax
extern "C" HRESULT CALLBACK threads(CIDebugClient *client, PCSTR argsIn)
{
ExtensionCommandContext exc(client);
std::string errorMessage;
int token;
commandTokens<StringList>(argsIn, &token);
const std::string gdbmi = gdbmiThreadList(exc.systemObjects(),
exc.symbols(),
exc.control(),
exc.advanced(),
&errorMessage);
if (gdbmi.empty()) {
ExtensionContext::instance().report('N', token, "threads", errorMessage.c_str());
} else {
ExtensionContext::instance().report('R', token, "threads", gdbmi.c_str());
}
return S_OK;
}
// Extension command 'registers':
// List all registers in GDBMI syntax
extern "C" HRESULT CALLBACK registers(CIDebugClient *Client, PCSTR argsIn)
{
ExtensionCommandContext exc(Client);
std::string errorMessage;
int token;
const StringList tokens = commandTokens<StringList>(argsIn, &token);
const bool humanReadable = !tokens.empty() && tokens.front() == "-h";
const std::string regs = gdbmiRegisters(exc.registers(), exc.control(), humanReadable, IncludePseudoRegisters, &errorMessage);
if (regs.empty()) {
ExtensionContext::instance().report('N', token, "registers", errorMessage.c_str());
} else {
ExtensionContext::instance().report('R', token, "registers", regs.c_str());
}
return S_OK;
}
// Extension command 'modules':
// List all modules in GDBMI syntax
extern "C" HRESULT CALLBACK modules(CIDebugClient *Client, PCSTR argsIn)
{
ExtensionCommandContext exc(Client);
std::string errorMessage;
int token;
const StringList tokens = commandTokens<StringList>(argsIn, &token);
const bool humanReadable = !tokens.empty() && tokens.front() == "-h";
const std::string modules = gdbmiModules(exc.symbols(), humanReadable, &errorMessage);
if (modules.empty()) {
ExtensionContext::instance().report('N', token, "modules", errorMessage.c_str());
} else {
ExtensionContext::instance().report('R', token, "modules", modules.c_str());
}
return S_OK;
}
// 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)
{
ExtensionContext::instance().notifyIdle();
return S_OK;
}
// Extension command 'help':
// Display version
extern "C" HRESULT CALLBACK help(CIDebugClient *, PCSTR)
{
dprintf("Qt Creator CDB extension built %s\n", __DATE__);
return S_OK;
}
// Extension command 'memory':
// Display memory as base64
extern "C" HRESULT CALLBACK memory(CIDebugClient *Client, PCSTR argsIn)
{
ExtensionCommandContext exc(Client);
std::string errorMessage;
std::string memory;
int token;
ULONG64 address = 0;
ULONG length = 0;
const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
if (tokens.size() == 2
&& integerFromString(tokens.front(), &address)
&& integerFromString(tokens.at(1), &length)) {
memory = memoryToBase64(exc.dataSpaces(), address, length, &errorMessage);
} else {
errorMessage = "Invalid parameters to memory command.";
}
if (memory.empty()) {
ExtensionContext::instance().report('N', token, "memory", errorMessage.c_str());
} else {
ExtensionContext::instance().report('R', token, "memory", memory.c_str());
if (!errorMessage.empty())
ExtensionContext::instance().report('W', token, "memory", errorMessage.c_str());
}
return S_OK;
}
// Hook for dumping Known Structs. Not currently used.
// Shows up in 'dv' as well as IDebugSymbolGroup::GetValueText.
extern "C" HRESULT CALLBACK KnownStructOutput(ULONG Flag, ULONG64 Address, PSTR StructName, PSTR Buffer, PULONG /* BufferSize */)
{
if (Flag == DEBUG_KNOWN_STRUCT_GET_NAMES) {
memcpy(Buffer, "\0\0", 2);
return S_OK;
}
// Usually 260 chars buf
std::ostringstream str;
str << " KnownStructOutput 0x" << std::hex << Address << ' '<< StructName;
strcpy(Buffer, str.str().c_str());
return S_OK;
}

View File

@@ -0,0 +1,180 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "stringutils.h"
#include <cctype>
#include <iostream>
#include <sstream>
static const char whiteSpace[] = " \t\r\n";
void trimFront(std::string &s)
{
if (s.empty())
return;
std::string::size_type pos = s.find_first_not_of(whiteSpace);
if (pos == 0)
return;
if (pos == std::string::npos) { // All blanks?!
s.clear();
} else {
s.erase(0, pos);
}
}
void trimBack(std::string &s)
{
if (s.empty())
return;
std::string::size_type pos = s.find_last_not_of(whiteSpace);
if (pos == std::string::npos) { // All blanks?!
s.clear();
} else {
if (++pos != s.size())
s.erase(pos, s.size() - pos);
}
}
void simplify(std::string &s)
{
trimFront(s);
trimBack(s);
if (s.empty())
return;
// 1) All blanks
const std::string::size_type size1 = s.size();
std::string::size_type pos = 0;
for ( ; pos < size1; pos++)
if (std::isspace(s.at(pos)))
s[pos] = ' ';
// 2) Simplify
for (pos = 0; pos < s.size(); ) {
std::string::size_type blankpos = s.find(' ', pos);
if (blankpos == std::string::npos)
break;
std::string::size_type tokenpos = blankpos + 1;
while (tokenpos < s.size() && s.at(tokenpos) == ' ')
tokenpos++;
if (tokenpos - blankpos > 1)
s.erase(blankpos, tokenpos - blankpos - 1);
pos = blankpos + 1;
}
}
void replace(std::wstring &s, wchar_t before, wchar_t after)
{
const std::wstring::size_type size = s.size();
for (std::wstring::size_type i = 0; i < size; i++)
if (s.at(i) == before)
s[i] = after;
}
static inline void formatGdbmiChar(std::ostream &str, wchar_t c)
{
switch (c) {
case L'\n':
str << "\\n";
break;
case L'\t':
str << "\\t";
break;
case L'\r':
str << "\\r";
break;
case L'\\':
case L'"':
str << '\\' << char(c);
break;
default:
if (c < 128) {
str << char(c);
} else {
// Always pad up to 3 digits in case a digit follows
const char oldFill = str.fill('0');
str << '\\' << std::oct;
str.width(3);
str << unsigned(c) << std::dec;
str.fill(oldFill);
}
break;
}
}
// Stream a wstring onto a char stream doing octal escaping
// suitable for GDBMI.
void gdbmiStringFormat::format(std::ostream &str) const
{
const std::string::size_type size = m_s.size();
for (std::string::size_type i = 0; i < size; i++)
formatGdbmiChar(str, wchar_t(m_s.at(i)));
}
void gdbmiWStringFormat::format(std::ostream &str) const
{
const std::wstring::size_type size = m_w.size();
for (std::wstring::size_type i = 0; i < size; i++)
formatGdbmiChar(str, m_w.at(i));
}
std::string wStringToGdbmiString(const std::wstring &w)
{
std::ostringstream str;
str << gdbmiWStringFormat(w);
return str.str();
}
std::string wStringToString(const std::wstring &w)
{
if (w.empty())
return std::string();
const std::string::size_type size = w.size();
std::string rc;
rc.reserve(size);
for (std::string::size_type i = 0; i < size; i++)
rc.push_back(char(w.at(i)));
return rc;
}
// Format a map as a GDBMI hash {key="value",..}
void formatGdbmiHash(std::ostream &os, const std::map<std::string, std::string> &m)
{
typedef std::map<std::string, std::string>::const_iterator It;
const It begin = m.begin();
const It cend = m.end();
os << '{';
for (It it = begin; it != cend; ++it) {
if (it != begin)
os << ',';
os << it->first << "=\"" << gdbmiStringFormat(it->second) << '"';
}
os << '}';
}

View File

@@ -0,0 +1,122 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef SPLIT_H
#define SPLIT_H
#include <string>
#include <sstream>
#include <map>
void trimFront(std::string &s);
void trimBack(std::string &s);
void simplify(std::string &s);
// Split by character separator.
template <class Iterator>
void split(const std::string &s, char sep, Iterator it)
{
const std::string::size_type size = s.size();
for (std::string::size_type pos = 0; pos < size; ) {
std::string::size_type nextpos = s.find(sep, pos);
if (nextpos == std::string::npos)
nextpos = size;
const std::string token = s.substr(pos, nextpos - pos);
*it = token;
++it;
pos = nextpos + 1;
}
}
// Format numbers, etc, as a string.
template <class Streamable>
std::string toString(const Streamable s)
{
std::ostringstream str;
str << s;
return str.str();
}
// Read an integer from a string as '10' or '0xA'
template <class Integer>
bool integerFromString(const std::string &s, Integer *v)
{
const bool isHex = s.compare(0, 2, "0x") == 0;
std::istringstream str(isHex ? s.substr(2, s.size() - 2) : s);
if (isHex)
str >> std::hex;
str >> *v;
return !str.fail();
}
void replace(std::wstring &s, wchar_t before, wchar_t after);
// Stream a string onto a char stream doing backslash & octal escaping
// suitable for GDBMI usable as 'str << gdbmiStringFormat(wstring)'
class gdbmiStringFormat {
public:
explicit gdbmiStringFormat(const std::string &s) : m_s(s) {}
void format(std::ostream &) const;
private:
const std::string &m_s;
};
// Stream a wstring onto a char stream doing backslash & octal escaping
// suitable for GDBMI usable as 'str << gdbmiWStringFormat(wstring)'
class gdbmiWStringFormat {
public:
explicit gdbmiWStringFormat(const std::wstring &w) : m_w(w) {}
void format(std::ostream &) const;
private:
const std::wstring &m_w;
};
inline std::ostream &operator<<(std::ostream &str, const gdbmiStringFormat &sf)
{
sf.format(str);
return str;
}
inline std::ostream &operator<<(std::ostream &str, const gdbmiWStringFormat &wsf)
{
wsf.format(str);
return str;
}
std::string wStringToGdbmiString(const std::wstring &w);
std::string wStringToString(const std::wstring &w);
// Format a map as a GDBMI hash {key="value",..}
void formatGdbmiHash(std::ostream &os, const std::map<std::string, std::string> &);
#endif // SPLIT_H

View File

@@ -0,0 +1,804 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#include "symbolgroup.h"
#include "stringutils.h"
#include "base64.h"
#include <algorithm>
#include <sstream>
#include <list>
#include <iterator>
#include <set>
enum { debug = 0 };
typedef std::vector<int>::size_type VectorIndexType;
const char rootNameC[] = "local";
enum { BufSize = 2048 };
std::ostream &operator<<(std::ostream &str, const DEBUG_SYMBOL_PARAMETERS &parameters)
{
str << "parent=";
if (parameters.ParentSymbol == DEBUG_ANY_ID) {
str << "DEBUG_ANY_ID";
} else {
str << parameters.ParentSymbol ;
}
str << " flags=" << parameters.Flags;
// Detailed flags:
if (parameters.Flags & DEBUG_SYMBOL_EXPANDED)
str << " EXPANDED";
if (parameters.Flags & DEBUG_SYMBOL_READ_ONLY)
str << " READONLY";
if (parameters.Flags & DEBUG_SYMBOL_IS_ARRAY)
str << " ARRAY";
if (parameters.Flags & DEBUG_SYMBOL_IS_FLOAT)
str << " FLOAT";
if (parameters.Flags & DEBUG_SYMBOL_IS_ARGUMENT)
str << " ARGUMENT";
if (parameters.Flags & DEBUG_SYMBOL_IS_LOCAL)
str << " LOCAL";
str << " typeId=" << parameters.TypeId << " subElements="
<< parameters.SubElements;
return str;
}
SymbolGroup::SymbolGroup(IDebugSymbolGroup2 *sg,
const SymbolParameterVector &vec,
ULONG threadId,
unsigned frame) :
m_symbolGroup(sg),
m_threadId(threadId),
m_frame(frame),
m_root(SymbolGroupNode::create(sg, rootNameC, vec))
{
}
SymbolGroup::~SymbolGroup()
{
m_symbolGroup->Release();
delete m_root;
}
static inline bool getSymbolCount(CIDebugSymbolGroup *symbolGroup,
ULONG *count,
std::string *errorMessage)
{
const HRESULT hr = symbolGroup->GetNumberSymbols(count);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetNumberSymbols", hr);
return false;
}
return true;
}
bool SymbolGroup::getSymbolParameters(CIDebugSymbolGroup *symbolGroup,
SymbolParameterVector *vec,
std::string *errorMessage)
{
ULONG count;
return getSymbolCount(symbolGroup, &count, errorMessage)
&& getSymbolParameters(symbolGroup, 0, count, vec, errorMessage);
}
bool SymbolGroup::getSymbolParameters(CIDebugSymbolGroup *symbolGroup,
unsigned long start,
unsigned long count,
SymbolParameterVector *vec,
std::string *errorMessage)
{
if (!count) {
vec->clear();
return true;
}
// Trim the count to the maximum count available. When expanding elements
// and passing SubElements as count, SubElements might be an estimate that
// is too large and triggers errors.
ULONG totalCount;
if (!getSymbolCount(symbolGroup, &totalCount, errorMessage))
return false;
if (start >= totalCount) {
std::ostringstream str;
str << "SymbolGroup::getSymbolParameters: Start parameter "
<< start << " beyond total " << totalCount << '.';
*errorMessage = str.str();
return false;
}
if (start + count > totalCount)
count = totalCount - start;
// Get parameters.
vec->resize(count);
const HRESULT hr = symbolGroup->GetSymbolParameters(start, count, &(*vec->begin()));
if (FAILED(hr)) {
std::ostringstream str;
str << "SymbolGroup::getSymbolParameters failed for index=" << start << ", count=" << count
<< ": " << msgDebugEngineComFailed("GetSymbolParameters", hr);
*errorMessage = str.str();
return false;
}
return true;
}
SymbolGroup *SymbolGroup::create(CIDebugControl *control, CIDebugSymbols *debugSymbols,
ULONG threadId, unsigned frame,
std::string *errorMessage)
{
errorMessage->clear();
ULONG obtainedFrameCount = 0;
const ULONG frameCount = frame + 1;
DEBUG_STACK_FRAME *frames = new DEBUG_STACK_FRAME[frameCount];
IDebugSymbolGroup2 *idebugSymbols = 0;
bool success = false;
SymbolParameterVector parameters;
// Obtain symbol group at stack frame.
do {
HRESULT hr = control->GetStackTrace(0, 0, 0, frames, frameCount, &obtainedFrameCount);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetStackTrace", hr);
break;
}
if (obtainedFrameCount < frameCount ) {
std::ostringstream str;
str << "Unable to obtain frame " << frame << " (" << obtainedFrameCount << ").";
*errorMessage = str.str();
break;
}
hr = debugSymbols->GetScopeSymbolGroup2(DEBUG_SCOPE_GROUP_LOCALS, NULL, &idebugSymbols);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetScopeSymbolGroup2", hr);
break;
}
hr = debugSymbols->SetScope(0, frames + frame, NULL, 0);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("SetScope", hr);
break;
}
// refresh with current frame
hr = debugSymbols->GetScopeSymbolGroup2(DEBUG_SCOPE_GROUP_LOCALS, idebugSymbols, &idebugSymbols);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("GetScopeSymbolGroup2", hr);
break;
}
if (!SymbolGroup::getSymbolParameters(idebugSymbols, &parameters, errorMessage))
break;
success = true;
} while (false);
delete [] frames;
if (!success) {
if (idebugSymbols)
idebugSymbols->Release();
return 0;
}
return new SymbolGroup(idebugSymbols, parameters, threadId, frame);
}
// ------- SymbolGroupNode
SymbolGroupNode::SymbolGroupNode(CIDebugSymbolGroup *symbolGroup,
const std::string &n,
SymbolGroupNode *parent) :
m_symbolGroup(symbolGroup), m_parent(parent), m_name(n)
{
memset(&m_parameters, 0, sizeof(DEBUG_SYMBOL_PARAMETERS));
m_parameters.ParentSymbol = DEBUG_ANY_ID;
}
void SymbolGroupNode::removeChildren()
{
if (!m_children.empty()) {
const SymbolGroupNodePtrVectorIterator end = m_children.end();
for (SymbolGroupNodePtrVectorIterator it = m_children.begin(); it != end; ++it)
delete *it;
m_children.clear();
}
}
bool SymbolGroupNode::isArrayElement() const
{
return m_parent && (m_parent->m_parameters.Flags & DEBUG_SYMBOL_IS_ARRAY);
}
// iName: Fix array elements to be named 'array.0' instead of 'array.[0]' so
// that sorting in Qt Creator works.
std::string SymbolGroupNode::iName() const
{
std::string rc = m_name;
//rc += isArrayElement() ? 'a' : 'n';
if (isArrayElement() && !rc.empty() && rc.at(0) == '[') {
const std::string::size_type last = rc.size() - 1;
if (rc.at(last) == ']') {
rc.erase(last, 1);
rc.erase(0, 1);
}
}
return rc;
}
// Return full iname as 'locals.this.m_sth'.
std::string SymbolGroupNode::fullIName() const
{
std::string rc = iName();
for (const SymbolGroupNode *p = parent(); p; p = p->parent()) {
rc.insert(0, 1, '.');
rc.insert(0, p->iName());
}
return rc;
}
// Index: Index of symbol, parameterOffset: Looking only at a part of the symbol array, offset
void SymbolGroupNode::parseParameters(VectorIndexType index,
VectorIndexType parameterOffset,
const SymbolGroup::SymbolParameterVector &vec)
{
static char buf[BufSize];
ULONG obtainedSize;
const bool isTopLevel = index == DEBUG_ANY_ID;
if (isTopLevel) {
m_parameters.Flags |= DEBUG_SYMBOL_EXPANDED;
} else {
m_parameters = vec.at(index - parameterOffset);
if (m_parameters.SubElements == 0 || !(m_parameters.Flags & DEBUG_SYMBOL_EXPANDED))
return; // No children
}
if (m_parameters.SubElements > 1)
m_children.reserve(m_parameters.SubElements);
const VectorIndexType size = vec.size();
// Scan the top level elements
const VectorIndexType startIndex = isTopLevel ? 0 : index + 1;
for (VectorIndexType pos = startIndex - parameterOffset; pos < size ; pos++ ) {
if (vec.at(pos).ParentSymbol == index) {
const VectorIndexType symbolGroupIndex = pos + parameterOffset;
HRESULT hr = m_symbolGroup->GetSymbolName(ULONG(symbolGroupIndex), buf, BufSize, &obtainedSize);
const std::string name = SUCCEEDED(hr) ? std::string(buf) : std::string("unnamed");
SymbolGroupNode *child = new SymbolGroupNode(m_symbolGroup, name, this);
child->parseParameters(symbolGroupIndex, parameterOffset, vec);
m_children.push_back(child);
}
}
if (isTopLevel)
m_parameters.SubElements = ULONG(m_children.size());
}
SymbolGroupNode *SymbolGroupNode::create(CIDebugSymbolGroup *sg, const std::string &name, const SymbolGroup::SymbolParameterVector &vec)
{
SymbolGroupNode *rc = new SymbolGroupNode(sg, name);
rc->parseParameters(DEBUG_ANY_ID, 0, vec);
return rc;
}
// Fix some oddities in CDB values
static void fixValue(const std::string &type, std::wstring *value)
{
// Pointers: fix '0x00000000`00000AD class bla' ... to "0xAD", but leave
// 'const char *' values as is.
if (!type.empty() && type.at(type.size() - 1) == L'*' && value->compare(0, 2, L"0x") == 0) {
// Remove dumb 64bit separator
if (value->size() > 10 && value->at(10) == L'`')
value->erase(10, 1);
const std::string::size_type firstNonNullDigit = value->find_first_not_of(L"0", 2);
// No on-null digits: plain null ptr.
if (firstNonNullDigit == std::string::npos || value->at(firstNonNullDigit) == ' ') {
*value = L"0x0";
return;
}
// Strip
if (firstNonNullDigit > 2)
value->erase(2, firstNonNullDigit - 2);
// Strip ' Class bla"
std::wstring::size_type classPos = value->find(L" struct", 2);
if (classPos == std::string::npos)
classPos = value->find(L" class", 2);
if (classPos != std::string::npos)
value->erase(classPos, value->size() - classPos);
return;
}
// Integers: fix '0n10' -> '10'
if (value->size() >= 3 && value->compare(0, 2, L"0n") == 0
&& (isdigit(value->at(2)) || value->at(2) == L'-')) {
value->erase(0, 2);
return;
}
}
// Check for ASCII-encode-able stuff. Plain characters + tabs at the most, no newline.
static bool isSevenBitClean(const wchar_t *buf, ULONG size)
{
const wchar_t *bufEnd = buf + size;
for (const wchar_t *bufPtr = buf; bufPtr < bufEnd; bufPtr++) {
const wchar_t c = *bufPtr;
if (c > 127 || (c < 32 && c != 9))
return false;
}
return true;
}
std::string SymbolGroupNode::getType(ULONG index) const
{
static char buf[BufSize];
const HRESULT hr = m_symbolGroup->GetSymbolTypeName(index, buf, BufSize, NULL);
return SUCCEEDED(hr) ? std::string(buf) : std::string();
}
wchar_t *SymbolGroupNode::getValue(ULONG index,
ULONG *obtainedSizeIn /* = 0 */) const
{
// Determine size and return allocated buffer
if (obtainedSizeIn)
*obtainedSizeIn = 0;
const ULONG maxValueSize = 262144;
ULONG obtainedSize = 0;
HRESULT hr = m_symbolGroup->GetSymbolValueTextWide(index, NULL, maxValueSize, &obtainedSize);
if (FAILED(hr))
return 0;
if (obtainedSize > maxValueSize)
obtainedSize = maxValueSize;
wchar_t *buffer = new wchar_t[obtainedSize];
hr = m_symbolGroup->GetSymbolValueTextWide(index, buffer, obtainedSize, &obtainedSize);
if (FAILED(hr)) { // Whoops, should not happen
delete [] buffer;
return 0;
}
if (obtainedSizeIn)
*obtainedSizeIn = obtainedSize;
return buffer;
}
ULONG64 SymbolGroupNode::address(ULONG index) const
{
ULONG64 address = 0;
const HRESULT hr = m_symbolGroup->GetSymbolOffset(index, &address);
if (SUCCEEDED(hr))
return address;
return 0;
}
std::wstring SymbolGroupNode::rawValue(ULONG index) const
{
std::wstring rc;
if (const wchar_t *wbuf = getValue(index)) {
rc = wbuf;
delete[] wbuf;
}
return rc;
}
std::wstring SymbolGroupNode::fixedValue(ULONG index) const
{
std::wstring value = rawValue(index);
fixValue(getType(index), &value);
return value;
}
SymbolGroupNode *SymbolGroupNode::childAt(unsigned i) const
{
return i < m_children.size() ? m_children.at(i) : static_cast<SymbolGroupNode *>(0);
}
static inline void indentStream(std::ostream &str, unsigned depth)
{
for (unsigned d = 0; d < depth; d++)
str << " ";
}
void SymbolGroupNode::dump(std::ostream &str, unsigned child, unsigned depth,
bool humanReadable, ULONG &index) const
{
const std::string iname = fullIName();
const std::string type = getType(index);
if (child) { // Separate list of children
str << ',';
if (humanReadable)
str << '\n';
}
if (humanReadable)
indentStream(str, depth);
str << "{iname=\"" << iname << "\",exp=\"" << iname << "\",name=\"" << m_name
<< "\",type=\"" << type << '"';
if (const ULONG64 addr = address(index))
str << ",addr=\"" << std::hex << std::showbase << addr << std::noshowbase << std::dec
<< '"';
ULONG obtainedSize = 0;
if (const wchar_t *wbuf = getValue(index, &obtainedSize)) {
const ULONG valueSize = obtainedSize - 1;
// ASCII or base64?
if (isSevenBitClean(wbuf, valueSize)) {
std::wstring value = wbuf;
fixValue(type, &value);
str << ",valueencoded=\"0\",value=\"" << gdbmiWStringFormat(value) << '"';
} else {
str << ",valueencoded=\"2\",value=\"";
base64Encode(str, reinterpret_cast<const unsigned char *>(wbuf), valueSize * sizeof(wchar_t));
str << '"';
}
delete [] wbuf;
}
// Children: Dump all known or subelements (guess).
const VectorIndexType childCountGuess = m_children.empty() ? m_parameters.SubElements : m_children.size();
// No children..suppose we are editable and enabled
if (childCountGuess == 0)
str << ",valueenabled=\"true\",valueeditable=\"true\"";
str << ",numchild=\"" << childCountGuess << '"';
if (!m_children.empty()) {
str << ",children=[";
if (humanReadable)
str << '\n';
}
}
void SymbolGroupNode::dumpChildrenVisited(std::ostream &str, bool humanReadable) const
{
if (!m_children.empty())
str << ']';
str << '}';
if (humanReadable)
str << '\n';
}
bool SymbolGroupNode::accept(SymbolGroupNodeVisitor &visitor, unsigned child, unsigned depth, ULONG &index) const
{
// If we happen to be the root node, just skip over
const bool invisibleRoot = index == DEBUG_ANY_ID;
const unsigned childDepth = invisibleRoot ? 0 : depth + 1;
if (invisibleRoot) {
index = 0;
} else {
// Visit us and move index forward.
if (visitor.visit(this, child, depth, index))
return true;
index++;
}
const unsigned childCount = unsigned(m_children.size());
for (unsigned c = 0; c < childCount; c++)
if (m_children.at(c)->accept(visitor, c, childDepth, index))
return true;
if (!invisibleRoot)
visitor.childrenVisited(this, depth);
return false;
}
void SymbolGroupNode::debug(std::ostream &str, unsigned verbosity, unsigned depth, ULONG index) const
{
indentStream(str, depth);
str << '"' << m_name << "\" Children=" << m_children.size() << ' ' << m_parameters;
if (verbosity)
str << " Address=0x" << std::hex << address(index) << std::dec << " Type=\"" << getType(index) << "\" Value=\"" << gdbmiWStringFormat(rawValue(index)) << '"';
str << '\n';
}
// Index offset when stepping past this node in a symbol parameter array. Basically
// self + recursive all child counts.
ULONG SymbolGroupNode::recursiveIndexOffset() const
{
ULONG rc = 1u;
if (!m_children.empty()) {
const SymbolGroupNodePtrVectorConstIterator cend = m_children.end();
for (SymbolGroupNodePtrVectorConstIterator it = m_children.begin(); it != cend; ++it)
rc += (*it)->recursiveIndexOffset();
}
return rc;
}
// Expand!
bool SymbolGroupNode::expand(ULONG index, std::string *errorMessage)
{
if (::debug > 1)
DebugPrint() << "SymbolGroupNode::expand " << m_name << ' ' << index;
if (!m_children.empty())
return true;
if (m_parameters.SubElements == 0) {
*errorMessage = "No subelements to expand in node: " + fullIName();
return false;
}
const HRESULT hr = m_symbolGroup->ExpandSymbol(index, TRUE);
if (FAILED(hr)) {
*errorMessage = msgDebugEngineComFailed("ExpandSymbol", hr);
return false;
}
SymbolGroup::SymbolParameterVector parameters;
// Retrieve parameters (including self, re-retrieve symbol parameters to get new 'expanded' flag
// and corrected SubElement count (might be estimate)) and create child nodes.
if (!SymbolGroup::getSymbolParameters(m_symbolGroup,
index, m_parameters.SubElements + 1,
&parameters, errorMessage))
return false;
parseParameters(index, index, parameters);
return true;
}
static inline std::string msgNotFound(const std::string &nodeName)
{
std::ostringstream str;
str << "Node '" << nodeName << "' not found.";
return str.str();
}
std::string SymbolGroup::dump(bool humanReadable) const
{
std::ostringstream str;
DumpSymbolGroupNodeVisitor visitor(str, humanReadable);
if (humanReadable)
str << '\n';
str << '[';
accept(visitor);
str << ']';
return str.str();
}
// Dump a node, potentially expand
std::string SymbolGroup::dump(const std::string &name, bool humanReadable, std::string *errorMessage)
{
ULONG index;
SymbolGroupNode *const node = find(name, &index);
if (node == 0) {
*errorMessage = msgNotFound(name);
return std::string();
}
if (node->subElements() && node->children().empty()) {
if (!expand(name, errorMessage))
return false;
}
std::ostringstream str;
if (humanReadable)
str << '\n';
DumpSymbolGroupNodeVisitor visitor(str, humanReadable);
str << '[';
node->accept(visitor, 0, 0, index);
str << ']';
return str.str();
}
std::string SymbolGroup::debug(unsigned verbosity) const
{
std::ostringstream str;
DebugSymbolGroupNodeVisitor visitor(str, verbosity);
accept(visitor);
return str.str();
}
/* expandList: Expand a list of inames with a 'mkdir -p'-like behaviour, that is,
* expand all sub-paths. The list of inames has thus to be reordered to expand the
* parent items first, for example "locals.this.i1.data,locals.this.i2" --->:
* "locals, locals.this, locals.this.i1, locals.this.i2, locals.this.i1.data".
* This is done here by creating a set of name parts keyed by level and name
* (thus purging duplicates). */
typedef std::pair<unsigned, std::string> InamePathEntry;
struct InamePathEntryLessThan : public std::binary_function<InamePathEntry, InamePathEntry, bool> {
bool operator()(const InamePathEntry &i1, const InamePathEntry& i2) const
{
if (i1.first < i2.first)
return true;
if (i1.first != i2.first)
return false;
return i1.second < i2.second;
}
};
typedef std::set<InamePathEntry, InamePathEntryLessThan> InamePathEntrySet;
// Expand a node list "locals.i1,locals.i2"
unsigned SymbolGroup::expandList(const std::vector<std::string> &nodes, std::string *errorMessage)
{
if (nodes.empty())
return 0;
// Create a set with a key <level, name>. Also required for 1 node.
InamePathEntrySet pathEntries;
const VectorIndexType nodeCount = nodes.size();
for (VectorIndexType i= 0; i < nodeCount; i++) {
const std::string &iname = nodes.at(i);
std::string::size_type pos = 0;
for (unsigned level = 0; pos < iname.size(); level++) {
std::string::size_type dotPos = iname.find('.', pos);
if (dotPos == std::string::npos)
dotPos = iname.size();
pathEntries.insert(InamePathEntry(level, iname.substr(0, dotPos)));
pos = dotPos + 1;
}
}
// Now expand going by level.
unsigned succeeded = 0;
std::string nodeError;
InamePathEntrySet::const_iterator cend = pathEntries.end();
for (InamePathEntrySet::const_iterator it = pathEntries.begin(); it != cend; ++it)
if (expand(it->second, &nodeError)) {
succeeded++;
} else {
if (!errorMessage->empty())
errorMessage->append(", ");
errorMessage->append(nodeError);
}
return succeeded;
}
bool SymbolGroup::expand(const std::string &nodeName, std::string *errorMessage)
{
ULONG index = DEBUG_ANY_ID;
SymbolGroupNode *node = find(nodeName, &index);
if (::debug)
DebugPrint() << "expand: " << nodeName << " found=" << (node != 0) << " index= " << index << '\n';
if (!node) {
*errorMessage = msgNotFound(nodeName);
return false;
}
if (node == m_root) // Shouldn't happen, still, all happy
return true;
return node->expand(index, errorMessage);
}
static inline std::string msgAssignError(const std::string &nodeName,
const std::string &value,
const std::string &why)
{
std::ostringstream str;
str << "Unable to assign '" << value << "' to '" << nodeName << "': " << why;
return str.str();
}
bool SymbolGroup::assign(const std::string &nodeName, const std::string &value,
std::string *errorMessage)
{
ULONG index;
SymbolGroupNode *node = find(nodeName, &index);
if (node == 0) {
*errorMessage = msgAssignError(nodeName, value, "No such node");
return false;
}
const HRESULT hr = m_symbolGroup->WriteSymbol(index, const_cast<char *>(value.c_str()));
if (FAILED(hr)) {
*errorMessage = msgAssignError(nodeName, value, msgDebugEngineComFailed("WriteSymbol", hr));
return false;
}
return true;
}
bool SymbolGroup::accept(SymbolGroupNodeVisitor &visitor) const
{
if (!m_root || m_root->children().empty())
return false;
ULONG index = DEBUG_ANY_ID;
return m_root->accept(visitor, 0, 0, index);
}
// Find "locals.this.i1" and move index recursively
static SymbolGroupNode *findNodeRecursion(const std::vector<std::string> &iname,
unsigned depth,
std::vector<SymbolGroupNode *> nodes,
ULONG *index = 0)
{
typedef std::vector<SymbolGroupNode *>::const_iterator ConstIt;
if (::debug > 1)
DebugPrint() << "findNodeRecursion " << iname.size() << '/'
<< iname.back() << " depth=" << depth
<< " nodes=" << nodes.size() << " index=" << (index ? *index : ULONG(0));
if (nodes.empty())
return 0;
// Find the child that matches the iname part at depth
const ConstIt cend = nodes.end();
for (ConstIt it = nodes.begin(); it != cend; ++it) {
SymbolGroupNode *c = *it;
if (c->name() == iname.at(depth)) {
if (depth == iname.size() - 1) { // Complete iname matched->happy.
return c;
} else {
// Sub-part of iname matched. Forward index and check children.
if (index)
(*index)++; // Skip ourselves
return findNodeRecursion(iname, depth + 1, c->children(), index);
}
} else {
if (index) // No match for this child, forward the index past all expanded children
*index += c->recursiveIndexOffset();
}
}
return 0;
}
SymbolGroupNode *SymbolGroup::findI(const std::string &iname, ULONG *index) const
{
if (index)
*index = DEBUG_ANY_ID;
if (iname.empty())
return 0;
// Match the root element only: Shouldn't happen, still, all happy
if (iname == m_root->name())
return m_root;
std::vector<std::string> inameTokens;
split(iname, '.', std::back_inserter(inameTokens));
// Must begin with root
if (inameTokens.front() != m_root->name())
return 0;
// Start with index = 0 at root's children
if (index)
*index = 0;
return findNodeRecursion(inameTokens, 1, m_root->children(), index);
}
SymbolGroupNode *SymbolGroup::find(const std::string &iname, ULONG *index) const
{
SymbolGroupNode *rc = findI(iname, index);
if (::debug > 1)
DebugPrint() << "SymbolGroup::find " << iname << ' ' << rc << ' ' << (index ? *index : ULONG(0));
return rc;
}
// --------- DebugSymbolGroupNodeVisitor
DebugSymbolGroupNodeVisitor::DebugSymbolGroupNodeVisitor(std::ostream &os, unsigned verbosity) :
m_os(os), m_verbosity(verbosity)
{
}
bool DebugSymbolGroupNodeVisitor::visit(const SymbolGroupNode *node,
unsigned /* child */, unsigned depth, ULONG index)
{
node->debug(m_os, m_verbosity, depth, index);
return false;
}
// --------------------- DumpSymbolGroupNodeVisitor
DumpSymbolGroupNodeVisitor::DumpSymbolGroupNodeVisitor(std::ostream &os,
bool humanReadable) :
m_os(os), m_humanReadable(humanReadable)
{
}
bool DumpSymbolGroupNodeVisitor::visit(const SymbolGroupNode *node, unsigned child, unsigned depth, ULONG index)
{
node->dump(m_os, child, depth, m_humanReadable, index);
return false;
}
void DumpSymbolGroupNodeVisitor::childrenVisited(const SymbolGroupNode *node, unsigned)
{
node->dumpChildrenVisited(m_os, m_humanReadable);
}

View File

@@ -0,0 +1,224 @@
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/
#ifndef SYMBOLGROUP_H
#define SYMBOLGROUP_H
#include "common.h"
#include <string>
#include <vector>
#include <iosfwd>
std::ostream &operator<<(std::ostream &, const DEBUG_SYMBOL_PARAMETERS&p);
class SymbolGroupNodeVisitor;
// Thin wrapper around a symbol group entry.
class SymbolGroupNode {
SymbolGroupNode(const SymbolGroupNode&);
SymbolGroupNode& operator=(const SymbolGroupNode&);
public:
typedef std::vector<DEBUG_SYMBOL_PARAMETERS> SymbolParameterVector;
typedef std::vector<SymbolGroupNode *> SymbolGroupNodePtrVector;
typedef SymbolGroupNodePtrVector::iterator SymbolGroupNodePtrVectorIterator;
typedef SymbolGroupNodePtrVector::const_iterator SymbolGroupNodePtrVectorConstIterator;
explicit SymbolGroupNode(CIDebugSymbolGroup *symbolGroup,
const std::string &name,
SymbolGroupNode *parent = 0);
~SymbolGroupNode() { removeChildren(); }
void removeChildren();
void parseParameters(SymbolParameterVector::size_type index,
SymbolParameterVector::size_type parameterOffset,
const SymbolParameterVector &vec);
static SymbolGroupNode *create(CIDebugSymbolGroup *sg, const std::string &name, const SymbolParameterVector &vec);
const std::string &name() const { return m_name; }
std::string fullIName() const;
std::string iName() const;
const SymbolGroupNodePtrVector &children() const { return m_children; }
SymbolGroupNode *childAt(unsigned) const;
const SymbolGroupNode *parent() const { return m_parent; }
// I/O: Gdbmi dump for Visitors
void dump(std::ostream &str, unsigned child, unsigned depth,
bool humanReadable, ULONG &index) const;
void dumpChildrenVisited(std::ostream &str, bool humanReadable) const;
// I/O: debug for Visitors
void debug(std::ostream &os, unsigned verbosity, unsigned depth, ULONG index) const;
std::wstring rawValue(ULONG index) const;
std::wstring fixedValue(ULONG index) const;
ULONG64 address(ULONG index) const;
bool accept(SymbolGroupNodeVisitor &visitor, unsigned child, unsigned depth, ULONG &index) const;
// Skip indexes of all children
ULONG recursiveIndexOffset() const;
bool expand(ULONG index, std::string *errorMessage);
ULONG subElements() const { return m_parameters.SubElements; }
private:
// Return allocated wide string array of value
wchar_t *getValue(ULONG index, ULONG *obtainedSize = 0) const;
std::string getType(ULONG index) const;
bool isArrayElement() const;
CIDebugSymbolGroup *const m_symbolGroup;
SymbolGroupNode *m_parent;
DEBUG_SYMBOL_PARAMETERS m_parameters; // Careful when using ParentSymbol. It might not be correct.
SymbolGroupNodePtrVector m_children;
const std::string m_name;
};
/* Visitor that takes care of iterating over the nodes and the index bookkeeping.
* visit() is not called for the (invisible) root node, but starting with the
* root's children with depth=0.
* Return true from visit() to terminate the recursion. */
class SymbolGroupNodeVisitor {
SymbolGroupNodeVisitor(const SymbolGroupNodeVisitor&);
SymbolGroupNodeVisitor& operator=(const SymbolGroupNodeVisitor&);
friend class SymbolGroupNode;
protected:
SymbolGroupNodeVisitor() {}
public:
virtual ~SymbolGroupNodeVisitor() {}
private:
virtual bool visit(const SymbolGroupNode *node, unsigned child, unsigned depth, ULONG index) = 0;
// Helper for formatting output.
virtual void childrenVisited(const SymbolGroupNode * /* node */, unsigned /* depth */) {}
};
// Thin wrapper around a symbol group storing a tree of expanded symbols rooted on
// a fake "locals" root element.
// Provides a find() method based on inames ("locals.this.i1.data") that retrieves
// that index based on the current expansion state.
class SymbolGroup {
public:
typedef std::vector<DEBUG_SYMBOL_PARAMETERS> SymbolParameterVector;
private:
explicit SymbolGroup(CIDebugSymbolGroup *,
const SymbolParameterVector &vec,
ULONG threadId, unsigned frame);
SymbolGroup(const SymbolGroup &);
SymbolGroup &operator=(const SymbolGroup &);
public:
typedef SymbolGroupNode::SymbolGroupNodePtrVector SymbolGroupNodePtrVector;
static SymbolGroup *create(CIDebugControl *control,
CIDebugSymbols *,
ULONG threadId,
unsigned frame,
std::string *errorMessage);
~SymbolGroup();
// Dump all
std::string dump(bool humanReadable = false) const;
// Expand node and dump
std::string dump(const std::string &name, bool humanReadable, std::string *errorMessage);
std::string debug(unsigned verbosity = 0) const;
unsigned frame() const { return m_frame; }
ULONG threadId() const { return m_threadId; }
const SymbolGroupNode *root() { return m_root; }
// Expand a node list "locals.i1,locals.i2", expanding all nested child nodes
// (think mkdir -p).
unsigned expandList(const std::vector<std::string> &nodes, std::string *errorMessage);
// Expand a single node "locals.A.B" requiring that "locals.A.B" is already visible
// (think mkdir without -p).
bool expand(const std::string &node, std::string *errorMessage);
bool accept(SymbolGroupNodeVisitor &visitor) const;
// Assign a value by iname
bool assign(const std::string &node,
const std::string &value,
std::string *errorMessage);
static bool getSymbolParameters(CIDebugSymbolGroup *m_symbolGroup,
unsigned long start,
unsigned long count,
SymbolParameterVector *vec,
std::string *errorMessage);
private:
SymbolGroupNode *find(const std::string &iname, ULONG *index = 0) const;
inline SymbolGroupNode *findI(const std::string &iname, ULONG *index = 0) const;
static bool getSymbolParameters(CIDebugSymbolGroup *m_symbolGroup,
SymbolParameterVector *vec,
std::string *errorMessage);
CIDebugSymbolGroup * const m_symbolGroup;
const unsigned m_frame;
const ULONG m_threadId;
SymbolGroupNode *const m_root;
};
// Debug output visitor.
class DebugSymbolGroupNodeVisitor : public SymbolGroupNodeVisitor {
public:
explicit DebugSymbolGroupNodeVisitor(std::ostream &os, unsigned verbosity = 0);
private:
virtual bool visit(const SymbolGroupNode *node, unsigned child, unsigned depth, ULONG index);
std::ostream &m_os;
const unsigned m_verbosity;
};
// Gdbmi dump output visitor.
class DumpSymbolGroupNodeVisitor : public SymbolGroupNodeVisitor {
public:
explicit DumpSymbolGroupNodeVisitor(std::ostream &os, bool humanReadable);
private:
virtual bool visit(const SymbolGroupNode *node, unsigned child, unsigned depth, ULONG index);
virtual void childrenVisited(const SymbolGroupNode * node, unsigned depth);
std::ostream &m_os;
const bool m_humanReadable;
};
#endif // SYMBOLGROUP_H

View File

@@ -0,0 +1,24 @@
@echo off
REM test.bat: Test script to launch CDB.exe using the extension
REM with the tests/manual demo project.
REM !qtcreatorcdbext.help
REM !qtcreatorcdbext.assign local.this.m_w=44
REM !qtcreatorcdbext.locals 0
set ROOT=d:\dev\qt4.7-vs8\creator
set _NT_DEBUGGER_EXTENSION_PATH=%ROOT%\lib\qtcreatorcdbext32
set EXT=qtcreatorcdbext.dll
set EXE=%ROOT%\tests\manual\gdbdebugger\gui\debug\gui.exe
set CDB=D:\Programme\Debugging Tools for Windows (x86)\cdb.exe
echo %CDB%
echo %EXT% %_NT_DEBUGGER_EXTENSION_PATH%
echo "!qtcreatorcdbext.pid"
REM Launch emulating cdbengine's setup with idle reporting
"%CDB%" -G -a%EXT% -c ".idle_cmd !qtcreatorcdbext.idle" %EXE%

View File

@@ -0,0 +1,23 @@
@echo off
REM test.bat: Test script to launch CDB.exe using the extension
REM with the tests/manual demo project.
REM !qtcreatorcdbext.help
REM !qtcreatorcdbext.assign local.this.m_w=44
REM !qtcreatorcdbext.locals 0
set ROOT=c:\qt\4.7-vs8\creator
set _NT_DEBUGGER_EXTENSION_PATH=%ROOT%\lib\qtcreatorcdbext64
set EXT=qtcreatorcdbext.dll
set EXE=%ROOT%\tests\manual\gdbdebugger\gui\debug\gui.exe
set CDB=C:\PROGRA~1\DEBUGG~1\cdb.exe
echo %CDB%
echo "!qtcreatorcdbext.pid"
REM Launch emulating cdbengine's setup with idle reporting
%CDB% -G -a%EXT% -c ".idle_cmd ^!qtcreatorcdbext.idle" %EXE%