forked from qt-creator/qt-creator
Debugger: Add extension library to be loaded into CDB.exe
This commit is contained in:
@@ -14,3 +14,5 @@ SUBDIRS = \
|
||||
qmleditorwidgets \
|
||||
symbianutils \
|
||||
3rdparty
|
||||
|
||||
win32:SUBDIRS += qtcreatorcdbext
|
||||
|
||||
80
src/libs/qtcreatorcdbext/base64.cpp
Normal file
80
src/libs/qtcreatorcdbext/base64.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
src/libs/qtcreatorcdbext/base64.h
Normal file
38
src/libs/qtcreatorcdbext/base64.h
Normal 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
|
||||
13
src/libs/qtcreatorcdbext/cdb_detect.pri
Normal file
13
src/libs/qtcreatorcdbext/cdb_detect.pri
Normal 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"
|
||||
}
|
||||
}
|
||||
120
src/libs/qtcreatorcdbext/common.cpp
Normal file
120
src/libs/qtcreatorcdbext/common.cpp
Normal 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;
|
||||
}
|
||||
76
src/libs/qtcreatorcdbext/common.h
Normal file
76
src/libs/qtcreatorcdbext/common.h
Normal 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
|
||||
302
src/libs/qtcreatorcdbext/eventcallback.cpp
Normal file
302
src/libs/qtcreatorcdbext/eventcallback.cpp
Normal 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;
|
||||
}
|
||||
157
src/libs/qtcreatorcdbext/eventcallback.h
Normal file
157
src/libs/qtcreatorcdbext/eventcallback.h
Normal 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
|
||||
319
src/libs/qtcreatorcdbext/extensioncontext.cpp
Normal file
319
src/libs/qtcreatorcdbext/extensioncontext.cpp
Normal 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;
|
||||
}
|
||||
134
src/libs/qtcreatorcdbext/extensioncontext.h
Normal file
134
src/libs/qtcreatorcdbext/extensioncontext.h
Normal 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
|
||||
561
src/libs/qtcreatorcdbext/gdbmihelpers.cpp
Normal file
561
src/libs/qtcreatorcdbext/gdbmihelpers.cpp
Normal 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, ¤tThreadId, 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(®isterCount);
|
||||
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 ® = 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();
|
||||
}
|
||||
148
src/libs/qtcreatorcdbext/gdbmihelpers.h
Normal file
148
src/libs/qtcreatorcdbext/gdbmihelpers.h
Normal 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
|
||||
88
src/libs/qtcreatorcdbext/iinterfacepointer.h
Normal file
88
src/libs/qtcreatorcdbext/iinterfacepointer.h
Normal 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
|
||||
98
src/libs/qtcreatorcdbext/outputcallback.cpp
Normal file
98
src/libs/qtcreatorcdbext/outputcallback.cpp
Normal 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;
|
||||
}
|
||||
64
src/libs/qtcreatorcdbext/outputcallback.h
Normal file
64
src/libs/qtcreatorcdbext/outputcallback.h
Normal 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
|
||||
14
src/libs/qtcreatorcdbext/qtcreatorcdbext.pro
Normal file
14
src/libs/qtcreatorcdbext/qtcreatorcdbext.pro
Normal 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
|
||||
}
|
||||
67
src/libs/qtcreatorcdbext/qtcreatorcdbext_build.pro
Normal file
67
src/libs/qtcreatorcdbext/qtcreatorcdbext_build.pro
Normal 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
|
||||
|
||||
368
src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp
Normal file
368
src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp
Normal 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;
|
||||
}
|
||||
180
src/libs/qtcreatorcdbext/stringutils.cpp
Normal file
180
src/libs/qtcreatorcdbext/stringutils.cpp
Normal 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 << '}';
|
||||
}
|
||||
122
src/libs/qtcreatorcdbext/stringutils.h
Normal file
122
src/libs/qtcreatorcdbext/stringutils.h
Normal 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
|
||||
804
src/libs/qtcreatorcdbext/symbolgroup.cpp
Normal file
804
src/libs/qtcreatorcdbext/symbolgroup.cpp
Normal 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 ¶meters)
|
||||
{
|
||||
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, ¶meters, 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,
|
||||
¶meters, 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);
|
||||
}
|
||||
224
src/libs/qtcreatorcdbext/symbolgroup.h
Normal file
224
src/libs/qtcreatorcdbext/symbolgroup.h
Normal 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
|
||||
24
src/libs/qtcreatorcdbext/test32.bat
Normal file
24
src/libs/qtcreatorcdbext/test32.bat
Normal 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%
|
||||
23
src/libs/qtcreatorcdbext/test64.bat
Normal file
23
src/libs/qtcreatorcdbext/test64.bat
Normal 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%
|
||||
Reference in New Issue
Block a user