aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/plugins/debugger/cdb/cdbdebugengine.cpp45
-rw-r--r--src/plugins/debugger/cdb/cdbdebugengine_p.h5
-rw-r--r--src/plugins/debugger/cdb/cdbdebugeventcallback.cpp20
-rw-r--r--src/plugins/debugger/cdb/cdbdumperhelper.cpp263
-rw-r--r--src/plugins/debugger/cdb/cdbdumperhelper.h56
-rw-r--r--src/plugins/debugger/cdb/cdbstackframecontext.cpp3
-rw-r--r--src/plugins/debugger/win/sharedlibraryinjector.cpp479
-rw-r--r--src/plugins/debugger/win/sharedlibraryinjector.h109
-rw-r--r--src/plugins/debugger/win/win.pri10
9 files changed, 864 insertions, 126 deletions
diff --git a/src/plugins/debugger/cdb/cdbdebugengine.cpp b/src/plugins/debugger/cdb/cdbdebugengine.cpp
index e79d9dfdb5e..a7d60ad7fe7 100644
--- a/src/plugins/debugger/cdb/cdbdebugengine.cpp
+++ b/src/plugins/debugger/cdb/cdbdebugengine.cpp
@@ -594,21 +594,7 @@ void CdbDebugEnginePrivate::processCreatedAttached(ULONG64 processHandle, ULONG6
// Clear any saved breakpoints and set initial breakpoints
m_engine->executeDebuggerCommand(QLatin1String("bc"));
if (m_debuggerManagerAccess->breakHandler()->hasPendingBreakpoints())
- m_engine->attemptBreakpointSynchronization();
- // At any event, we want a temporary breakpoint at main() to load
- // the dumpers.
- if (m_dumper->state() == CdbDumperHelper::NotLoaded) {
- if (!hasBreakPointAtMain(m_debuggerManagerAccess->breakHandler())) {
- QString errorMessage;
- CDBBreakPoint mainBP;
- // Do not resolve at this point in the rare event someone
- // has main in a module
- mainBP.funcName = QLatin1String("main");
- mainBP.oneShot = true;
- if (!mainBP.add(m_cif.debugControl, &errorMessage))
- m_debuggerManagerAccess->showQtDumperLibraryWarning(errorMessage);
- }
- }
+ m_engine->attemptBreakpointSynchronization();
}
void CdbDebugEngine::processTerminated(unsigned long exitCode)
@@ -1356,6 +1342,12 @@ void CdbDebugEngine::slotConsoleStubTerminated()
exitDebugger();
}
+void CdbDebugEnginePrivate::notifyCrashed()
+{
+ // Cannot go over crash point to execute calls.
+ m_dumper->disable();
+}
+
void CdbDebugEnginePrivate::handleDebugEvent()
{
if (debugCDB)
@@ -1367,18 +1359,9 @@ void CdbDebugEnginePrivate::handleDebugEvent()
switch (mode) {
case BreakEventHandle:
- case BreakEventMain:
- if (mode == BreakEventMain)
- m_dumper->load(m_debuggerManager);
m_debuggerManagerAccess->notifyInferiorStopped();
updateThreadList();
- updateStackTrace();
- break;
- case BreakEventMainLoadDumpers:
- // Temp stop to load dumpers
- m_dumper->load(m_debuggerManager);
- m_engine->startWatchTimer();
- continueInferiorProcess();
+ updateStackTrace();
break;
case BreakEventIgnoreOnce:
m_engine->startWatchTimer();
@@ -1492,6 +1475,7 @@ void CdbDebugEnginePrivate::handleModuleLoad(const QString &name)
{
if (debugCDB>2)
qDebug() << Q_FUNC_INFO << "\n " << name;
+ m_dumper->moduleLoadHook(name, m_hDebuggeeProcess);
updateModules();
}
@@ -1500,17 +1484,6 @@ void CdbDebugEnginePrivate::handleBreakpointEvent(PDEBUG_BREAKPOINT2 pBP)
Q_UNUSED(pBP)
if (debugCDB)
qDebug() << Q_FUNC_INFO;
- // Did we hit main() and did the user want that or is that just
- // our internal BP to load the dumpers?
- QString errorMessage;
- CDBBreakPoint bp;
- if (bp.retrieve(pBP, &errorMessage) && !bp.funcName.isEmpty()) {
- if (bp.funcName == QLatin1String("main") || bp.funcName.endsWith(QLatin1String("!main"))) {
- m_breakEventMode = bp.oneShot ? BreakEventMainLoadDumpers : BreakEventMain;
- }
- if (debugCDB)
- qDebug() << bp << " b-mode=" << m_breakEventMode;
- }
}
void CdbDebugEngine::reloadSourceFiles()
diff --git a/src/plugins/debugger/cdb/cdbdebugengine_p.h b/src/plugins/debugger/cdb/cdbdebugengine_p.h
index 04636a72167..373257bd8f4 100644
--- a/src/plugins/debugger/cdb/cdbdebugengine_p.h
+++ b/src/plugins/debugger/cdb/cdbdebugengine_p.h
@@ -98,10 +98,6 @@ struct CdbDebugEnginePrivate
enum HandleBreakEventMode { // Special modes for break event handler.
BreakEventHandle,
BreakEventIgnoreOnce,
- // We hit main (and the user intended it)
- BreakEventMain,
- // We hit main (and the user did not intend it, just load dumpers)
- BreakEventMainLoadDumpers,
BreakEventSyncBreakPoints,
};
@@ -134,6 +130,7 @@ struct CdbDebugEnginePrivate
bool continueInferior(QString *errorMessage);
bool attemptBreakpointSynchronization(QString *errorMessage);
+ void notifyCrashed();
static bool executeDebuggerCommand(CIDebugControl *ctrl, const QString &command, QString *errorMessage);
static bool evaluateExpression(CIDebugControl *ctrl, const QString &expression, DEBUG_VALUE *v, QString *errorMessage);
diff --git a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp
index 07eab3215e0..42e48b3dc91 100644
--- a/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp
+++ b/src/plugins/debugger/cdb/cdbdebugeventcallback.cpp
@@ -300,7 +300,7 @@ void formatException(const EXCEPTION_RECORD64 *e, QTextStream &str)
str << "attempt to continue execution after noncontinuable exception";
break;
case EXCEPTION_PRIV_INSTRUCTION:
- str << "priviledged instruction";
+ str << "privileged instruction";
break;
case EXCEPTION_SINGLE_STEP:
str << "single step";
@@ -336,6 +336,19 @@ void formatException(const EXCEPTION_RECORD64 *e,
}
}
+static bool isFatalException(LONG code)
+{
+ switch (code) {
+ case EXCEPTION_BREAKPOINT:
+ case EXCEPTION_SINGLE_STEP:
+ case 0x406d1388: // Mysterious exception at start of application
+ return false;
+ default:
+ break;
+ }
+ return true;
+}
+
STDMETHODIMP CdbDebugEventCallback::Exception(
THIS_
__in PEXCEPTION_RECORD64 Exception,
@@ -347,9 +360,12 @@ STDMETHODIMP CdbDebugEventCallback::Exception(
QTextStream str(&msg);
formatException(Exception, m_pEngine->m_d->m_dumper, str);
}
+ const bool fatal = isFatalException(Exception->ExceptionCode);
if (debugCDB)
- qDebug() << Q_FUNC_INFO << '\n' << msg;
+ qDebug() << Q_FUNC_INFO << '\n' << fatal << msg;
m_pEngine->m_d->m_debuggerManagerAccess->showApplicationOutput(msg);
+ if (fatal)
+ m_pEngine->m_d->notifyCrashed();
return S_OK;
}
diff --git a/src/plugins/debugger/cdb/cdbdumperhelper.cpp b/src/plugins/debugger/cdb/cdbdumperhelper.cpp
index 3f4ffdab8fd..541aa7ea1d0 100644
--- a/src/plugins/debugger/cdb/cdbdumperhelper.cpp
+++ b/src/plugins/debugger/cdb/cdbdumperhelper.cpp
@@ -35,6 +35,8 @@
#include "cdbsymbolgroupcontext.h"
#include "watchhandler.h"
+#include "sharedlibraryinjector.h"
+
#include <QtCore/QRegExp>
#include <QtCore/QCoreApplication>
#include <QtCore/QTextStream>
@@ -45,9 +47,47 @@ static const char *dumperModuleNameC = "gdbmacros";
static const char *qtCoreModuleNameC = "QtCore";
static const ULONG waitTimeOutMS = 30000;
static const char *dumperPrefixC = "dumper:";
+
+/* Loading the dumpers is 2 step process:
+ * 1) The library must be loaded into the debuggee, for which there are
+ * 2 approaches:
+ * - Injection loading using the SharedLibraryInjector which
+ * launches a remote thread in the debuggee which loads the
+ * library. Drawbacks:
+ * * The remote thread must not starve.
+ * * It is not possible to wait loading and loading occurs late,
+ * after entering main()
+ * - Debugger Call loading, which has the debuggee execute
+ * a LoadLibrary call via debugger commands. Drawbacks:
+ * * Slow
+ * * Requires presence of a symbol of the same prototype as
+ * LoadLibraryA as the original prototype is not sufficient.
+ * 2) Call a query function (protocol 1 of the dumper) to obtain a list
+ * of handled types and a map of known sizes.
+ *
+ * The class currently launches injection loading from the module
+ * load hook as soon as it sees a Qt module.
+ * The dumpType() function performs the rest of the [delayed] initialization.
+ * If the load has not finished, it attempts call loading and
+ * executes the initial query protocol.
+ *
+ * Note: The main technique here is having the debuggee call functions
+ * using the .call command (which takes a function with a known
+ * prototype and simple, integer parameters).
+ * This does not work from an IDebugEvent callback, as it will cause
+ * WaitForEvent() to fail with unknown errors.
+ * It mostly works from breakpoints, with the addditional restriction
+ * that complex functions only work from 'well-defined' breakpoints
+ * (such as main()) and otherwise cause access violation exceptions
+ * (for example LoadLibrary).
+ * Exceptions occuring in the functions to be called must be handled
+ * by __try/__except (they show up in the debugger and must acknowledged
+ * by gN (go not handled). */
+
namespace Debugger {
namespace Internal {
+// ------- Call load helpers
// Alloc memory in debuggee using the ".dvalloc" command as
// there seems to be no API for it.
static bool allocDebuggeeMemory(CdbComInterfaces *cif,
@@ -144,13 +184,44 @@ static bool debuggeeLoadLibrary(IDebuggerManagerAccessForEngines *access,
return true;
}
+// ---- Load messages
+static inline QString msgMethod(bool injectOrCall)
+{
+ return injectOrCall ?
+ QCoreApplication::translate("CdbDumperHelper", "injection") :
+ QCoreApplication::translate("CdbDumperHelper", "debugger call");
+}
+
+static QString msgLoading(const QString &library, bool injectOrCall)
+{
+ return QCoreApplication::translate("CdbDumperHelper",
+ "Loading the custom dumper library '%1' (%2) ...").
+ arg(library, msgMethod(injectOrCall));
+}
+
+static QString msgLoadFailed(const QString &library, bool injectOrCall, const QString &why)
+{
+ return QCoreApplication::translate("CdbDumperHelper",
+ "Loading of the custom dumper library '%1' (%2) failed: %3").
+ arg(library, msgMethod(injectOrCall), why);
+}
+
+static QString msgLoadSucceeded(const QString &library, bool injectOrCall)
+{
+ return QCoreApplication::translate("CdbDumperHelper",
+ "Loaded the custom dumper library '%1' (%2).").
+ arg(library, msgMethod(injectOrCall));
+}
+
// ------------------- CdbDumperHelper
-CdbDumperHelper::CdbDumperHelper(IDebuggerManagerAccessForEngines *access,
+CdbDumperHelper::CdbDumperHelper(DebuggerManager *manager,
CdbComInterfaces *cif) :
+ m_tryInjectLoad(true),
m_messagePrefix(QLatin1String(dumperPrefixC)),
m_state(NotLoaded),
- m_access(access),
+ m_manager(manager),
+ m_access(manager),
m_cif(cif),
m_inBufferAddress(0),
m_inBufferSize(0),
@@ -165,6 +236,14 @@ CdbDumperHelper::~CdbDumperHelper()
clearBuffer();
}
+void CdbDumperHelper::disable()
+{
+ if (loadDebug)
+ qDebug() << Q_FUNC_INFO;
+ m_access->showDebuggerOutput(m_messagePrefix, QCoreApplication::translate("CdbDumperHelper", "Disabling dumpers due to debuggee crash..."));
+ m_state = Disabled;
+}
+
void CdbDumperHelper::clearBuffer()
{
if (m_buffer) {
@@ -175,6 +254,8 @@ void CdbDumperHelper::clearBuffer()
void CdbDumperHelper::reset(const QString &library, bool enabled)
{
+ if (loadDebug)
+ qDebug() << Q_FUNC_INFO << '\n' << library << enabled;
m_library = library;
m_state = enabled ? NotLoaded : Disabled;
m_dumpObjectSymbol = QLatin1String("qDumpObjectData440");
@@ -185,63 +266,113 @@ void CdbDumperHelper::reset(const QString &library, bool enabled)
clearBuffer();
}
-// Attempt to load and initialize dumpers, give feedback
-// to user.
-void CdbDumperHelper::load(DebuggerManager *manager)
+void CdbDumperHelper::moduleLoadHook(const QString &module, HANDLE debuggeeHandle)
{
- enum Result { Failed, Succeeded, NoQtApp };
-
- if (m_state != NotLoaded)
- return;
- manager->showStatusMessage(QCoreApplication::translate("CdbDumperHelper", "Loading dumpers..."), 10000);
- QString message;
- Result result = Failed;
- do {
- // Do we have Qt and are we already loaded by accident?
- QStringList modules;
- if (!getModuleNameList(m_cif->debugSymbols, &modules, &message))
- break;
- if (modules.filter(QLatin1String(qtCoreModuleNameC)).isEmpty()) {
- message = QCoreApplication::translate("CdbDumperHelper", "The debugger does not appear to be Qt application.");
- result = NoQtApp;
- }
- // Make sure the dumper lib is loaded.
- if (modules.filter(QLatin1String(dumperModuleNameC), Qt::CaseInsensitive).isEmpty()) {
- // Try to load
- if (!debuggeeLoadLibrary(m_access, m_cif, m_library, &message)) {
- break;
+ if (loadDebug > 1)
+ qDebug() << "moduleLoadHook" << module << m_state << debuggeeHandle;
+ switch (m_state) {
+ case Disabled:
+ case Initialized:
+ break;
+ case NotLoaded:
+ // Try an inject load as soon as a Qt lib is loaded.
+ // for the thread to finish as this would lock up.
+ if (m_tryInjectLoad && module.contains(QLatin1String("Qt"), Qt::CaseInsensitive)) {
+ // Also shows up in the log window.
+ m_manager->showStatusMessage(msgLoading(m_library, true), 10000);
+ QString errorMessage;
+ SharedLibraryInjector sh(GetProcessId(debuggeeHandle));
+ if (sh.remoteInject(m_library, false, &errorMessage)) {
+ m_state = InjectLoading;
+ } else {
+ m_state = InjectLoadFailed;
+ // Ok, try call loading...
+ m_access->showDebuggerOutput(m_messagePrefix, msgLoadFailed(m_library, true, errorMessage));
}
- } else {
- m_access->showDebuggerOutput(m_messagePrefix, QCoreApplication::translate("CdbDumperHelper", "The dumper module appears to be already loaded."));
}
- // Resolve symbols and do call to get types
- if (!resolveSymbols(&message))
- break;
- if (!getKnownTypes(&message))
- break;
- message = QCoreApplication::translate("CdbDumperHelper", "Dumper library '%1' loaded.").arg(m_library);
- result = Succeeded;
- } while (false);
- // eval state and notify user
- switch (result) {
- case Failed:
- message = QCoreApplication::translate("CdbDumperHelper", "The dumper library '%1' could not be loaded:\n%2").arg(m_library, message);
- m_access->showDebuggerOutput(m_messagePrefix, message);
- m_access->showQtDumperLibraryWarning(message);
- m_state = Disabled;
- break;
- case Succeeded:
- m_access->showDebuggerOutput(m_messagePrefix, message);
- m_access->showDebuggerOutput(m_messagePrefix, m_helper.toString());
- m_state = Loaded;
break;
- case NoQtApp:
- m_access->showDebuggerOutput(m_messagePrefix, message);
- m_state = Disabled;
+ case InjectLoading:
+ // check if gdbmacros.dll loaded
+ if (module.contains(QLatin1String(dumperModuleNameC), Qt::CaseInsensitive)) {
+ m_state = Loaded;
+ m_access->showDebuggerOutput(m_messagePrefix, msgLoadSucceeded(m_library, true));
+ }
break;
- }
+ }
+}
+
+// Try to load dumpers by triggering calls using the debugger
+CdbDumperHelper::CallLoadResult CdbDumperHelper::initCallLoad(QString *errorMessage)
+{
if (loadDebug)
- qDebug() << Q_FUNC_INFO << "\n<" << result << '>' << m_state << message;
+ qDebug() << Q_FUNC_INFO;
+ // Do we have Qt and are we already loaded by accident?
+ QStringList modules;
+ if (!getModuleNameList(m_cif->debugSymbols, &modules, errorMessage))
+ return CallLoadError;
+ // Are we already loaded by some accident?
+ if (!modules.filter(QLatin1String(dumperModuleNameC), Qt::CaseInsensitive).isEmpty())
+ return CallLoadAlreadyLoaded;
+ // Is that Qt application at all?
+ if (modules.filter(QLatin1String(qtCoreModuleNameC), Qt::CaseInsensitive).isEmpty())
+ return CallLoadNoQtApp;
+ // Try to load
+ if (!debuggeeLoadLibrary(m_access, m_cif, m_library, errorMessage))
+ return CallLoadError;
+ return CallLoadOk;
+}
+
+bool CdbDumperHelper::ensureInitialized(QString *errorMessage)
+{
+ if (loadDebug)
+ qDebug() << Q_FUNC_INFO << '\n' << m_state;
+
+ switch (m_state) {
+ case Disabled:
+ *errorMessage = QLatin1String("Internal error, attempt to call disabled dumper");
+ return false;
+ case Initialized:
+ return true;
+ // Injection load failed or disabled: Try a call load.
+ case NotLoaded:
+ case InjectLoading:
+ case InjectLoadFailed:
+ // Also shows up in the log window.
+ m_manager->showStatusMessage(msgLoading(m_library, false), 10000);
+ switch (initCallLoad(errorMessage)) {
+ case CallLoadOk:
+ case CallLoadAlreadyLoaded:
+ m_access->showDebuggerOutput(m_messagePrefix, msgLoadSucceeded(m_library, false));
+ m_state = Loaded;
+ break;
+ case CallLoadError:
+ *errorMessage = msgLoadFailed(m_library, false, *errorMessage);
+ m_access->showDebuggerOutput(m_messagePrefix, *errorMessage);
+ m_access->showQtDumperLibraryWarning(*errorMessage);
+ return false;
+ case CallLoadNoQtApp:
+ m_access->showDebuggerOutput(m_messagePrefix, QCoreApplication::translate("CdbDumperHelper", "The debuggee does not appear to be Qt application."));
+ disable();
+ return true;
+ }
+ break;
+ case Loaded: // Injection load succeeded, ideally
+ break;
+ }
+ // Perform remaining initialization
+ m_manager->showStatusMessage(QCoreApplication::translate("CdbDumperHelper", "Initializing dumpers..."), 10000);
+ const bool ok = initResolveSymbols(errorMessage) && initKnownTypes(errorMessage);
+ if (ok) {
+ m_access->showDebuggerOutput(m_messagePrefix, QCoreApplication::translate("CdbDumperHelper", "Custom dumper library initialized."));
+ m_access->showDebuggerOutput(m_messagePrefix, m_helper.toString());
+ m_state = Initialized;
+ } else {
+ disable();
+ *errorMessage = QCoreApplication::translate("CdbDumperHelper", "The custom dumper library could not be initialized: %1").arg(*errorMessage);
+ m_access->showDebuggerOutput(m_messagePrefix, *errorMessage);
+ m_access->showQtDumperLibraryWarning(*errorMessage);
+ }
+ return ok;
}
// Retrieve address and optionally size of a symbol.
@@ -275,7 +406,7 @@ static inline bool getSymbolAddress(CIDebugSymbols *sg,
return true;
}
-bool CdbDumperHelper::resolveSymbols(QString *errorMessage)
+bool CdbDumperHelper::initResolveSymbols(QString *errorMessage)
{
// Resolve the symbols we need (potentially namespaced).
// There is a 'qDumpInBuffer' in QtCore as well.
@@ -302,7 +433,8 @@ bool CdbDumperHelper::resolveSymbols(QString *errorMessage)
return true;
}
-bool CdbDumperHelper::getKnownTypes(QString *errorMessage)
+// Call query protocol to retrieve known types and sizes
+bool CdbDumperHelper::initKnownTypes(QString *errorMessage)
{
QByteArray output;
QString callCmd;
@@ -398,15 +530,29 @@ bool CdbDumperHelper::callDumper(const QString &callCmd, const QByteArray &inBuf
return true;
}
+static inline QString msgDumpFailed(const WatchData &wd, const QString *why)
+{
+ return QString::fromLatin1("Unable to dump '%1' (%2): %3").arg(wd.name, wd.type, *why);
+}
+
CdbDumperHelper::DumpResult CdbDumperHelper::dumpType(const WatchData &wd, bool dumpChildren, int source,
QList<WatchData> *result, QString *errorMessage)
{
// Check failure cache and supported types
- if (m_failedTypes.contains(wd.type))
+ if (m_state == Disabled || m_failedTypes.contains(wd.type))
return DumpNotHandled;
+
+ // Ensure types are parsed and known.
+ if (!ensureInitialized(errorMessage)) {
+ *errorMessage = msgDumpFailed(wd, errorMessage);
+ m_access->showDebuggerOutput(m_messagePrefix, *errorMessage);
+ return DumpError;
+ }
+
+ // Known type?
const QtDumperHelper::TypeData td = m_helper.typeData(wd.type);
if (loadDebug)
- qDebug() << wd.type << td;
+ qDebug() << "dumpType" << wd.type << td;
if (td.type == QtDumperHelper::UnknownType)
return DumpNotHandled;
@@ -415,6 +561,7 @@ CdbDumperHelper::DumpResult CdbDumperHelper::dumpType(const WatchData &wd, bool
"Querying dumpers for '%1'/'%2' (%3)").
arg(wd.name, wd.exp, wd.type);
m_access->showDebuggerOutput(m_messagePrefix, message);
+
const DumpExecuteResult der = executeDump(wd, td, dumpChildren, source, result, errorMessage);
if (der == DumpExecuteOk)
return DumpOk;
@@ -424,7 +571,7 @@ CdbDumperHelper::DumpResult CdbDumperHelper::dumpType(const WatchData &wd, bool
if (der == DumpExecuteSizeFailed)
m_failedTypes.push_back(wd.type);
// log error
- *errorMessage = QString::fromLatin1("Unable to dump '%1' (%2): %3").arg(wd.name, wd.type, *errorMessage);
+ *errorMessage = *errorMessage = msgDumpFailed(wd, errorMessage);
m_access->showDebuggerOutput(m_messagePrefix, *errorMessage);
return DumpError;
}
diff --git a/src/plugins/debugger/cdb/cdbdumperhelper.h b/src/plugins/debugger/cdb/cdbdumperhelper.h
index 9500d4d4e01..8ff890630d1 100644
--- a/src/plugins/debugger/cdb/cdbdumperhelper.h
+++ b/src/plugins/debugger/cdb/cdbdumperhelper.h
@@ -46,54 +46,64 @@ class DebuggerManager;
/* For code clarity, all the stuff related to custom dumpers goes here.
* "Custom dumper" is a library compiled against the current
* Qt containing functions to evaluate values of Qt classes
- * (such as QString, taking pointers to their addresses).
- * The library must be loaded into the debuggee.
- * Loading the dumpers requires making the debuggee call functions
- * (LoadLibrary() and the dumper functions). This only works if the
- * debuggee is in a 'well-defined' breakpoint state (such as at 'main()').
- * Calling the load functions from an IDebugEvent callback causes
- * WaitForEvent() to fail with unknown errors. Calling the load functions from an
- * non 'well-defined' (arbitrary) breakpoint state will cause LoadLibrary
- * to trigger an access violations.
- * Currently, we call the load function when stopping at 'main()' for which
- * we set a temporary break point if the user does not want to stop there. */
+ * (such as QString), taking pointers to their addresses.
+ * The dumper functions produce formatted string output which is
+ * converted into WatchData items with the help of QtDumperHelper.
+ *
+ * Usage: When launching the debugger, call reset() with path to dumpers
+ * and enabled flag. From the module load event callback, call
+ * moduleLoadHook() to initialize.
+ * dumpType() is the main query function to obtain a list of WatchData from
+ * WatchData item produced by the smbol context.
+ * Call disable(), should the debuggee crash (as performing debuggee
+ * calls is no longer possible, then).*/
class CdbDumperHelper
{
Q_DISABLE_COPY(CdbDumperHelper)
public:
enum State {
- Disabled,
+ Disabled, // Disabled or failed
NotLoaded,
+ InjectLoadFailed,
+ InjectLoading,
Loaded,
- Failed
+ Initialized, // List of types, etc. retrieved
};
- explicit CdbDumperHelper(IDebuggerManagerAccessForEngines *access,
+ explicit CdbDumperHelper(DebuggerManager *manager,
CdbComInterfaces *cif);
~CdbDumperHelper();
- State state() const { return m_state; }
- operator bool() const { return m_state == Loaded; }
+ State state() const { return m_state; }
+ bool isEnabled() const { return m_state != Disabled; }
+
+ // Disable in case of a debuggee crash.
+ void disable();
// Call before starting the debugger
void reset(const QString &library, bool enabled);
- // Call in a temporary breakpoint state to actually load.
- void load(DebuggerManager *manager);
+ // Call from the module load callback to perform initialization.
+ void moduleLoadHook(const QString &module, HANDLE debuggeeHandle);
+ // Dump a WatchData item.
enum DumpResult { DumpNotHandled, DumpOk, DumpError };
DumpResult dumpType(const WatchData &d, bool dumpChildren, int source,
QList<WatchData> *result, QString *errorMessage);
- // bool handlesType(const QString &typeName) const;
-
inline CdbComInterfaces *comInterfaces() const { return m_cif; }
private:
+ enum CallLoadResult { CallLoadOk, CallLoadError, CallLoadNoQtApp, CallLoadAlreadyLoaded };
+
void clearBuffer();
- bool resolveSymbols(QString *errorMessage);
- bool getKnownTypes(QString *errorMessage);
+
+ bool ensureInitialized(QString *errorMessage);
+ CallLoadResult initCallLoad(QString *errorMessage);
+ bool initResolveSymbols(QString *errorMessage);
+ bool initKnownTypes(QString *errorMessage);
+
bool getTypeSize(const QString &typeName, int *size, QString *errorMessage);
bool runTypeSizeQuery(const QString &typeName, int *size, QString *errorMessage);
bool callDumper(const QString &call, const QByteArray &inBuffer, const char **outputPtr, QString *errorMessage);
@@ -105,8 +115,10 @@ private:
static bool writeToDebuggee(CIDebugDataSpaces *ds, const QByteArray &buffer, quint64 address, QString *errorMessage);
+ const bool m_tryInjectLoad;
const QString m_messagePrefix;
State m_state;
+ DebuggerManager *m_manager;
IDebuggerManagerAccessForEngines *m_access;
CdbComInterfaces *m_cif;
diff --git a/src/plugins/debugger/cdb/cdbstackframecontext.cpp b/src/plugins/debugger/cdb/cdbstackframecontext.cpp
index be04856ccc0..fd59bd0fa6b 100644
--- a/src/plugins/debugger/cdb/cdbstackframecontext.cpp
+++ b/src/plugins/debugger/cdb/cdbstackframecontext.cpp
@@ -117,8 +117,7 @@ WatchHandlerSorterInserter &WatchHandlerSorterInserter::operator=(WatchData wd)
// -----------CdbStackFrameContext
CdbStackFrameContext::CdbStackFrameContext(const QSharedPointer<CdbDumperHelper> &dumper,
CdbSymbolGroupContext *symbolContext) :
- m_useDumpers(dumper->state() == CdbDumperHelper::Loaded
- && theDebuggerBoolSetting(UseDebuggingHelpers)),
+ m_useDumpers(dumper->isEnabled() && theDebuggerBoolSetting(UseDebuggingHelpers)),
m_dumper(dumper),
m_symbolContext(symbolContext)
{
diff --git a/src/plugins/debugger/win/sharedlibraryinjector.cpp b/src/plugins/debugger/win/sharedlibraryinjector.cpp
new file mode 100644
index 00000000000..628263d3e15
--- /dev/null
+++ b/src/plugins/debugger/win/sharedlibraryinjector.cpp
@@ -0,0 +1,479 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information ([email protected])
+**
+** 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 [email protected].
+**
+**************************************************************************/
+
+//
+// WinANSI - An ANSI implementation for the modern Windows
+//
+// Copyright (C) 2008- Marius Storm-Olsen <[email protected]>
+//
+// Based on work by Robert Kuster in article
+// http://software.rkuster.com/articles/winspy.htm
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// -------------------------------------------------------------------------------------------------
+
+#include "sharedlibraryinjector.h"
+#include <utils/winutils.h>
+
+#include <QtCore/QDebug>
+
+enum { debug = 0 };
+
+static QString msgFuncFailed(const char *f, unsigned long error)
+{
+ return QString::fromLatin1("%1 failed: %2").arg(QLatin1String(f), Core::Utils::winErrorMessage(error));
+}
+
+// Resolve a symbol from a library handle
+template <class SymbolType>
+inline bool resolveSymbol(const char *libraryName, HMODULE libraryHandle, const char *symbolName, SymbolType *s, QString *errorMessage)
+{
+ *s = 0;
+ void *vs = ::GetProcAddress(libraryHandle, symbolName);
+ if (vs == 0) {
+ *errorMessage = QString::fromLatin1("Unable to resolve '%2' in '%1'.").arg(QString::fromAscii(symbolName), QString::fromAscii(libraryName));
+ return false;
+ }
+ *s = reinterpret_cast<SymbolType>(vs);
+ return true;
+}
+
+// Resolve a symbol from a library
+template <class SymbolType>
+inline bool resolveSymbol(const char *library, const char *symbolName, SymbolType *s, QString *errorMessage)
+{
+ *s = 0;
+ const HMODULE hm = ::GetModuleHandleA(library);
+ if (hm == NULL) {
+ *errorMessage = QString::fromLatin1("Module '%1' does not exist.").arg(QString::fromAscii(library));
+ return false;
+ }
+ return resolveSymbol(library, hm , symbolName, s, errorMessage);
+}
+
+
+namespace Debugger {
+namespace Internal {
+
+SharedLibraryInjector::SharedLibraryInjector(unsigned long processId,
+ unsigned long threadId) :
+ m_processId(processId),
+ m_threadId(threadId),
+ m_hasEscalatedPrivileges(false)
+{
+}
+
+SharedLibraryInjector::~SharedLibraryInjector()
+{
+}
+
+void SharedLibraryInjector::setPid(unsigned long pid)
+{
+ m_processId = pid;
+}
+
+void SharedLibraryInjector::setThreadId(unsigned long tid)
+{
+ m_threadId = tid;
+}
+
+void SharedLibraryInjector::setModulePath(const QString &modulePath)
+{
+ m_modulePath = modulePath;
+}
+
+bool SharedLibraryInjector::remoteInject(const QString &modulePath,
+ bool waitForThread, QString *errorMessage)
+{
+ setModulePath(modulePath);
+ return doRemoteInjection(m_processId, NULL, m_modulePath, waitForThread, errorMessage);
+}
+
+bool SharedLibraryInjector::stubInject(const QString &modulePath, unsigned long entryPoint, QString *errorMessage)
+{
+ setModulePath(modulePath);
+ return doStubInjection(m_processId, m_modulePath, entryPoint, errorMessage);
+}
+
+bool SharedLibraryInjector::unload(HMODULE hFreeModule, QString *errorMessage)
+{
+ return doRemoteInjection(m_processId, hFreeModule, QString(), true, errorMessage); // Always use remote thread to unload
+}
+
+bool SharedLibraryInjector::unload(const QString &modulePath, QString *errorMessage)
+{
+ const HMODULE hMod = modulePath.isEmpty() ?
+ findModuleHandle(m_modulePath, errorMessage) :
+ findModuleHandle(modulePath, errorMessage);
+ if (!hMod)
+ return false;
+
+ return doRemoteInjection(m_processId, hMod, NULL, true, errorMessage); // Always use remote thread to unload
+}
+
+bool SharedLibraryInjector::hasLoaded(const QString &modulePath)
+{
+ QString errorMessage;
+ return findModuleHandle(modulePath.isEmpty() ? m_modulePath : modulePath, &errorMessage) != NULL;
+}
+
+QString SharedLibraryInjector::findModule(const QString &moduleName)
+{
+ const TCHAR *moduleNameC = moduleName.utf16();
+ if (GetFileAttributesW(moduleNameC) != INVALID_FILE_ATTRIBUTES)
+ return moduleName;
+
+ TCHAR testpathC[MAX_PATH];
+ // Check application path first
+ GetModuleFileNameW(NULL, testpathC, MAX_PATH);
+ QString testPath = QString::fromUtf16(testpathC);
+ const int lastSlash = testPath.lastIndexOf(QLatin1Char('\\'));
+ if (lastSlash != -1)
+ testPath.truncate(lastSlash + 1);
+ testPath += moduleName;
+ if (GetFileAttributesW(testPath.utf16()) != INVALID_FILE_ATTRIBUTES)
+ return testPath;
+ // Path Search
+ if (SearchPathW(NULL, moduleName.utf16(), NULL, sizeof(testpathC)/2, testpathC, NULL))
+ return QString::fromUtf16(testpathC);
+ // Last chance, if the module has already been loaded in this process, then use that path
+ const HMODULE loadedModule = GetModuleHandleW(moduleName.utf16());
+ if (loadedModule) {
+ GetModuleFileNameW(loadedModule, testpathC, sizeof(testpathC));
+ if (GetFileAttributes(testpathC) != INVALID_FILE_ATTRIBUTES)
+ return QString::fromUtf16(testpathC);
+ }
+ return QString();
+}
+
+unsigned long SharedLibraryInjector::getModuleEntryPoint(const QString &moduleName)
+{
+ // If file doesn't exist, just treat it like we cannot figure out the entry point
+ if (moduleName.isEmpty() || GetFileAttributesW(moduleName.utf16()) == INVALID_FILE_ATTRIBUTES)
+ return 0;
+
+ // Read the first 1K of data from the file
+ unsigned char peData[1024];
+ unsigned long peDataSize = 0;
+ const HANDLE hFile = CreateFileW(moduleName.utf16(), FILE_READ_DATA, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
+ if (hFile == INVALID_HANDLE_VALUE
+ || !ReadFile(hFile, peData, sizeof(peData), &peDataSize, NULL))
+ return 0;
+ CloseHandle(hFile);
+
+ // Now we check to see if there is an optional header we can read
+ IMAGE_DOS_HEADER *dosHeader = reinterpret_cast<IMAGE_DOS_HEADER *>(peData);
+ if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE // DOS signature is incorrect, or
+ || dosHeader->e_lfanew == 0) // executable's PE data contains no offset to New EXE header
+ return 0;
+
+ IMAGE_NT_HEADERS *ntHeaders = (IMAGE_NT_HEADERS *)(peData + dosHeader->e_lfanew);
+ if (ntHeaders->Signature != IMAGE_NT_SIGNATURE // NT signature is incorrect, or
+ || !ntHeaders->OptionalHeader.ImageBase // ImageBase or EntryPoint addresses are incorrect
+ || !ntHeaders->OptionalHeader.AddressOfEntryPoint)
+ return 0;
+
+ return ntHeaders->OptionalHeader.ImageBase
+ + ntHeaders->OptionalHeader.AddressOfEntryPoint;
+}
+
+bool SharedLibraryInjector::escalatePrivileges(QString *errorMessage)
+{
+ if (debug)
+ qDebug() << Q_FUNC_INFO << m_hasEscalatedPrivileges;
+
+ if (m_hasEscalatedPrivileges)
+ return true;
+
+ bool success = false;
+ HANDLE hToken = 0;
+ do {
+ TOKEN_PRIVILEGES Debug_Privileges;
+ if (!LookupPrivilegeValue (NULL, SE_DEBUG_NAME, &Debug_Privileges.Privileges[0].Luid)) {
+ *errorMessage = msgFuncFailed("LookupPrivilegeValue", GetLastError());
+ break;
+ }
+
+ Debug_Privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; // set to enable privilege
+ Debug_Privileges.PrivilegeCount = 1; // working with only 1
+
+ if (!OpenProcessToken (GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) {
+ *errorMessage = msgFuncFailed("OpenProcessToken", GetLastError());
+ break;
+ }
+ if (!AdjustTokenPrivileges(hToken, FALSE, &Debug_Privileges, 0, NULL, NULL)) {
+ *errorMessage = msgFuncFailed("AdjustTokenPrivileges", GetLastError());
+ break;
+
+ }
+ success = true;
+ } while (false);
+
+ if (hToken)
+ CloseHandle (hToken);
+
+ m_hasEscalatedPrivileges = success;
+ return success;
+}
+
+static void *writeDataToProcess(HANDLE process, void *data, unsigned size, QString *errorMessage)
+{
+ void *memory = VirtualAllocEx(process, NULL, size, MEM_COMMIT, PAGE_READWRITE);
+ if (!memory) {
+ *errorMessage = msgFuncFailed("VirtualAllocEx", GetLastError());
+ return 0;
+ }
+ if (!WriteProcessMemory(process, memory, data, size, NULL)) {
+ *errorMessage = msgFuncFailed("WriteProcessMemory", GetLastError());
+ return 0;
+ }
+ return memory;
+}
+
+static void *writeUtf16StringToProcess(HANDLE process, const QString &what, QString *errorMessage)
+{
+ QByteArray whatData = QByteArray(reinterpret_cast<const char*>(what.utf16()), what.size() * 2);
+ whatData += '\0';
+ whatData += '\0';
+ return writeDataToProcess(process, whatData.data(), whatData.size(), errorMessage);
+}
+
+bool SharedLibraryInjector::doStubInjection(unsigned long pid,
+ const QString &modulePath,
+ unsigned long entryPoint,
+ QString *errorMessage)
+{
+ if (debug)
+ qDebug() << pid << modulePath << entryPoint;
+ if (modulePath.isEmpty())
+ return false;
+
+ if (!escalatePrivileges(errorMessage))
+ return false;
+
+#if (defined(WIN64) || defined(_WIN64) || defined(__WIN64__))
+ *errorMessage = QLatin1String("Not implemented for this architecture.");
+ return false;
+#else
+ byte stubCode[] = {
+ 0x68, 0, 0, 0, 0, // push 0x00000000 - Placeholder for the entrypoint address
+ 0x9C, // pushfd - Save the flags and registers
+ 0x60, // pushad
+ 0x68, 0, 0, 0, 0, // push 0x00000000 - Placeholder for the string address
+ 0xB8, 0, 0, 0, 0, // mov eax, 0x00000000 - Placeholder for the LoadLibrary address
+ 0xFF, 0xD0, // call eax - Call LoadLibrary with string address
+ 0x61, // popad - Restore flags and registry
+ 0x9D, // popfd
+ 0xC3 // retn - Return (pops the entrypoint, and executes)
+ };
+
+ const HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
+ if (!hProcess)
+ return false;
+
+ const HANDLE hThread = OpenThread(THREAD_GET_CONTEXT | THREAD_SET_CONTEXT, false, m_threadId);
+ if (!hThread) {
+ CloseHandle(hProcess);
+ return false;
+ }
+
+ // Get address of LoadLibraryW in kernel32.dll
+ unsigned long funcPtr;
+ if (!resolveSymbol("kernel32.dll", "LoadLibraryW", &funcPtr, errorMessage))
+ return false;
+
+ // Allocate and write DLL path to target process' memory
+ // 0-terminated Utf16 string.
+ void *dllString = writeUtf16StringToProcess(hProcess, modulePath, errorMessage);
+ if (!dllString)
+ return false;
+
+ // Allocate memory in target process, for the stubCode which will load the module, and execute
+ // the process' entry-point
+ const unsigned long stubLen = sizeof(stubCode);
+ // Modify the stubCode to have the proper entry-point, DLL module string, and function pointer
+ *(unsigned long*)(stubCode+1) = entryPoint;
+ *(unsigned long*)(stubCode+8) = reinterpret_cast<unsigned long>(dllString);
+ *(unsigned long*)(stubCode+13) = funcPtr;
+
+ // If we cannot write the stubCode into the process, we simply bail out, and the process will
+ // continues execution as normal
+ void *stub = writeDataToProcess(hProcess, stubCode, stubLen, errorMessage);
+ if (!stub)
+ return false;
+ // Set the process' main thread to start executing from the stubCode address which we've just
+ // allocated in the process' memory.. If we cannot do that, bail out
+ CONTEXT ctx;
+ ctx.ContextFlags = CONTEXT_CONTROL;
+ if(!GetThreadContext(hThread, &ctx))
+ return false;
+ ctx.Eip = reinterpret_cast<DWORD>(stub);
+ ctx.ContextFlags = CONTEXT_CONTROL;
+ if(!SetThreadContext(hThread, &ctx))
+ return false;
+
+ CloseHandle(hProcess);
+ CloseHandle(hThread);
+ return true;
+#endif
+}
+
+// -------------------------------------------------------------------------------------------------
+
+bool SharedLibraryInjector::doRemoteInjection(unsigned long pid,
+ HMODULE hFreeModule,
+ const QString &modulePath,
+ bool waitForThread,
+ QString *errorMessage)
+{
+ if (debug)
+ qDebug() << Q_FUNC_INFO << '\n' << hFreeModule << pid << waitForThread<< modulePath;
+
+ if (!hFreeModule && modulePath.isEmpty())
+ return false;
+
+ escalatePrivileges(errorMessage);
+
+ const HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
+ if (hProcess == NULL) {
+ *errorMessage = msgFuncFailed("OpenProcess", GetLastError());
+ qDebug() << "Failed to open process PID %d with all access\n" << pid;
+ return false;
+ }
+
+ void *pszLibFileRemote = 0;
+ HANDLE hThread = 0;
+ // Call "FreeLibrary(hFreeModule)" to unload
+ if (hFreeModule) {
+ PTHREAD_START_ROUTINE pfnThreadRtn;
+ if (!resolveSymbol("Kernel32", "FreeLibrary", &pfnThreadRtn, errorMessage))
+ return false;
+ hThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, hFreeModule, 0, NULL);
+ } else {
+ pszLibFileRemote = writeUtf16StringToProcess(hProcess, modulePath, errorMessage);
+ if (!pszLibFileRemote)
+ return false;
+ // Loadlibrary routine
+ PTHREAD_START_ROUTINE pfnThreadRtn;
+ if (!resolveSymbol("Kernel32", "LoadLibraryW", &pfnThreadRtn, errorMessage))
+ return false;
+ hThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnThreadRtn, pszLibFileRemote, 0, NULL);
+ }
+ if (hThread == NULL) {
+ *errorMessage = msgFuncFailed("CreateRemoteThread", GetLastError());
+ return false;
+ }
+
+ if (!SetThreadPriority(hThread, THREAD_PRIORITY_HIGHEST)) {
+ *errorMessage = msgFuncFailed("SetThreadPriority", GetLastError());
+ return false;
+ }
+
+ if (waitForThread) {
+ if (::WaitForSingleObject(hThread, 20000) != WAIT_OBJECT_0) {
+ *errorMessage = QString::fromLatin1("WaitForSingleObject timeout");
+ ::CloseHandle(hThread);
+ ::CloseHandle(hProcess);
+ return false;
+ }
+
+ if (pszLibFileRemote)
+ ::VirtualFreeEx(hProcess, pszLibFileRemote, 0, MEM_RELEASE);
+ }
+
+ if (hThread)
+ ::CloseHandle(hThread);
+
+ if (hProcess)
+ ::CloseHandle(hProcess);
+ if (debug)
+ qDebug() << "success" << Q_FUNC_INFO;
+ return true;
+}
+
+typedef BOOL (WINAPI *PFNENUMPROCESSMODULES) (HANDLE hProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded);
+typedef DWORD (WINAPI *PFNGETMODULEFILENAMEEXW) (HANDLE hProcess, HMODULE hModule, LPTSTR lpFilename, DWORD nSize);
+
+// We will require this function to get a module handle of our original module
+HMODULE SharedLibraryInjector::findModuleHandle(const QString &modulePath, QString *errorMessage)
+{
+ if (debug)
+ qDebug() << Q_FUNC_INFO << modulePath;
+
+ if (!escalatePrivileges(errorMessage))
+ return 0;
+
+ HMODULE hMods[1024];
+ DWORD cbNeeded;
+
+ HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, m_processId);
+ if (hProcess == NULL)
+ return 0;
+
+ const char *psAPI_Lib = "PSAPI.DLL";
+ HMODULE m_hModPSAPI = ::LoadLibraryA(psAPI_Lib);
+ PFNENUMPROCESSMODULES m_pfnEnumProcessModules;
+ PFNGETMODULEFILENAMEEXW m_pfnGetModuleFileNameExW;
+ if (!resolveSymbol(psAPI_Lib, m_hModPSAPI, "EnumProcessModules", &m_pfnEnumProcessModules, errorMessage)
+ || !resolveSymbol(psAPI_Lib, m_hModPSAPI, "GetModuleFileNameExW", &m_pfnGetModuleFileNameExW, errorMessage))
+ return false;
+
+ if(m_pfnEnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) {
+ const unsigned count = cbNeeded / sizeof(HMODULE);
+ for (unsigned i = 0; i < count; i++) {
+ TCHAR szModName[MAX_PATH];
+ if (m_pfnGetModuleFileNameExW(hProcess, hMods[i], szModName, sizeof(szModName))) {
+ if (QString::fromUtf16(szModName) == modulePath) {
+ ::FreeLibrary(m_hModPSAPI);
+ ::CloseHandle(hProcess);
+ return hMods[i];
+ }
+ }
+ }
+ }
+
+ ::CloseHandle(hProcess);
+ return 0;
+}
+
+} // namespace Internal
+} // namespace Debugger
diff --git a/src/plugins/debugger/win/sharedlibraryinjector.h b/src/plugins/debugger/win/sharedlibraryinjector.h
new file mode 100644
index 00000000000..f1356b03f19
--- /dev/null
+++ b/src/plugins/debugger/win/sharedlibraryinjector.h
@@ -0,0 +1,109 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Qt Software Information ([email protected])
+**
+** 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 [email protected].
+**
+**************************************************************************/
+
+//
+// WinANSI - An ANSI implementation for the modern Windows
+//
+// Copyright (C) 2008- Marius Storm-Olsen <[email protected]>
+//
+// Based on work by Robert Kuster in article
+// http://software.rkuster.com/articles/winspy.htm
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+//
+// -------------------------------------------------------------------------------------------------
+
+#ifndef SHAREDLIBRARYINJECTOR_H
+#define SHAREDLIBRARYINJECTOR_H
+
+#include <windows.h>
+#include <QtCore/QString>
+
+namespace Debugger {
+namespace Internal {
+
+/* SharedLibraryInjector: Injects a DLL into a remote process.
+ * Escalates the calling process rights. */
+class SharedLibraryInjector {
+ Q_DISABLE_COPY(SharedLibraryInjector)
+public:
+
+ explicit SharedLibraryInjector(unsigned long remotePid, unsigned long remoteThreadId = 0);
+ ~SharedLibraryInjector();
+
+ void setModulePath(const QString &modulePath);
+ bool hasLoaded(const QString &modulePath = QString());
+
+ // Remote injection, to be used for running processes
+ bool remoteInject(const QString &modulePath, bool waitForThread, QString *errorMessage);
+
+ // Stub injection, to be used before execution starts
+ bool stubInject(const QString &modulePath, unsigned long entryPoint, QString *errorMessage);
+
+ bool unload(const QString &modulePath /* = QString()*/, QString *errorMessage);
+ bool unload(HMODULE hFreeModule, QString *errorMessage);
+
+ void setPid(unsigned long pid);
+ void setThreadId(unsigned long tid);
+
+ static QString findModule(const QString &moduleName);
+ static unsigned long getModuleEntryPoint(const QString &moduleName);
+
+private:
+ bool escalatePrivileges(QString *errorMessage);
+ bool doRemoteInjection(unsigned long pid, HMODULE hFreeModule,
+ const QString &modulePath, bool waitForThread,
+ QString *errorMessage);
+ bool doStubInjection(unsigned long pid, const QString &modulePath,
+ unsigned long entryPoint, QString *errorMessage);
+
+ HMODULE findModuleHandle(const QString &modulePath, QString *errorMessage);
+
+ unsigned long m_processId;
+ unsigned long m_threadId;
+ QString m_modulePath;
+ bool m_hasEscalatedPrivileges;
+};
+
+} // namespace Internal
+} // namespace Debugger
+
+#endif // SHAREDLIBRARYINJECTOR_H
diff --git a/src/plugins/debugger/win/win.pri b/src/plugins/debugger/win/win.pri
index b1edeac9299..a0b1ef45cbb 100644
--- a/src/plugins/debugger/win/win.pri
+++ b/src/plugins/debugger/win/win.pri
@@ -1,5 +1,11 @@
INCLUDEPATH+=$$PWD
SOURCES += $$PWD/peutils.cpp \
- $$PWD/dbgwinutils.cpp
+ $$PWD/dbgwinutils.cpp \
+ $$PWD/sharedlibraryinjector.cpp
+
HEADERS += $$PWD/peutils.h \
- $$PWD/dbgwinutils.h
+ $$PWD/dbgwinutils.h \
+ $$PWD/sharedlibraryinjector.h
+
+# For the Privilege manipulation functions in sharedlibraryinjector.cpp
+LIBS += advapi32.lib