From 56d3664934cb825dabffab0cb9b5735849d9c11d Mon Sep 17 00:00:00 2001 From: Friedemann Kleint <Friedemann.Kleint@nokia.com> Date: Thu, 18 Nov 2010 13:55:52 +0100 Subject: [PATCH] Debugger: Add extension library to be loaded into CDB.exe --- src/libs/libs.pro | 2 + src/libs/qtcreatorcdbext/base64.cpp | 80 ++ src/libs/qtcreatorcdbext/base64.h | 38 + src/libs/qtcreatorcdbext/cdb_detect.pri | 13 + src/libs/qtcreatorcdbext/common.cpp | 120 +++ src/libs/qtcreatorcdbext/common.h | 76 ++ src/libs/qtcreatorcdbext/eventcallback.cpp | 302 +++++++ src/libs/qtcreatorcdbext/eventcallback.h | 157 ++++ src/libs/qtcreatorcdbext/extensioncontext.cpp | 319 +++++++ src/libs/qtcreatorcdbext/extensioncontext.h | 134 +++ src/libs/qtcreatorcdbext/gdbmihelpers.cpp | 561 ++++++++++++ src/libs/qtcreatorcdbext/gdbmihelpers.h | 148 ++++ src/libs/qtcreatorcdbext/iinterfacepointer.h | 88 ++ src/libs/qtcreatorcdbext/outputcallback.cpp | 98 +++ src/libs/qtcreatorcdbext/outputcallback.h | 64 ++ src/libs/qtcreatorcdbext/qtcreatorcdbext.pro | 14 + .../qtcreatorcdbext/qtcreatorcdbext_build.pro | 67 ++ .../qtcreatorcdbext/qtcreatorcdbextension.cpp | 368 ++++++++ src/libs/qtcreatorcdbext/stringutils.cpp | 180 ++++ src/libs/qtcreatorcdbext/stringutils.h | 122 +++ src/libs/qtcreatorcdbext/symbolgroup.cpp | 804 ++++++++++++++++++ src/libs/qtcreatorcdbext/symbolgroup.h | 224 +++++ src/libs/qtcreatorcdbext/test32.bat | 24 + src/libs/qtcreatorcdbext/test64.bat | 23 + 24 files changed, 4026 insertions(+) create mode 100644 src/libs/qtcreatorcdbext/base64.cpp create mode 100644 src/libs/qtcreatorcdbext/base64.h create mode 100644 src/libs/qtcreatorcdbext/cdb_detect.pri create mode 100644 src/libs/qtcreatorcdbext/common.cpp create mode 100644 src/libs/qtcreatorcdbext/common.h create mode 100644 src/libs/qtcreatorcdbext/eventcallback.cpp create mode 100644 src/libs/qtcreatorcdbext/eventcallback.h create mode 100644 src/libs/qtcreatorcdbext/extensioncontext.cpp create mode 100644 src/libs/qtcreatorcdbext/extensioncontext.h create mode 100644 src/libs/qtcreatorcdbext/gdbmihelpers.cpp create mode 100644 src/libs/qtcreatorcdbext/gdbmihelpers.h create mode 100644 src/libs/qtcreatorcdbext/iinterfacepointer.h create mode 100644 src/libs/qtcreatorcdbext/outputcallback.cpp create mode 100644 src/libs/qtcreatorcdbext/outputcallback.h create mode 100644 src/libs/qtcreatorcdbext/qtcreatorcdbext.pro create mode 100644 src/libs/qtcreatorcdbext/qtcreatorcdbext_build.pro create mode 100644 src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp create mode 100644 src/libs/qtcreatorcdbext/stringutils.cpp create mode 100644 src/libs/qtcreatorcdbext/stringutils.h create mode 100644 src/libs/qtcreatorcdbext/symbolgroup.cpp create mode 100644 src/libs/qtcreatorcdbext/symbolgroup.h create mode 100644 src/libs/qtcreatorcdbext/test32.bat create mode 100644 src/libs/qtcreatorcdbext/test64.bat diff --git a/src/libs/libs.pro b/src/libs/libs.pro index 570785f8344..fce7bc63f8b 100644 --- a/src/libs/libs.pro +++ b/src/libs/libs.pro @@ -14,3 +14,5 @@ SUBDIRS = \ qmleditorwidgets \ symbianutils \ 3rdparty + +win32:SUBDIRS += qtcreatorcdbext diff --git a/src/libs/qtcreatorcdbext/base64.cpp b/src/libs/qtcreatorcdbext/base64.cpp new file mode 100644 index 00000000000..d6121daafc0 --- /dev/null +++ b/src/libs/qtcreatorcdbext/base64.cpp @@ -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; + } + } +} diff --git a/src/libs/qtcreatorcdbext/base64.h b/src/libs/qtcreatorcdbext/base64.h new file mode 100644 index 00000000000..bfafc2b4117 --- /dev/null +++ b/src/libs/qtcreatorcdbext/base64.h @@ -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 diff --git a/src/libs/qtcreatorcdbext/cdb_detect.pri b/src/libs/qtcreatorcdbext/cdb_detect.pri new file mode 100644 index 00000000000..da024eb6008 --- /dev/null +++ b/src/libs/qtcreatorcdbext/cdb_detect.pri @@ -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" + } +} diff --git a/src/libs/qtcreatorcdbext/common.cpp b/src/libs/qtcreatorcdbext/common.cpp new file mode 100644 index 00000000000..309a202b2d7 --- /dev/null +++ b/src/libs/qtcreatorcdbext/common.cpp @@ -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; +} diff --git a/src/libs/qtcreatorcdbext/common.h b/src/libs/qtcreatorcdbext/common.h new file mode 100644 index 00000000000..198cb1eb7a5 --- /dev/null +++ b/src/libs/qtcreatorcdbext/common.h @@ -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 diff --git a/src/libs/qtcreatorcdbext/eventcallback.cpp b/src/libs/qtcreatorcdbext/eventcallback.cpp new file mode 100644 index 00000000000..f05f66d2adc --- /dev/null +++ b/src/libs/qtcreatorcdbext/eventcallback.cpp @@ -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; +} diff --git a/src/libs/qtcreatorcdbext/eventcallback.h b/src/libs/qtcreatorcdbext/eventcallback.h new file mode 100644 index 00000000000..579bb7c138f --- /dev/null +++ b/src/libs/qtcreatorcdbext/eventcallback.h @@ -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 diff --git a/src/libs/qtcreatorcdbext/extensioncontext.cpp b/src/libs/qtcreatorcdbext/extensioncontext.cpp new file mode 100644 index 00000000000..b393752d89a --- /dev/null +++ b/src/libs/qtcreatorcdbext/extensioncontext.cpp @@ -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; +} diff --git a/src/libs/qtcreatorcdbext/extensioncontext.h b/src/libs/qtcreatorcdbext/extensioncontext.h new file mode 100644 index 00000000000..79933dffece --- /dev/null +++ b/src/libs/qtcreatorcdbext/extensioncontext.h @@ -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 diff --git a/src/libs/qtcreatorcdbext/gdbmihelpers.cpp b/src/libs/qtcreatorcdbext/gdbmihelpers.cpp new file mode 100644 index 00000000000..aff59c3a0e3 --- /dev/null +++ b/src/libs/qtcreatorcdbext/gdbmihelpers.cpp @@ -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(); +} diff --git a/src/libs/qtcreatorcdbext/gdbmihelpers.h b/src/libs/qtcreatorcdbext/gdbmihelpers.h new file mode 100644 index 00000000000..73622668cc4 --- /dev/null +++ b/src/libs/qtcreatorcdbext/gdbmihelpers.h @@ -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 diff --git a/src/libs/qtcreatorcdbext/iinterfacepointer.h b/src/libs/qtcreatorcdbext/iinterfacepointer.h new file mode 100644 index 00000000000..558d27b2dca --- /dev/null +++ b/src/libs/qtcreatorcdbext/iinterfacepointer.h @@ -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 diff --git a/src/libs/qtcreatorcdbext/outputcallback.cpp b/src/libs/qtcreatorcdbext/outputcallback.cpp new file mode 100644 index 00000000000..026d2ab3a1e --- /dev/null +++ b/src/libs/qtcreatorcdbext/outputcallback.cpp @@ -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; +} diff --git a/src/libs/qtcreatorcdbext/outputcallback.h b/src/libs/qtcreatorcdbext/outputcallback.h new file mode 100644 index 00000000000..76f73ad3c41 --- /dev/null +++ b/src/libs/qtcreatorcdbext/outputcallback.h @@ -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 diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbext.pro b/src/libs/qtcreatorcdbext/qtcreatorcdbext.pro new file mode 100644 index 00000000000..5012a427d38 --- /dev/null +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbext.pro @@ -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 +} diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbext_build.pro b/src/libs/qtcreatorcdbext/qtcreatorcdbext_build.pro new file mode 100644 index 00000000000..9733de7b5b2 --- /dev/null +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbext_build.pro @@ -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 + diff --git a/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp new file mode 100644 index 00000000000..8212b32620e --- /dev/null +++ b/src/libs/qtcreatorcdbext/qtcreatorcdbextension.cpp @@ -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; +} diff --git a/src/libs/qtcreatorcdbext/stringutils.cpp b/src/libs/qtcreatorcdbext/stringutils.cpp new file mode 100644 index 00000000000..ed0c7309fda --- /dev/null +++ b/src/libs/qtcreatorcdbext/stringutils.cpp @@ -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 << '}'; +} diff --git a/src/libs/qtcreatorcdbext/stringutils.h b/src/libs/qtcreatorcdbext/stringutils.h new file mode 100644 index 00000000000..cb809aeb918 --- /dev/null +++ b/src/libs/qtcreatorcdbext/stringutils.h @@ -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 diff --git a/src/libs/qtcreatorcdbext/symbolgroup.cpp b/src/libs/qtcreatorcdbext/symbolgroup.cpp new file mode 100644 index 00000000000..ba33d804d83 --- /dev/null +++ b/src/libs/qtcreatorcdbext/symbolgroup.cpp @@ -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); +} diff --git a/src/libs/qtcreatorcdbext/symbolgroup.h b/src/libs/qtcreatorcdbext/symbolgroup.h new file mode 100644 index 00000000000..de9843ea3df --- /dev/null +++ b/src/libs/qtcreatorcdbext/symbolgroup.h @@ -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 diff --git a/src/libs/qtcreatorcdbext/test32.bat b/src/libs/qtcreatorcdbext/test32.bat new file mode 100644 index 00000000000..00693f786df --- /dev/null +++ b/src/libs/qtcreatorcdbext/test32.bat @@ -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% diff --git a/src/libs/qtcreatorcdbext/test64.bat b/src/libs/qtcreatorcdbext/test64.bat new file mode 100644 index 00000000000..0185785f178 --- /dev/null +++ b/src/libs/qtcreatorcdbext/test64.bat @@ -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% -- GitLab