Files
qt-creator/src/plugins/debugger/cdb/coreengine.cpp

1160 lines
41 KiB
C++
Raw Normal View History

/**************************************************************************
**
** This file is part of Qt Creator
**
2010-03-05 11:25:49 +01:00
** 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 "coreengine.h"
#include "debugeventcallbackbase.h"
#include "debugoutputbase.h"
#include "symbolgroupcontext.h"
#include <utils/winutils.h>
#include <utils/abstractprocess.h>
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include <QtCore/QLibrary>
#include <QtCore/QDebug>
#define DBGHELP_TRANSLATE_TCHAR
#include <inc/Dbghelp.h>
static const char *dbgHelpDllC = "dbghelp";
static const char *dbgEngineDllC = "dbgeng";
static const char *debugCreateFuncC = "DebugCreate";
enum { debug = 0 };
static inline QString msgLibLoadFailed(const QString &lib, const QString &why)
{
return QCoreApplication::translate("Debugger::Cdb",
"Unable to load the debugger engine library '%1': %2").
arg(lib, why);
}
static inline ULONG getInterruptTimeOutSecs(CIDebugControl *ctl)
{
ULONG rc = 0;
ctl->GetInterruptTimeout(&rc);
return rc;
}
namespace CdbCore {
QString msgDebugEngineComResult(HRESULT hr)
{
switch (hr) {
case S_OK:
return QLatin1String("S_OK");
case S_FALSE:
return QLatin1String("S_FALSE");
case E_FAIL:
break;
case E_INVALIDARG:
return QLatin1String("E_INVALIDARG");
case E_NOINTERFACE:
return QLatin1String("E_NOINTERFACE");
case E_OUTOFMEMORY:
return QLatin1String("E_OUTOFMEMORY");
case E_UNEXPECTED:
return QLatin1String("E_UNEXPECTED");
case E_NOTIMPL:
return QLatin1String("E_NOTIMPL");
}
if (hr == HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED))
return QLatin1String("ERROR_ACCESS_DENIED");;
if (hr == HRESULT_FROM_NT(STATUS_CONTROL_C_EXIT))
return QLatin1String("STATUS_CONTROL_C_EXIT");
return QLatin1String("E_FAIL ") + Utils::winErrorMessage(HRESULT_CODE(hr));
}
QString msgComFailed(const char *func, HRESULT hr)
{
return QString::fromLatin1("%1 failed: %2").arg(QLatin1String(func), msgDebugEngineComResult(hr));
}
QString msgDebuggerCommandFailed(const QString &command, HRESULT hr)
{
return QString::fromLatin1("Unable to execute '%1': %2").arg(command, msgDebugEngineComResult(hr));
}
const char *msgExecutionStatusString(ULONG executionStatus)
{
switch (executionStatus) {
case DEBUG_STATUS_NO_CHANGE:
return "DEBUG_STATUS_NO_CHANGE";
case DEBUG_STATUS_GO:
return "DEBUG_STATUS_GO";
case DEBUG_STATUS_GO_HANDLED:
return "DEBUG_STATUS_GO_HANDLED";
case DEBUG_STATUS_GO_NOT_HANDLED:
return "DEBUG_STATUS_GO_NOT_HANDLED";
case DEBUG_STATUS_STEP_OVER:
return "DEBUG_STATUS_STEP_OVER";
case DEBUG_STATUS_STEP_INTO:
return "DEBUG_STATUS_STEP_INTO";
case DEBUG_STATUS_BREAK:
return "DEBUG_STATUS_BREAK";
case DEBUG_STATUS_NO_DEBUGGEE:
return "DEBUG_STATUS_NO_DEBUGGEE";
case DEBUG_STATUS_STEP_BRANCH:
return "DEBUG_STATUS_STEP_BRANCH";
case DEBUG_STATUS_IGNORE_EVENT:
return "DEBUG_STATUS_IGNORE_EVENT";
case DEBUG_STATUS_RESTART_REQUESTED:
return "DEBUG_STATUS_RESTART_REQUESTED";
case DEBUG_STATUS_REVERSE_GO:
return "DEBUG_STATUS_REVERSE_GO";
case DEBUG_STATUS_REVERSE_STEP_BRANCH:
return "DEBUG_STATUS_REVERSE_STEP_BRANCH";
case DEBUG_STATUS_REVERSE_STEP_OVER:
return "DEBUG_STATUS_REVERSE_STEP_OVER";
case DEBUG_STATUS_REVERSE_STEP_INTO:
return "DEBUG_STATUS_REVERSE_STEP_INTO";
default:
break;
}
return "<Unknown execution status>";
}
static const ULONG defaultSymbolOptions = SYMOPT_CASE_INSENSITIVE | SYMOPT_UNDNAME |
SYMOPT_LOAD_LINES | SYMOPT_OMAP_FIND_NEAREST |
SYMOPT_AUTO_PUBLICS;
// ComInterfaces
ComInterfaces::ComInterfaces() :
debugClient(0),
debugControl(0),
debugSystemObjects(0),
debugSymbols(0),
debugRegisters(0),
debugDataSpaces(0)
{
}
// Thin wrapper around the 'DBEng' debugger engine shared library
// which is loaded at runtime.
class DebuggerEngineLibrary
{
public:
DebuggerEngineLibrary();
bool init(const QString &path, QString *dbgEngDLL, QString *errorMessage);
inline HRESULT debugCreate(REFIID interfaceId, PVOID *interfaceHandle) const
{ return m_debugCreate(interfaceId, interfaceHandle); }
private:
// The exported functions of the library
typedef HRESULT (STDAPICALLTYPE *DebugCreateFunction)(REFIID, PVOID *);
DebugCreateFunction m_debugCreate;
};
// --------- DebuggerEngineLibrary
DebuggerEngineLibrary::DebuggerEngineLibrary() :
m_debugCreate(0)
{
}
// Build a lib name as "Path\x.dll"
static inline QString libPath(const QString &libName, const QString &path = QString())
{
QString rc = path;
if (!rc.isEmpty())
rc += QDir::separator();
rc += libName;
rc += QLatin1String(".dll");
return rc;
}
bool DebuggerEngineLibrary::init(const QString &path,
QString *dbgEngDLL,
QString *errorMessage)
{
// Load the dependent help lib first
const QString helpLibPath = libPath(QLatin1String(dbgHelpDllC), path);
QLibrary helpLib(helpLibPath, 0);
if (!helpLib.isLoaded() && !helpLib.load()) {
*errorMessage = msgLibLoadFailed(helpLibPath, helpLib.errorString());
return false;
}
// Load dbgeng lib
const QString engineLibPath = libPath(QLatin1String(dbgEngineDllC), path);
QLibrary lib(engineLibPath, 0);
if (!lib.isLoaded() && !lib.load()) {
*errorMessage = msgLibLoadFailed(engineLibPath, lib.errorString());
return false;
}
*dbgEngDLL = engineLibPath;
// Locate symbols
void *createFunc = lib.resolve(debugCreateFuncC);
if (!createFunc) {
*errorMessage = QCoreApplication::translate("Debugger::Cdb", "Unable to resolve '%1' in the debugger engine library '%2'").
arg(QLatin1String(debugCreateFuncC), QLatin1String(dbgEngineDllC));
return false;
}
m_debugCreate = static_cast<DebugCreateFunction>(createFunc);
return true;
}
// ------ Engine
CoreEngine::CoreEngine(QObject *parent) :
QObject(parent),
m_watchTimer(-1),
m_moduleCount(0),
m_lastTimerModuleCount(0),
m_modulesLoadedEmitted(true)
{
}
CoreEngine::~CoreEngine()
{
if (m_cif.debugClient) {
m_cif.debugClient->SetOutputCallbacksWide(0);
m_cif.debugClient->SetEventCallbacksWide(0);
m_cif.debugClient->Release();
}
if (m_cif.debugControl)
m_cif.debugControl->Release();
if (m_cif.debugSystemObjects)
m_cif.debugSystemObjects->Release();
if (m_cif.debugSymbols)
m_cif.debugSymbols->Release();
if (m_cif.debugRegisters)
m_cif.debugRegisters->Release();
if (m_cif.debugDataSpaces)
m_cif.debugDataSpaces->Release();
}
bool CoreEngine::init(const QString &dllEnginePath, QString *errorMessage)
{
enum { bufLen = 10240 };
// Load the DLL
DebuggerEngineLibrary lib;
if (!lib.init(dllEnginePath, &m_dbengDLL, errorMessage))
return false;
// Initialize the COM interfaces
HRESULT hr;
hr = lib.debugCreate( __uuidof(IDebugClient5), reinterpret_cast<void**>(&m_cif.debugClient));
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Creation of IDebugClient5 failed: %1").arg(msgDebugEngineComResult(hr));
return false;
}
hr = lib.debugCreate( __uuidof(IDebugControl4), reinterpret_cast<void**>(&m_cif.debugControl));
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Creation of IDebugControl4 failed: %1").arg(msgDebugEngineComResult(hr));
return false;
}
hr = lib.debugCreate( __uuidof(IDebugSystemObjects4), reinterpret_cast<void**>(&m_cif.debugSystemObjects));
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Creation of IDebugSystemObjects4 failed: %1").arg(msgDebugEngineComResult(hr));
return false;
}
hr = lib.debugCreate( __uuidof(IDebugSymbols3), reinterpret_cast<void**>(&m_cif.debugSymbols));
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Creation of IDebugSymbols3 failed: %1").arg(msgDebugEngineComResult(hr));
return false;
}
hr = m_cif.debugSymbols->SetSymbolOptions(defaultSymbolOptions);
if (FAILED(hr)) {
*errorMessage = msgComFailed("SetSymbolOptions", hr);
return false;
}
WCHAR buf[bufLen];
hr = m_cif.debugSymbols->GetImagePathWide(buf, bufLen, 0);
if (FAILED(hr)) {
*errorMessage = msgComFailed("GetImagePathWide", hr);
return false;
}
m_baseImagePath = QString::fromWCharArray(buf);
hr = lib.debugCreate( __uuidof(IDebugRegisters2), reinterpret_cast<void**>(&m_cif.debugRegisters));
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Creation of IDebugRegisters2 failed: %1").arg(msgDebugEngineComResult(hr));
return false;
}
hr = lib.debugCreate( __uuidof(IDebugDataSpaces4), reinterpret_cast<void**>(&m_cif.debugDataSpaces));
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Creation of IDebugDataSpaces4 failed: %1").arg(msgDebugEngineComResult(hr));
return false;
}
if (debug)
qDebug() << QString::fromLatin1("CDB Initialization succeeded, interrupt time out %1s.").arg(getInterruptTimeOutSecs(m_cif.debugControl));
return true;
}
CoreEngine::DebugOutputBasePtr
CoreEngine::setDebugOutput(const DebugOutputBasePtr &o)
{
const CoreEngine::DebugOutputBasePtr old = m_debugOutput;
m_debugOutput = o;
m_cif.debugClient->SetOutputCallbacksWide(m_debugOutput.data());
return old;
}
CoreEngine::DebugEventCallbackBasePtr
CoreEngine::setDebugEventCallback(const DebugEventCallbackBasePtr &e)
{
// Keep the module count.
const unsigned oldModuleCount = moduleCount();
const CoreEngine::DebugEventCallbackBasePtr old = m_debugEventCallback;
m_debugEventCallback = e;
m_cif.debugClient->SetEventCallbacksWide(m_debugEventCallback.data());
setModuleCount(oldModuleCount);
return old;
}
void CoreEngine::startWatchTimer()
{
if (debug)
qDebug() << Q_FUNC_INFO;
if (m_watchTimer == -1)
m_watchTimer = startTimer(0);
}
void CoreEngine::killWatchTimer()
{
if (debug)
qDebug() << Q_FUNC_INFO;
if (m_watchTimer != -1) {
killTimer(m_watchTimer);
m_watchTimer = -1;
}
}
HRESULT CoreEngine::waitForEvent(int timeOutMS)
{
const HRESULT hr = m_cif.debugControl->WaitForEvent(0, timeOutMS);
if (debug)
if (debug > 1 || hr != S_FALSE)
qDebug() << Q_FUNC_INFO << "WaitForEvent" << timeOutMS << msgDebugEngineComResult(hr);
return hr;
}
void CoreEngine::timerEvent(QTimerEvent* te)
{
// Fetch away the debug events and notify if debuggee
// stops. Note that IDebugEventCallback does not
// cover all cases of a debuggee stopping execution
// (such as step over,etc).
if (te->timerId() != m_watchTimer)
return;
switch (waitForEvent(1)) {
case S_OK:
killWatchTimer();
emit watchTimerDebugEvent();
break;
case S_FALSE:
// Detect startup (all modules loaded) if the module
// count no longer changes in a time-out.
if (!m_modulesLoadedEmitted) {
const int newModuleCount = moduleCount();
if (newModuleCount && newModuleCount == m_lastTimerModuleCount) {
m_modulesLoadedEmitted = true;
emit modulesLoaded();
} else {
m_lastTimerModuleCount = newModuleCount;
}
}
break;
case E_PENDING:
case E_FAIL:
break;
case E_UNEXPECTED: // Occurs on ExitProcess.
killWatchTimer();
break;
}
}
void CoreEngine::resetModuleLoadTimer()
{
m_lastTimerModuleCount = 0;
setModuleCount(0);
m_modulesLoadedEmitted = false;
}
bool CoreEngine::startDebuggerWithExecutable(const QString &workingDirectory,
const QString &filename,
const QStringList &args,
const QStringList &envList,
QString *errorMessage)
{
resetModuleLoadTimer();
DEBUG_CREATE_PROCESS_OPTIONS dbgopts;
memset(&dbgopts, 0, sizeof(dbgopts));
dbgopts.CreateFlags = DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS;
// Set image path
const QFileInfo fi(filename);
QString imagePath = QDir::toNativeSeparators(fi.absolutePath());
if (!m_baseImagePath.isEmpty()) {
imagePath += QLatin1Char(';');
imagePath += m_baseImagePath;
}
HRESULT hr = m_cif.debugSymbols->SetImagePathWide(reinterpret_cast<PCWSTR>(imagePath.utf16()));
if (FAILED(hr)) {
*errorMessage = tr("Unable to set the image path to %1: %2").arg(imagePath, msgComFailed("SetImagePathWide", hr));
return false;
}
if (debug)
qDebug() << Q_FUNC_INFO <<'\n' << filename << imagePath;
const QString cmd = Utils::AbstractProcess::createWinCommandline(filename, args);
if (debug)
qDebug() << "Starting " << cmd;
PCWSTR env = 0;
QByteArray envData;
if (!envList.empty()) {
envData = Utils::AbstractProcess::createWinEnvironment(Utils::AbstractProcess::fixWinEnvironment(envList));
env = reinterpret_cast<PCWSTR>(envData.data());
}
// The working directory cannot be empty.
PCWSTR workingDirC = 0;
const QString workingDirN = workingDirectory.isEmpty() ? QString() : QDir::toNativeSeparators(workingDirectory);
if (!workingDirN.isEmpty())
workingDirC = (PCWSTR)workingDirN.utf16();
hr = m_cif.debugClient->CreateProcess2Wide(NULL,
reinterpret_cast<PWSTR>(const_cast<ushort *>(cmd.utf16())),
&dbgopts, sizeof(dbgopts),
workingDirC, env);
if (FAILED(hr)) {
*errorMessage = tr("Unable to create a process '%1': %2").arg(cmd, msgDebugEngineComResult(hr));
return false;
}
return true;
}
bool CoreEngine::startAttachDebugger(qint64 pid,
bool suppressInitialBreakPoint,
QString *errorMessage)
{
resetModuleLoadTimer();
// Need to attach invasively, otherwise, no notification signals
// for for CreateProcess/ExitProcess occur.
// Initial breakpoint occur:
// 1) Desired: When attaching to a crashed process
// 2) Undesired: When starting up a console process, in conjunction
// with the 32bit Wow-engine
// As of version 6.11, the flag only affects 1). 2) Still needs to be suppressed
// by lookup at the state of the application (startup trap). However,
// there is no startup trap when attaching to a process that has been
// running for a while. (see notifyException).
ULONG flags = DEBUG_ATTACH_INVASIVE_RESUME_PROCESS;
if (suppressInitialBreakPoint)
flags |= DEBUG_ATTACH_INVASIVE_NO_INITIAL_BREAK;
const HRESULT hr = m_cif.debugClient->AttachProcess(NULL, pid, flags);
if (debug)
qDebug() << "Attaching to " << pid << " using flags" << flags << " returns " << hr;
if (FAILED(hr)) {
*errorMessage = tr("Attaching to a process failed for process id %1: %2").arg(pid).arg(msgDebugEngineComResult(hr));
return false;
}
return true;
}
static inline QString pathString(const QStringList &s)
{ return s.join(QString(QLatin1Char(';'))); }
QStringList CoreEngine::sourcePaths() const
{
WCHAR wszBuf[MAX_PATH];
if (SUCCEEDED(m_cif.debugSymbols->GetSourcePathWide(wszBuf, MAX_PATH, 0)))
return QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)).split(QLatin1Char(';'));
return QStringList();
}
bool CoreEngine::setSourcePaths(const QStringList &s, QString *errorMessage)
{
const HRESULT hr = m_cif.debugSymbols->SetSourcePathWide(reinterpret_cast<PCWSTR>(pathString(s).utf16()));
if (FAILED(hr)) {
if (errorMessage)
*errorMessage = msgComFailed("SetSourcePathWide", hr);
return false;
}
return true;
}
QStringList CoreEngine::symbolPaths() const
{
WCHAR wszBuf[MAX_PATH];
if (SUCCEEDED(m_cif.debugSymbols->GetSymbolPathWide(wszBuf, MAX_PATH, 0)))
return QString::fromUtf16(reinterpret_cast<const ushort *>(wszBuf)).split(QLatin1Char(';'));
return QStringList();
}
bool CoreEngine::setSymbolPaths(const QStringList &s, QString *errorMessage)
{
const HRESULT hr = m_cif.debugSymbols->SetSymbolPathWide(reinterpret_cast<PCWSTR>(pathString(s).utf16()));
if (FAILED(hr)) {
if (errorMessage)
*errorMessage = msgComFailed("SetSymbolPathWide", hr);
return false;
}
return true;
}
bool CoreEngine::isVerboseSymbolLoading() const
{
ULONG opts;
const HRESULT hr = m_cif.debugSymbols->GetSymbolOptions(&opts);
return SUCCEEDED(hr) && (opts & SYMOPT_DEBUG);
}
bool CoreEngine::setVerboseSymbolLoading(bool newValue)
{
ULONG opts;
HRESULT hr = m_cif.debugSymbols->GetSymbolOptions(&opts);
if (FAILED(hr)) {
qWarning("%s", qPrintable(msgComFailed("GetSymbolOptions", hr)));
return false;
}
const bool isVerbose = (opts & SYMOPT_DEBUG);
if (isVerbose == newValue)
return true;
if (newValue) {
opts |= SYMOPT_DEBUG;
} else {
opts &= ~SYMOPT_DEBUG;
}
hr = m_cif.debugSymbols->SetSymbolOptions(opts);
if (FAILED(hr)) {
qWarning("%s", qPrintable(msgComFailed("SetSymbolOptions", hr)));
return false;
}
return true;
}
bool CoreEngine::executeDebuggerCommand(const QString &command, QString *errorMessage)
{
// output to all clients, else we do not see anything
const HRESULT hr = m_cif.debugControl->ExecuteWide(DEBUG_OUTCTL_ALL_CLIENTS, reinterpret_cast<PCWSTR>(command.utf16()), 0);
if (debug)
qDebug() << "executeDebuggerCommand" << command << SUCCEEDED(hr);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Unable to execute '%1': %2").
arg(command, msgDebugEngineComResult(hr));
return false;
}
return true;
}
// Those should not fail
CoreEngine::ExpressionSyntax CoreEngine::expressionSyntax() const
{
ULONG expressionSyntax = DEBUG_EXPR_MASM;
const HRESULT hr = m_cif.debugControl->GetExpressionSyntax(&expressionSyntax);
if (FAILED(hr))
qWarning("Unable to retrieve expression syntax: %s", qPrintable(msgComFailed("GetExpressionSyntax", hr)));
return expressionSyntax == DEBUG_EXPR_MASM ? AssemblerExpressionSyntax : CppExpressionSyntax;
}
CoreEngine::ExpressionSyntax CoreEngine::setExpressionSyntax(ExpressionSyntax es)
{
const ExpressionSyntax old = expressionSyntax();
if (old != es) {
if (debug)
qDebug() << "Setting expression syntax" << es;
const HRESULT hr = m_cif.debugControl->SetExpressionSyntax(es == AssemblerExpressionSyntax ? DEBUG_EXPR_MASM : DEBUG_EXPR_CPLUSPLUS);
if (FAILED(hr))
qWarning("Unable to set expression syntax: %s", qPrintable(msgComFailed("SetExpressionSyntax", hr)));
}
return old;
}
CoreEngine::CodeLevel CoreEngine::codeLevel() const
{
ULONG currentCodeLevel = DEBUG_LEVEL_ASSEMBLY;
HRESULT hr = m_cif.debugControl->GetCodeLevel(&currentCodeLevel);
if (FAILED(hr)) {
qWarning("Cannot determine code level: %s", qPrintable(msgComFailed("GetCodeLevel", hr)));
}
return currentCodeLevel == DEBUG_LEVEL_ASSEMBLY ? CodeLevelAssembly : CodeLevelSource;
}
CoreEngine::CodeLevel CoreEngine::setCodeLevel(CodeLevel cl)
{
const CodeLevel old = codeLevel();
if (old != cl) {
if (debug)
qDebug() << "Setting code level" << cl;
const HRESULT hr = m_cif.debugControl->SetCodeLevel(cl == CodeLevelAssembly ? DEBUG_LEVEL_ASSEMBLY : DEBUG_LEVEL_SOURCE);
if (FAILED(hr))
qWarning("Unable to set code level: %s", qPrintable(msgComFailed("SetCodeLevel", hr)));
}
return old;
}
bool CoreEngine::evaluateExpression(const QString &expression,
QString *value,
QString *type,
QString *errorMessage)
{
DEBUG_VALUE debugValue;
if (!evaluateExpression(expression, &debugValue, errorMessage))
return false;
*value = debugValueToString(debugValue, type, 10, m_cif.debugControl);
return true;
}
bool CoreEngine::evaluateExpression(const QString &expression,
DEBUG_VALUE *debugValue,
QString *errorMessage)
{
if (debug > 1)
qDebug() << Q_FUNC_INFO << expression;
memset(debugValue, 0, sizeof(DEBUG_VALUE));
// Use CPP syntax, original syntax must be restored, else setting breakpoints will fail.
SyntaxSetter syntaxSetter(this, CppExpressionSyntax);
Q_UNUSED(syntaxSetter)
ULONG errorPosition = 0;
const HRESULT hr = m_cif.debugControl->EvaluateWide(reinterpret_cast<PCWSTR>(expression.utf16()),
DEBUG_VALUE_INVALID, debugValue,
&errorPosition);
if (FAILED(hr)) {
if (HRESULT_CODE(hr) == 517) {
*errorMessage = QString::fromLatin1("Unable to evaluate '%1': Expression out of scope.").
arg(expression);
} else {
*errorMessage = QString::fromLatin1("Unable to evaluate '%1': Error at %2: %3").
arg(expression).arg(errorPosition).arg(msgDebugEngineComResult(hr));
}
return false;
}
return true;
}
ULONG CoreEngine::executionStatus() const
{
ULONG ex = DEBUG_STATUS_NO_CHANGE;
const HRESULT hr = m_cif.debugControl->GetExecutionStatus(&ex);
if (FAILED(hr))
qWarning("Cannot determine execution status: %s", qPrintable(msgComFailed("GetExecutionStatus", hr)));
return ex;
}
bool CoreEngine::setExecutionStatus(ULONG ex, QString *errorMessage)
{
const HRESULT hr = m_cif.debugControl->SetExecutionStatus(ex);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Cannot set execution status to %1: %2")
.arg(QLatin1String(msgExecutionStatusString(ex)), msgComFailed("SetExecutionStatus", hr));
return false;
}
return true;
}
static inline void appendError(const QString &what, QString *appendableErrorMessage)
{
if (!appendableErrorMessage->isEmpty())
appendableErrorMessage->append(QLatin1Char('\n'));
appendableErrorMessage->append(what);
}
bool CoreEngine::detachCurrentProcess(QString *appendableErrorMessage)
{
const HRESULT hr = m_cif.debugClient->DetachCurrentProcess();
if (FAILED(hr)) {
appendError(msgComFailed("DetachCurrentProcess", hr), appendableErrorMessage);
return false;
}
return true;
}
bool CoreEngine::terminateCurrentProcess(QString *appendableErrorMessage)
{
const HRESULT hr = m_cif.debugClient->TerminateCurrentProcess();
if (FAILED(hr)) {
appendError(msgComFailed("TerminateCurrentProcess", hr), appendableErrorMessage);
return false;
}
return true;
}
bool CoreEngine::terminateProcesses(QString *appendableErrorMessage)
{
const HRESULT hr = m_cif.debugClient->TerminateProcesses();
if (FAILED(hr)) {
appendError(msgComFailed("TerminateProcesses", hr), appendableErrorMessage);
return false;
}
return true;
}
bool CoreEngine::endSession(QString *appendableErrorMessage)
{
const HRESULT hr = m_cif.debugClient->EndSession(DEBUG_END_PASSIVE);
if (FAILED(hr)) {
appendError(msgComFailed("EndSession", hr), appendableErrorMessage);
return false;
}
return true;
}
bool CoreEngine::allocDebuggeeMemory(int size, ULONG64 *addressPtr, QString *errorMessage)
{
*addressPtr = 0;
const QString allocCmd = QLatin1String(".dvalloc ") + QString::number(size);
QSharedPointer<StringOutputHandler> outputHandler(new StringOutputHandler);
OutputRedirector redir(this, outputHandler);
Q_UNUSED(redir)
if (!executeDebuggerCommand(allocCmd, errorMessage))
return false;
// "Allocated 1000 bytes starting at 003a0000" or
// "Allocated 1000 bytes starting at 00000000'000023ab" (64bit) / hopefully never localized
const QString output = outputHandler->result().trimmed();
const int lastBlank = output.lastIndexOf(QLatin1Char(' '));
if (lastBlank != -1) {
const QString hexNumberS = QLatin1String("0x") + output.mid(lastBlank + 1);
quint64 address;
if (SymbolGroupContext::getUnsignedHexValue(hexNumberS, &address)) {
*addressPtr = address;
return true;
}
} // blank
*errorMessage = QString::fromLatin1("Failed to parse output '%1'").arg(output);
return false;
}
// Alloc an AscII string in debuggee
bool CoreEngine::createDebuggeeAscIIString(const QString &s,
ULONG64 *address,
QString *errorMessage)
{
QByteArray sAsciiData = s.toLocal8Bit();
sAsciiData += '\0';
if (!allocDebuggeeMemory(sAsciiData.size(), address, errorMessage))
return false;
const HRESULT hr = m_cif.debugDataSpaces->WriteVirtual(*address, sAsciiData.data(), sAsciiData.size(), 0);
if (FAILED(hr)) {
*errorMessage= msgComFailed("WriteVirtual", hr);
return false;
}
return true;
}
// Write to debuggee memory in chunks
bool CoreEngine::writeToDebuggee(const QByteArray &buffer, quint64 address, QString *errorMessage)
{
char *ptr = const_cast<char*>(buffer.data());
ULONG bytesToWrite = buffer.size();
while (bytesToWrite > 0) {
ULONG bytesWritten = 0;
const HRESULT hr = m_cif.debugDataSpaces->WriteVirtual(address, ptr, bytesToWrite, &bytesWritten);
if (FAILED(hr)) {
*errorMessage = msgComFailed("WriteVirtual", hr);
return false;
}
bytesToWrite -= bytesWritten;
ptr += bytesWritten;
}
return true;
}
bool CoreEngine::dissassemble(ULONG64 offset,
unsigned long beforeLines,
unsigned long afterLines,
QString *target,
QString *errorMessage)
{
const ULONG flags = DEBUG_DISASM_MATCHING_SYMBOLS|DEBUG_DISASM_SOURCE_LINE_NUMBER|DEBUG_DISASM_SOURCE_FILE_NAME;
// Catch the output by temporarily setting another handler.
// We use the method that outputs to the output handler as it
// conveniently provides the 'beforeLines' context (stepping back
// in assembler code). We build a complete string first as line breaks
// may occur in-between messages.
QSharedPointer<StringOutputHandler> outputHandler(new StringOutputHandler);
OutputRedirector redir(this, outputHandler);
// For some reason, we need to output to "all clients"
const HRESULT hr = m_cif.debugControl->OutputDisassemblyLines(DEBUG_OUTCTL_ALL_CLIENTS,
beforeLines, beforeLines + afterLines,
offset, flags, 0, 0, 0, 0);
if (FAILED(hr)) {
*errorMessage= QString::fromLatin1("Unable to dissamble at 0x%1: %2").
arg(offset, 0, 16).arg(msgComFailed("OutputDisassemblyLines", hr));
return false;
}
*target = outputHandler->result();
return true;
}
quint64 CoreEngine::getSourceLineAddress(const QString &file,
int line,
QString *errorMessage) const
{
ULONG64 rc = 0;
const HRESULT hr = m_cif.debugSymbols->GetOffsetByLineWide(line,
(wchar_t*)(file.utf16()),
&rc);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Unable to determine address of %1:%2 : %3").
arg(file).arg(line).arg(msgComFailed("GetOffsetByLine", hr));
return 0;
}
return rc;
}
2010-07-13 11:13:36 +02:00
void CoreEngine::outputVersion()
{
m_cif.debugControl->OutputVersionInformation(DEBUG_OUTCTL_ALL_CLIENTS);
}
bool CoreEngine::autoDetectPath(QString *outPath,
QStringList *checkedDirectories /* = 0 */)
{
// Look for $ProgramFiles/"Debugging Tools For Windows <bit-idy>" and its
// " (x86)", " (x64)" variations. Qt Creator needs 64/32 bit depending
// on how it was built.
#ifdef Q_OS_WIN64
static const char *postFixes[] = {" (x64)", " 64-bit" };
#else
static const char *postFixes[] = { " (x86)", " (x32)" };
#endif
outPath->clear();
const QByteArray programDirB = qgetenv("ProgramFiles");
if (programDirB.isEmpty())
return false;
const QString programDir = QString::fromLocal8Bit(programDirB) + QDir::separator();
const QString installDir = QLatin1String("Debugging Tools For Windows");
QString path = programDir + installDir;
if (checkedDirectories)
checkedDirectories->push_back(path);
if (QFileInfo(path).isDir()) {
*outPath = QDir::toNativeSeparators(path);
return true;
}
const int rootLength = path.size();
for (int i = 0; i < sizeof(postFixes)/sizeof(const char*); i++) {
path.truncate(rootLength);
path += QLatin1String(postFixes[i]);
if (checkedDirectories)
checkedDirectories->push_back(path);
if (QFileInfo(path).isDir()) {
*outPath = QDir::toNativeSeparators(path);
return true;
}
}
return false;
}
bool CoreEngine::debugBreakProcess(HANDLE hProcess, QString *errorMessage)
{
const bool rc = DebugBreakProcess(hProcess);
if (!rc)
*errorMessage = QString::fromLatin1("DebugBreakProcess failed: %1").arg(Utils::winErrorMessage(GetLastError()));
return rc;
}
bool CoreEngine::setInterrupt(QString *errorMessage)
{
const HRESULT hr = interfaces().debugControl->SetInterrupt(DEBUG_INTERRUPT_ACTIVE|DEBUG_INTERRUPT_EXIT);
if (FAILED(hr)) {
*errorMessage = QString::fromLatin1("Unable to interrupt debuggee after %1s: %2").
arg(getInterruptTimeOutSecs(interfaces().debugControl)).arg(msgComFailed("SetInterrupt", hr));
return false;
}
return true;
}
// Module count is normally kept in the event callback.
// Should we have none, we do the book-keeping ourselves.
unsigned CoreEngine::moduleCount() const
{
return m_debugEventCallback.isNull() ? m_moduleCount : m_debugEventCallback->moduleCount();
}
void CoreEngine::setModuleCount(unsigned m)
{
m_moduleCount = m;
if (!m_debugEventCallback.isNull())
m_debugEventCallback->setModuleCount(m);
}
static inline const char *debugFilterDescription(ULONG in)
{
switch (in) {
case DEBUG_FILTER_BREAK:
return "break";
case DEBUG_FILTER_SECOND_CHANCE_BREAK:
return "2nd-chance-break";
case DEBUG_FILTER_OUTPUT:
return "output";
case DEBUG_FILTER_IGNORE:
return "ignore";
default:
break;
}
return "unknown";
}
static inline QString msgCannotChangeExceptionCommands(unsigned long code, const QString &why)
{
return QString::fromLatin1("Cannot change exception commands for 0x%1: %2").
arg(code, 0, 16).arg(why);
}
bool CoreEngine::setBreakOnThrow(bool b, QString *errorMessage)
{
// See eventFilterStatus() for defaults
const unsigned long code = 0xe06d7363;
const unsigned long executionCommand = b ? DEBUG_FILTER_BREAK : DEBUG_FILTER_SECOND_CHANCE_BREAK;
const unsigned long continueCommand = b ? DEBUG_FILTER_BREAK : DEBUG_FILTER_SECOND_CHANCE_BREAK;
return setExceptionCommands(code, executionCommand, continueCommand, errorMessage);
}
bool CoreEngine::setExceptionCommands(ULONG code,
ULONG executionCommand,
ULONG continueCommand,
QString *errorMessage)
{
DEBUG_EXCEPTION_FILTER_PARAMETERS exceptionParameters;
HRESULT hr = m_cif.debugControl->GetExceptionFilterParameters(1, &code, 0, &exceptionParameters);
if (FAILED(hr)) {
*errorMessage = msgCannotChangeExceptionCommands(code, msgComFailed("GetExceptionFilterParameters", hr));
return false;
}
if (exceptionParameters.ExecutionOption == executionCommand
&& exceptionParameters.ContinueOption == continueCommand)
return true;
if (debug)
qDebug("Changing exception commands of 0x%x from %s/%s to %s/%s",
code,
debugFilterDescription(exceptionParameters.ExecutionOption),
debugFilterDescription(exceptionParameters.ContinueOption),
debugFilterDescription(executionCommand),
debugFilterDescription(continueCommand));
exceptionParameters.ExecutionOption = executionCommand;
exceptionParameters.ContinueOption = continueCommand;
hr = m_cif.debugControl->SetExceptionFilterParameters(1, &exceptionParameters);
if (FAILED(hr)) {
*errorMessage = msgCannotChangeExceptionCommands(code, msgComFailed("SetExceptionFilterParameters", hr));
return false;
}
return true;
}
static void formatEventFilter(CIDebugControl *ctl, unsigned long start, unsigned long end,
bool isException,
QTextStream &str)
{
enum { bufSize =2048 };
WCHAR buffer[bufSize];
for (unsigned long i = start; i < end; i++) {
HRESULT hr = ctl->GetEventFilterTextWide(i, buffer, bufSize, 0);
if (SUCCEEDED(hr)) {
ULONG size;
str << "- #" << i << " \"" << QString::fromUtf16(buffer) << '"';
hr = ctl->GetEventFilterCommandWide(i, buffer, bufSize, &size);
if (SUCCEEDED(hr) && size > 1)
str << " command: '" << QString::fromUtf16(buffer) << '\'';
if (isException) {
DEBUG_EXCEPTION_FILTER_PARAMETERS exceptionParameters;
hr = ctl->GetExceptionFilterParameters(1, 0, i, &exceptionParameters);
if (SUCCEEDED(hr)) {
str.setIntegerBase(16);
str << " code: 0x" << exceptionParameters.ExceptionCode;
str.setIntegerBase(10);
str << " execute: '"
<< debugFilterDescription(exceptionParameters.ExecutionOption)
<< "' continue: '" << debugFilterDescription(exceptionParameters.ContinueOption)
<< '\'';
if (exceptionParameters.SecondCommandSize) {
hr = ctl->GetExceptionFilterSecondCommandWide(i, buffer, bufSize, 0);
if (SUCCEEDED(hr))
str << " 2nd-command '" << QString::fromUtf16(buffer) << '\'';
}
}
} // isException
str << '\n';
}
}
}
QString CoreEngine::eventFilterStatus() const
{
ULONG specificEvents, specificExceptions, arbitraryExceptions;
QString rc;
QTextStream str(&rc);
HRESULT hr = m_cif.debugControl->GetNumberEventFilters(&specificEvents, &specificExceptions, &arbitraryExceptions);
if (FAILED(hr))
return QString();
str << "Specific events\n";
formatEventFilter(m_cif.debugControl, 0, specificEvents, false, str);
const ULONG arbitraryExceptionsStart = specificEvents + specificExceptions;
str << "Specific exceptions\n";
formatEventFilter(m_cif.debugControl, specificEvents, arbitraryExceptionsStart, true, str);
str << "Arbitrary exceptions\n";
const ULONG arbitraryExceptionsEnd = arbitraryExceptionsStart + arbitraryExceptions;
formatEventFilter(m_cif.debugControl, arbitraryExceptionsStart, arbitraryExceptionsEnd, true, str);
return rc;
}
// ------------- DEBUG_VALUE formatting helpers
// format an array of integers as "0x323, 0x2322, ..."
template <class Integer>
static QString formatArrayHelper(const Integer *array, int size, int base = 10)
{
QString rc;
const QString hexPrefix = QLatin1String("0x");
const QString separator = QLatin1String(", ");
const bool hex = base == 16;
for (int i = 0; i < size; i++) {
if (i)
rc += separator;
if (hex)
rc += hexPrefix;
rc += QString::number(array[i], base);
}
return rc;
}
QString hexFormatArray(const unsigned short *array, int size)
{
return formatArrayHelper(array, size, 16);
}
// Helper to format an integer with
// a hex prefix in case base = 16
template <class Integer>
inline QString formatInteger(Integer value, int base)
{
QString rc;
if (base == 16)
rc = QLatin1String("0x");
rc += QString::number(value, base);
return rc;
}
QString debugValueToString(const DEBUG_VALUE &dv,
QString *qType /* =0 */,
int integerBase,
CIDebugControl *ctl /* =0 */)
{
switch (dv.Type) {
case DEBUG_VALUE_INT8:
if (qType)
*qType = QLatin1String("char");
return formatInteger(dv.I8, integerBase);
case DEBUG_VALUE_INT16:
if (qType)
*qType = QLatin1String("short");
return formatInteger(static_cast<short>(dv.I16), integerBase);
case DEBUG_VALUE_INT32:
if (qType)
*qType = QLatin1String("long");
return formatInteger(static_cast<long>(dv.I32), integerBase);
case DEBUG_VALUE_INT64:
if (qType)
*qType = QLatin1String("long long");
return formatInteger(static_cast<long long>(dv.I64), integerBase);
case DEBUG_VALUE_FLOAT32:
if (qType)
*qType = QLatin1String("float");
return QString::number(dv.F32);
case DEBUG_VALUE_FLOAT64:
if (qType)
*qType = QLatin1String("double");
return QString::number(dv.F64);
case DEBUG_VALUE_FLOAT80:
case DEBUG_VALUE_FLOAT128: { // Convert to double
DEBUG_VALUE doubleValue;
double d = 0.0;
if (ctl && SUCCEEDED(ctl->CoerceValue(const_cast<DEBUG_VALUE*>(&dv), DEBUG_VALUE_FLOAT64, &doubleValue))) {
d = dv.F64;
} else {
qWarning("Unable to convert DEBUG_VALUE_FLOAT80/DEBUG_VALUE_FLOAT128 values");
}
if (qType)
*qType = QLatin1String(dv.Type == DEBUG_VALUE_FLOAT80 ? "80bit-float" : "128bit-float");
return QString::number(d);
}
case DEBUG_VALUE_VECTOR64: {
if (qType)
*qType = QLatin1String("64bit-vector");
QString rc = QLatin1String("bytes: ");
rc += formatArrayHelper(dv.VI8, 8, integerBase);
rc += QLatin1String(" long: ");
rc += formatArrayHelper(dv.VI32, 2, integerBase);
return rc;
}
case DEBUG_VALUE_VECTOR128: {
if (qType)
*qType = QLatin1String("128bit-vector");
QString rc = QLatin1String("bytes: ");
rc += formatArrayHelper(dv.VI8, 16, integerBase);
rc += QLatin1String(" long long: ");
rc += formatArrayHelper(dv.VI64, 2, integerBase);
return rc;
}
}
if (qType)
*qType = QString::fromLatin1("Unknown type #%1:").arg(dv.Type);
return formatArrayHelper(dv.RawBytes, 24, integerBase);
}
bool debugValueToInteger(const DEBUG_VALUE &dv, qint64 *value)
{
*value = 0;
switch (dv.Type) {
case DEBUG_VALUE_INT8:
*value = dv.I8;
return true;
case DEBUG_VALUE_INT16:
*value = static_cast<short>(dv.I16);
return true;
case DEBUG_VALUE_INT32:
*value = static_cast<long>(dv.I32);
return true;
case DEBUG_VALUE_INT64:
*value = static_cast<long long>(dv.I64);
return true;
default:
break;
}
return false;
}
} // namespace CdbCore