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 \
|
qmleditorwidgets \
|
||||||
symbianutils \
|
symbianutils \
|
||||||
3rdparty
|
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