From d0865ed2a2ba126c6eb1d30ed17cd0abce7ed097 Mon Sep 17 00:00:00 2001
From: Banana Joe <qtc-committer@nokia.com>
Date: Mon, 9 Feb 2009 10:55:46 +0100
Subject: [PATCH] Initial revision of the cdbdebugger playground.

---
 tests/manual/cdbdebugger/cdbdebugger.pro      |  20 ++
 tests/manual/cdbdebugger/debugger.cpp         | 305 ++++++++++++++++++
 tests/manual/cdbdebugger/debugger.h           |  71 ++++
 tests/manual/cdbdebugger/main.cpp             |  11 +
 tests/manual/cdbdebugger/mainwindow.cpp       |  90 ++++++
 tests/manual/cdbdebugger/mainwindow.h         |  26 ++
 tests/manual/cdbdebugger/mainwindow.ui        | 174 ++++++++++
 tests/manual/cdbdebugger/outputcallback.cpp   |  61 ++++
 tests/manual/cdbdebugger/outputcallback.h     |  30 ++
 .../cdbdebugger/windbgeventcallback.cpp       | 186 +++++++++++
 .../manual/cdbdebugger/windbgeventcallback.h  | 126 ++++++++
 tests/manual/cdbdebugger/windbgthread.cpp     | 150 +++++++++
 tests/manual/cdbdebugger/windbgthread.h       |  49 +++
 13 files changed, 1299 insertions(+)
 create mode 100644 tests/manual/cdbdebugger/cdbdebugger.pro
 create mode 100644 tests/manual/cdbdebugger/debugger.cpp
 create mode 100644 tests/manual/cdbdebugger/debugger.h
 create mode 100644 tests/manual/cdbdebugger/main.cpp
 create mode 100644 tests/manual/cdbdebugger/mainwindow.cpp
 create mode 100644 tests/manual/cdbdebugger/mainwindow.h
 create mode 100644 tests/manual/cdbdebugger/mainwindow.ui
 create mode 100644 tests/manual/cdbdebugger/outputcallback.cpp
 create mode 100644 tests/manual/cdbdebugger/outputcallback.h
 create mode 100644 tests/manual/cdbdebugger/windbgeventcallback.cpp
 create mode 100644 tests/manual/cdbdebugger/windbgeventcallback.h
 create mode 100644 tests/manual/cdbdebugger/windbgthread.cpp
 create mode 100644 tests/manual/cdbdebugger/windbgthread.h

diff --git a/tests/manual/cdbdebugger/cdbdebugger.pro b/tests/manual/cdbdebugger/cdbdebugger.pro
new file mode 100644
index 00000000000..cc9b4c63182
--- /dev/null
+++ b/tests/manual/cdbdebugger/cdbdebugger.pro
@@ -0,0 +1,20 @@
+TEMPLATE = app
+TARGET = 
+DEPENDPATH += .
+INCLUDEPATH += .
+
+DEFINES += 
+
+LIBS += Dbghelp.lib dbgeng.lib
+
+HEADERS += mainwindow.h \
+           debugger.h \
+           outputcallback.h \
+           windbgeventcallback.h \
+           windbgthread.h
+FORMS += mainwindow.ui
+SOURCES += main.cpp mainwindow.cpp \
+           debugger.cpp \
+           outputcallback.cpp \
+           windbgeventcallback.cpp \
+           windbgthread.cpp
diff --git a/tests/manual/cdbdebugger/debugger.cpp b/tests/manual/cdbdebugger/debugger.cpp
new file mode 100644
index 00000000000..14df5688b0b
--- /dev/null
+++ b/tests/manual/cdbdebugger/debugger.cpp
@@ -0,0 +1,305 @@
+#include "debugger.h"
+#include "windbgthread.h"
+#include "outputcallback.h"
+#include "windbgeventcallback.h"
+
+#include <QFileInfo>
+#include <QTimerEvent>
+#include <QDebug>
+
+Debugger::Debugger(QObject* parent)
+:   QObject(parent),
+    m_callbackEvent(this),
+    m_watchTimer(-1),
+    m_hDebuggeeProcess(0)
+{
+    HRESULT hr;
+    hr = DebugCreate( __uuidof(IDebugClient5), reinterpret_cast<void**>(&m_pDebugClient));
+    if (FAILED(hr)) m_pDebugClient = 0;
+    hr = DebugCreate( __uuidof(IDebugControl4), reinterpret_cast<void**>(&m_pDebugControl));
+    if (FAILED(hr)) m_pDebugControl = 0;
+    hr = DebugCreate( __uuidof(IDebugSystemObjects4), reinterpret_cast<void**>(&m_pDebugSystemObjects));
+    if (FAILED(hr)) m_pDebugSystemObjects = 0;
+    hr = DebugCreate( __uuidof(IDebugSymbols3), reinterpret_cast<void**>(&m_pDebugSymbols));
+    if (FAILED(hr)) m_pDebugSymbols = 0;
+    hr = DebugCreate( __uuidof(IDebugRegisters2), reinterpret_cast<void**>(&m_pDebugRegisters));
+    if (FAILED(hr)) m_pDebugRegisters = 0;
+
+    m_pDebugClient->SetOutputCallbacks(&g_outputCallbacks);
+    m_pDebugClient->SetEventCallbacks(&m_callbackEvent);
+}
+
+Debugger::~Debugger()
+{
+    killTimer(m_watchTimer);
+    if (m_pDebugClient)
+        m_pDebugClient->Release();
+    if (m_pDebugControl)
+        m_pDebugControl->Release();
+    if (m_pDebugSystemObjects)
+        m_pDebugSystemObjects->Release();
+    if (m_pDebugSymbols)
+        m_pDebugSymbols->Release();
+    if (m_pDebugRegisters)
+        m_pDebugRegisters->Release();
+}
+
+void Debugger::timerEvent(QTimerEvent* te)
+{
+    if (te->timerId() != m_watchTimer)
+        return;
+
+    HRESULT hr;
+    hr = m_pDebugControl->WaitForEvent(0, 1);
+    switch (hr) {
+        case S_OK:
+            //qDebug() << "S_OK";
+            //hr = m_pDebugControl->SetExecutionStatus(DEBUG_STATUS_BREAK);
+            killTimer(m_watchTimer);
+            m_watchTimer = -1;
+            handleDebugEvent();
+            break;
+        case S_FALSE:
+            //qDebug() << "S_FALSE";
+            break;
+        case E_PENDING:
+            qDebug() << "S_PENDING";
+            break;
+        case E_UNEXPECTED:
+            killTimer(m_watchTimer);
+            m_watchTimer = -1;
+            break;
+        case E_FAIL:
+            qDebug() << "E_FAIL";
+            break;
+        default:
+            qDebug() << "asser welljuh";
+    }
+}
+
+void Debugger::openProcess(const QString& filename)
+{
+    DEBUG_CREATE_PROCESS_OPTIONS dbgopts;
+    memset(&dbgopts, 0, sizeof(dbgopts));
+    dbgopts.CreateFlags = DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS;
+
+    HRESULT hr;
+
+    QFileInfo fi(filename);
+    m_pDebugSymbols->AppendImagePathWide(fi.absolutePath().replace('/','\\').utf16());
+    //m_pDebugSymbols->AppendSymbolPathWide(fi.absolutePath().replace('/','\\').utf16());
+    //m_pDebugSymbols->AppendSymbolPathWide(L"D:\\dev\\qt\\4.4.3\\lib");
+    m_pDebugSymbols->SetSymbolOptions(SYMOPT_CASE_INSENSITIVE | SYMOPT_UNDNAME | SYMOPT_DEBUG | SYMOPT_LOAD_LINES | SYMOPT_OMAP_FIND_NEAREST | SYMOPT_AUTO_PUBLICS);
+    //m_pDebugSymbols->AddSymbolOptions(SYMOPT_CASE_INSENSITIVE | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS | SYMOPT_DEBUG | SYMOPT_LOAD_LINES | SYMOPT_OMAP_FIND_NEAREST | SYMOPT_AUTO_PUBLICS | SYMOPT_NO_IMAGE_SEARCH);
+
+    hr = m_pDebugClient->CreateProcess2Wide(NULL,
+                                            const_cast<PWSTR>(filename.utf16()),
+                                            &dbgopts,
+                                            sizeof(dbgopts),
+                                            NULL,  // TODO: think about the initial directory
+                                            NULL); // TODO: think about setting the environment
+    if (FAILED(hr)) {
+        qWarning("CreateProcess2Wide failed");
+        return;
+    }
+
+    m_watchTimer = startTimer(0);
+}
+
+void Debugger::closeProcess()
+{
+    m_pDebugClient->TerminateCurrentProcess();
+}
+
+void Debugger::breakAtCurrentPosition()
+{
+    if (!m_hDebuggeeProcess)
+        return;
+    if (!DebugBreakProcess(m_hDebuggeeProcess))
+        qWarning("DebugBreakProcess failed.");
+}
+
+void Debugger::continueProcess()
+{
+    m_watchTimer = startTimer(0);
+}
+
+void Debugger::handleDebugEvent()
+{
+    HRESULT hr;
+
+    ULONG numberOfThreads;
+    hr = m_pDebugSystemObjects->GetNumberThreads(&numberOfThreads);
+    const ULONG maxThreadIds = 200;
+    ULONG threadIds[maxThreadIds];
+    ULONG biggestThreadId = qMin(maxThreadIds, numberOfThreads - 1);
+    hr = m_pDebugSystemObjects->GetThreadIdsByIndex(0, biggestThreadId, threadIds, 0);
+    for (ULONG threadId = 0; threadId <= biggestThreadId; ++threadId) {
+        qDebug() << "dumping stack for thread" << threadId;
+
+        m_pDebugSystemObjects->SetCurrentThreadId(threadId);
+
+        ULONG64 frameOffset, instructionOffset, stackOffset;
+        if (FAILED(m_pDebugRegisters->GetFrameOffset2(DEBUG_REGSRC_DEBUGGEE, &frameOffset)) ||
+            FAILED(m_pDebugRegisters->GetInstructionOffset2(DEBUG_REGSRC_DEBUGGEE, &instructionOffset)) ||
+            FAILED(m_pDebugRegisters->GetStackOffset2(DEBUG_REGSRC_DEBUGGEE, &stackOffset)))
+        {
+            frameOffset = instructionOffset = stackOffset = 0;
+        }
+        //frameOffset = instructionOffset = stackOffset = 0;
+
+        const ULONG numFrames = 100;
+        ULONG numFramesFilled = 0;
+        DEBUG_STACK_FRAME frames[numFrames];
+        hr = m_pDebugControl->GetStackTrace(frameOffset, stackOffset, instructionOffset, frames, numFrames, &numFramesFilled);
+        if (FAILED(hr))
+            qDebug() << "GetStackTrace failed";
+
+        const size_t buflen = 1024;
+        WCHAR wszBuf[buflen];
+        for (ULONG i=0; i < numFramesFilled; ++i) {
+            m_pDebugSymbols->GetNameByOffsetWide(frames[i].InstructionOffset, wszBuf, buflen, 0, 0);
+            qDebug() << QString::fromUtf16(wszBuf);
+        }
+
+        //m_pDebugSymbols->GetImagePathWide(wszBuf, buflen, 0);
+        //qDebug() << "ImagePath" << QString::fromUtf16(wszBuf);
+        //m_pDebugSymbols->GetSymbolPathWide(wszBuf, buflen, 0);
+        //qDebug() << "SymbolPath" << QString::fromUtf16(wszBuf);
+
+        //m_pDebugControl->OutputStackTrace(DEBUG_OUTCTL_THIS_CLIENT, 0, 2, DEBUG_STACK_FRAME_ADDRESSES | DEBUG_STACK_COLUMN_NAMES | DEBUG_STACK_FRAME_NUMBERS);
+        //m_pDebugControl->OutputStackTrace(DEBUG_OUTCTL_THIS_CLIENT, frames, numFramesFilled, DEBUG_STACK_SOURCE_LINE);
+    }
+}
+
+void Debugger::handleCreateProcessEvent(DEBUG_EVENT* e)
+{
+    //qDebug() << "CREATE_PROCESS_DEBUG_EVENT";
+    //m_hDebuggeeProcess = e->u.CreateProcessInfo.hProcess;
+    //m_hDebuggeeThread  = e->u.CreateProcessInfo.hThread;
+    //m_hDebuggeeImage   = e->u.CreateProcessInfo.hFile;
+
+    //QFileInfo fi(m_pDbgProcess->processFileName());
+    //BOOL bSuccess;
+    //bSuccess = SymInitialize(m_hDebuggeeProcess, fi.absolutePath().utf16(), FALSE);
+    //if (!bSuccess)
+    //    qWarning("SymInitialize failed");
+    //else {
+    //    SymSetOptions(SYMOPT_DEBUG | SYMOPT_LOAD_LINES | SYMOPT_FAIL_CRITICAL_ERRORS);
+    //    if (!SymLoadModule64(m_hDebuggeeProcess, m_hDebuggeeImage, NULL, NULL, NULL, NULL))
+    //        qDebug() << "SymLoadModule64 failed w/ error code" << GetLastError();
+    //}
+}
+
+void Debugger::handleExceptionEvent(DEBUG_EVENT* e)
+{
+    //BOOL bSuccess;
+    //SuspendThread(m_hDebuggeeThread);
+
+    //CONTEXT context;
+    //memset(&context, 0, sizeof(context));
+    //context.ContextFlags = CONTEXT_ALL;
+    //bSuccess = GetThreadContext(m_hDebuggeeThread, &context);
+    //if (!bSuccess)
+    //    qDebug() << "GetThreadContext failed w/ error code" << GetLastError();
+    //ResumeThread(m_hDebuggeeThread);
+
+    //STACKFRAME64 stackFrame;
+    //stackFrame.AddrPC.Offset = context.Eip;
+    //stackFrame.AddrPC.Mode = AddrModeFlat;
+    //stackFrame.AddrFrame.Offset = context.Ebp;
+    //stackFrame.AddrFrame.Mode = AddrModeFlat;
+    //stackFrame.AddrStack.Offset = context.Esp;
+    //stackFrame.AddrStack.Mode = AddrModeFlat;
+    //m_currentStackTrace.clear();
+
+    //do {
+    //    StackFrame sf;
+    //    bSuccess = StackWalk64(IMAGE_FILE_MACHINE_I386, m_hDebuggeeProcess, m_hDebuggeeThread, &stackFrame,
+    //                           &context, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL);
+    //    if (bSuccess) {
+    //        qDebug() << "StackWalk";
+    //        IMAGEHLP_MODULE64 moduleInfo;
+    //        moduleInfo.SizeOfStruct = sizeof(moduleInfo);
+    //        if (SymGetModuleInfo64(m_hDebuggeeProcess, stackFrame.AddrPC.Offset, &moduleInfo))
+    //            qDebug() << "SymGetModuleInfo64 success!";
+    //        else
+    //            qDebug() << "SymGetModuleInfo64 failed w/ error code" << GetLastError();
+    //    }
+
+    //    if (stackFrame.AddrPC.Offset) {
+    //        DWORD64 dwDisplacement;
+    //        const size_t bufferSize = 200;
+    //        class MySymbol : public IMAGEHLP_SYMBOL64
+    //        {
+    //        public:
+    //        private:
+    //            char buffer[bufferSize];
+    //        };
+    //        MySymbol img;
+    //        ZeroMemory(&img, sizeof(img));
+    //        img.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
+    //        img.MaxNameLength = bufferSize;
+
+    //        BOOL bSuccess;
+    //        bSuccess = SymGetSymFromAddr64(m_hDebuggeeProcess,
+    //                                       stackFrame.AddrPC.Offset,
+    //                                       &dwDisplacement,
+    //                                       &img);
+    //        if (bSuccess) {
+    //            qDebug() << "SymGetSymFromAddr64:" << img.Name;
+    //            sf.symbol = QString::fromLocal8Bit(img.Name);
+    //        }
+    //        else
+    //            qDebug() << "SymGetSymFromAddr64 failed w/ error code" << GetLastError();
+    //    }
+
+    //    if (stackFrame.AddrPC.Offset) {
+    //        DWORD dwDisplacement;
+    //        IMAGEHLP_LINE64 line;
+    //        ZeroMemory(&line, sizeof(line));
+    //        line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
+
+    //        BOOL bSuccess;
+    //        bSuccess = SymGetLineFromAddr64(m_hDebuggeeProcess,
+    //                                        stackFrame.AddrPC.Offset,
+    //                                        &dwDisplacement,
+    //                                        &line);
+    //        if (bSuccess) {
+    //            //qDebug() << "SymGetLineFromAddr64:" << QString::fromUtf16((ushort*)line.FileName) << line.LineNumber;
+    //            sf.filename = QString::fromUtf16((ushort*)line.FileName);
+    //            sf.line = line.LineNumber;
+    //        } else
+    //            qDebug() << "SymGetLineFromAddr64 failed w/ error code" << GetLastError();
+
+    //        m_currentStackTrace.append(sf);                
+    //    }
+    //} while (bSuccess);
+
+    //emit debuggeePaused();
+}
+
+void Debugger::handleOutputDebugStringEvent(DEBUG_EVENT* e)
+{
+    //qDebug() << "OUTPUT_DEBUG_STRING_EVENT";
+    //BOOL bSuccess;
+    //SIZE_T nNumberOfBytesRead;
+    //void* buffer;
+    //QString result;
+    //if (e->u.DebugString.fUnicode) {
+    //    buffer = malloc(e->u.DebugString.nDebugStringLength * sizeof(WCHAR));
+    //} else {
+    //    buffer = malloc(e->u.DebugString.nDebugStringLength * sizeof(char));
+    //}
+
+    //bSuccess = ReadProcessMemory(m_hDebuggeeProcess, e->u.DebugString.lpDebugStringData,
+    //                             buffer, e->u.DebugString.nDebugStringLength, &nNumberOfBytesRead);
+    //if (bSuccess) {
+    //    if (e->u.DebugString.fUnicode)
+    //        result = QString::fromUtf16(reinterpret_cast<ushort*>(buffer), nNumberOfBytesRead);
+    //    else
+    //        result = QString::fromLocal8Bit(reinterpret_cast<char*>(buffer), nNumberOfBytesRead);
+    //    emit debugOutput(result);
+    //}
+    //free(buffer);
+}
diff --git a/tests/manual/cdbdebugger/debugger.h b/tests/manual/cdbdebugger/debugger.h
new file mode 100644
index 00000000000..f170caff67e
--- /dev/null
+++ b/tests/manual/cdbdebugger/debugger.h
@@ -0,0 +1,71 @@
+#pragma once
+
+#include "windbgeventcallback.h"
+
+#include <QObject>
+#include <QVector>
+
+#define DBGHELP_TRANSLATE_TCHAR
+#include <Dbghelp.h>
+
+class WinDbgThread;
+
+class Debugger : public QObject
+{
+    Q_OBJECT
+public:
+    Debugger(QObject* parent = 0);
+    ~Debugger();
+
+    void openProcess(const QString& filename);
+    void closeProcess();
+    void breakAtCurrentPosition();
+    void continueProcess();
+
+    struct StackFrame
+    {
+        QString symbol;
+        QString filename;
+        uint    line;
+    };
+
+    typedef QVector<StackFrame> StackTrace;
+    StackTrace stackTrace() { return m_currentStackTrace; }
+
+signals:
+    void debugOutput(const QString&);
+    void debuggeePaused();
+
+protected:
+    void timerEvent(QTimerEvent*);
+
+private:
+    void handleDebugEvent();
+    void handleCreateProcessEvent(DEBUG_EVENT* e);
+    void handleExceptionEvent(DEBUG_EVENT* e);
+    void handleOutputDebugStringEvent(DEBUG_EVENT* e);
+
+private:
+    HANDLE           m_hDebuggeeProcess;
+    HANDLE           m_hDebuggeeThread;
+    HANDLE           m_hDebuggeeImage;
+    StackTrace       m_currentStackTrace;
+    //DWORD64 m_dwModuleBaseAddress;
+
+    int                   m_watchTimer;
+    IDebugClient5*        m_pDebugClient;
+    IDebugControl4*       m_pDebugControl;
+    IDebugSystemObjects4* m_pDebugSystemObjects;
+    IDebugSymbols3*       m_pDebugSymbols;
+    IDebugRegisters2*     m_pDebugRegisters;
+    WinDbgEventCallback   m_callbackEvent;
+
+    //struct ThreadInfo
+    //{
+    //    ULONG64 handle, dataOffset, startOffset;
+    //};
+
+    //QVector<ThreadInfo>   m_threadlist;
+
+    friend class WinDbgEventCallback;
+};
diff --git a/tests/manual/cdbdebugger/main.cpp b/tests/manual/cdbdebugger/main.cpp
new file mode 100644
index 00000000000..fb18f90e12e
--- /dev/null
+++ b/tests/manual/cdbdebugger/main.cpp
@@ -0,0 +1,11 @@
+#include "mainwindow.h"
+#include <QApplication>
+
+int main(int argc, char* argv[])
+{
+    QApplication app(argc, argv);
+    MainWindow mw;
+    if (argc >= 2) mw.setDebuggee(argv[1]);
+    mw.show();
+    return app.exec();
+}
diff --git a/tests/manual/cdbdebugger/mainwindow.cpp b/tests/manual/cdbdebugger/mainwindow.cpp
new file mode 100644
index 00000000000..3cce2118922
--- /dev/null
+++ b/tests/manual/cdbdebugger/mainwindow.cpp
@@ -0,0 +1,90 @@
+#include "mainwindow.h"
+#include <QFileDialog>
+#include <QTextStream>
+#include <QDebug>
+
+MainWindow::MainWindow()
+:   QMainWindow(0, 0)
+{
+    setupUi(this);
+
+    connect(&m_debugger, SIGNAL(debugOutput(const QString&)), SLOT(appendOutput(const QString&)));
+    connect(&m_debugger, SIGNAL(debuggeePaused()), SLOT(onDebuggeePaused()));    
+}
+
+void MainWindow::setDebuggee(const QString& filename)
+{
+    m_debugger.openProcess(filename);
+}
+
+void MainWindow::on_actionOpen_triggered()
+{
+    QString exeName;
+    exeName = QFileDialog::getOpenFileName(this, "Open Executable", ".", "*.exe");
+    if (!exeName.isNull())
+        m_debugger.openProcess(exeName);
+}
+
+void MainWindow::on_actionExit_triggered()
+{
+    close();
+}
+
+void MainWindow::on_actionBreak_triggered()
+{
+    m_debugger.breakAtCurrentPosition();
+}
+
+void MainWindow::on_actionRun_triggered()
+{
+    m_debugger.continueProcess();
+}
+
+void MainWindow::on_lstStack_itemClicked(QListWidgetItem* item)
+{
+    Debugger::StackFrame sf = m_stackTrace[ lstStack->row(item) ];
+    QFile f(sf.filename);
+    if (!f.exists())
+        return;
+
+    f.open(QFile::ReadOnly);
+    QTextStream ts(&f);
+    int cursorPos = 0;
+    int currentLine = 0;
+    QString fullText;
+    do {
+        QString strLine = ts.readLine();
+        currentLine++;
+        if (currentLine < sf.line)
+            cursorPos += strLine.length();
+        fullText.append(strLine + "\n");
+    } while (!ts.atEnd());
+    codeWindow->setPlainText(fullText);
+
+    //QList<QTextEdit::ExtraSelection> extraSelections;
+    //extraSelections.append(QTextEdit::ExtraSelection());
+
+    //QTextEdit::ExtraSelection& exsel = extraSelections.first();
+    //exsel.cursor.setPosition(cursorPos, QTextCursor::MoveAnchor);
+    //exsel.cursor.select(QTextCursor::LineUnderCursor);
+    //exsel.format.setBackground(Qt::red);
+    //exsel.format.setFontUnderline(true);
+    //codeWindow->setExtraSelections(extraSelections);
+}
+
+void MainWindow::appendOutput(const QString& str)
+{
+    teOutput->setPlainText(teOutput->toPlainText() + str);
+}
+
+void MainWindow::onDebuggeePaused()
+{
+    lstStack->clear();
+    m_stackTrace = m_debugger.stackTrace();
+    foreach (Debugger::StackFrame sf, m_stackTrace) {
+        QString str = sf.symbol;
+        if (!sf.filename.isEmpty())
+            str.append(" at " + sf.filename + ":" + QString::number(sf.line));
+        lstStack->addItem(str);
+    }
+}
diff --git a/tests/manual/cdbdebugger/mainwindow.h b/tests/manual/cdbdebugger/mainwindow.h
new file mode 100644
index 00000000000..9831b508bbd
--- /dev/null
+++ b/tests/manual/cdbdebugger/mainwindow.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include "ui_mainwindow.h"
+#include "debugger.h"
+
+class MainWindow : public QMainWindow, private Ui_MainWindow
+{
+    Q_OBJECT
+public:
+    MainWindow();
+
+    void setDebuggee(const QString& filename);
+
+private slots:
+    void on_actionOpen_triggered();
+    void on_actionExit_triggered();
+    void on_actionBreak_triggered();
+    void on_actionRun_triggered();
+    void on_lstStack_itemClicked(QListWidgetItem*);
+    void appendOutput(const QString&);
+    void onDebuggeePaused();
+
+private:
+    Debugger    m_debugger;
+    Debugger::StackTrace m_stackTrace;
+};
\ No newline at end of file
diff --git a/tests/manual/cdbdebugger/mainwindow.ui b/tests/manual/cdbdebugger/mainwindow.ui
new file mode 100644
index 00000000000..56b0706f2d2
--- /dev/null
+++ b/tests/manual/cdbdebugger/mainwindow.ui
@@ -0,0 +1,174 @@
+<ui version="4.0" >
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow" >
+  <property name="geometry" >
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>599</width>
+    <height>606</height>
+   </rect>
+  </property>
+  <property name="windowTitle" >
+   <string>MainWindow</string>
+  </property>
+  <widget class="QWidget" name="centralwidget" >
+   <layout class="QVBoxLayout" name="verticalLayout" >
+    <item>
+     <widget class="QPlainTextEdit" name="codeWindow" >
+      <property name="sizePolicy" >
+       <sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
+        <horstretch>0</horstretch>
+        <verstretch>1</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="undoRedoEnabled" >
+       <bool>false</bool>
+      </property>
+      <property name="lineWrapMode" >
+       <enum>QPlainTextEdit::NoWrap</enum>
+      </property>
+      <property name="readOnly" >
+       <bool>true</bool>
+      </property>
+      <property name="textInteractionFlags" >
+       <set>Qt::NoTextInteraction</set>
+      </property>
+     </widget>
+    </item>
+    <item>
+     <widget class="QTabWidget" name="tabWidget" >
+      <property name="sizePolicy" >
+       <sizepolicy vsizetype="Expanding" hsizetype="Expanding" >
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <property name="currentIndex" >
+       <number>1</number>
+      </property>
+      <widget class="QWidget" name="tab" >
+       <attribute name="title" >
+        <string>Threads</string>
+       </attribute>
+       <layout class="QHBoxLayout" name="horizontalLayout" >
+        <item>
+         <widget class="QListWidget" name="lstThreads" />
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="tab_2" >
+       <attribute name="title" >
+        <string>Stack</string>
+       </attribute>
+       <layout class="QHBoxLayout" name="horizontalLayout_2" >
+        <item>
+         <widget class="QListWidget" name="lstStack" />
+        </item>
+       </layout>
+      </widget>
+      <widget class="QWidget" name="tab_3" >
+       <attribute name="title" >
+        <string>Output</string>
+       </attribute>
+       <layout class="QHBoxLayout" name="horizontalLayout_3" >
+        <item>
+         <widget class="QPlainTextEdit" name="teOutput" />
+        </item>
+       </layout>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QMenuBar" name="menubar" >
+   <property name="geometry" >
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>599</width>
+     <height>21</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menu_Debug" >
+    <property name="title" >
+     <string>&amp;Debug</string>
+    </property>
+    <addaction name="actionRun" />
+    <addaction name="actionBreak" />
+    <addaction name="actionStepOver" />
+    <addaction name="actionStopDebugging" />
+    <addaction name="actionStepInto" />
+   </widget>
+   <widget class="QMenu" name="menu_File" >
+    <property name="title" >
+     <string>&amp;File</string>
+    </property>
+    <addaction name="actionOpen" />
+    <addaction name="actionClose" />
+    <addaction name="separator" />
+    <addaction name="actionExit" />
+   </widget>
+   <addaction name="menu_File" />
+   <addaction name="menu_Debug" />
+  </widget>
+  <widget class="QStatusBar" name="statusbar" />
+  <action name="actionOpen" >
+   <property name="text" >
+    <string>&amp;Open...</string>
+   </property>
+   <property name="shortcut" >
+    <string>Ctrl+O</string>
+   </property>
+  </action>
+  <action name="actionClose" >
+   <property name="text" >
+    <string>&amp;Close</string>
+   </property>
+  </action>
+  <action name="actionExit" >
+   <property name="text" >
+    <string>E&amp;xit</string>
+   </property>
+  </action>
+  <action name="actionRun" >
+   <property name="text" >
+    <string>&amp;Run</string>
+   </property>
+   <property name="shortcut" >
+    <string>F5</string>
+   </property>
+  </action>
+  <action name="actionBreak" >
+   <property name="text" >
+    <string>&amp;Break</string>
+   </property>
+  </action>
+  <action name="actionStepOver" >
+   <property name="text" >
+    <string>Step over</string>
+   </property>
+   <property name="shortcut" >
+    <string>F10</string>
+   </property>
+  </action>
+  <action name="actionStepInto" >
+   <property name="text" >
+    <string>Step into</string>
+   </property>
+   <property name="shortcut" >
+    <string>F11</string>
+   </property>
+  </action>
+  <action name="actionStopDebugging" >
+   <property name="text" >
+    <string>Stop debugging</string>
+   </property>
+   <property name="shortcut" >
+    <string>Shift+F5</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tests/manual/cdbdebugger/outputcallback.cpp b/tests/manual/cdbdebugger/outputcallback.cpp
new file mode 100644
index 00000000000..4a0ca81bec1
--- /dev/null
+++ b/tests/manual/cdbdebugger/outputcallback.cpp
@@ -0,0 +1,61 @@
+#include <stdio.h>
+#include <windows.h>
+#include <dbgeng.h>
+
+#include "outputcallback.h"
+
+WinDbgOutputCallback g_outputCallbacks;
+
+STDMETHODIMP
+WinDbgOutputCallback::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)
+WinDbgOutputCallback::AddRef(
+    THIS
+    )
+{
+    // This class is designed to be static so
+    // there's no true refcount.
+    return 1;
+}
+
+STDMETHODIMP_(ULONG)
+WinDbgOutputCallback::Release(
+    THIS
+    )
+{
+    // This class is designed to be static so
+    // there's no true refcount.
+    return 0;
+}
+
+STDMETHODIMP
+WinDbgOutputCallback::Output(
+    THIS_
+    IN ULONG Mask,
+    IN PCSTR Text
+    )
+{
+    UNREFERENCED_PARAMETER(Mask);
+    fputs(Text, stdout);
+    return S_OK;
+}
diff --git a/tests/manual/cdbdebugger/outputcallback.h b/tests/manual/cdbdebugger/outputcallback.h
new file mode 100644
index 00000000000..73004c7be67
--- /dev/null
+++ b/tests/manual/cdbdebugger/outputcallback.h
@@ -0,0 +1,30 @@
+#ifndef __OUT_HPP__
+#define __OUT_HPP__
+
+class WinDbgOutputCallback : public IDebugOutputCallbacks
+{
+public:
+    // 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 PCSTR Text
+        );
+};
+
+extern WinDbgOutputCallback g_outputCallbacks;
+
+#endif // #ifndef __OUT_HPP__
diff --git a/tests/manual/cdbdebugger/windbgeventcallback.cpp b/tests/manual/cdbdebugger/windbgeventcallback.cpp
new file mode 100644
index 00000000000..39826f99a59
--- /dev/null
+++ b/tests/manual/cdbdebugger/windbgeventcallback.cpp
@@ -0,0 +1,186 @@
+#include "windbgeventcallback.h"
+#include "debugger.h"
+
+STDMETHODIMP
+WinDbgEventCallback::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)
+WinDbgEventCallback::AddRef(
+    THIS
+    )
+{
+    // This class is designed to be static so
+    // there's no true refcount.
+    return 1;
+}
+
+STDMETHODIMP_(ULONG)
+WinDbgEventCallback::Release(
+    THIS
+    )
+{
+    // This class is designed to be static so
+    // there's no true refcount.
+    return 0;
+}
+
+STDMETHODIMP WinDbgEventCallback::GetInterestMask(
+    THIS_
+    __out PULONG Mask
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::Breakpoint(
+    THIS_
+    __in PDEBUG_BREAKPOINT Bp
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::Exception(
+    THIS_
+    __in PEXCEPTION_RECORD64 Exception,
+    __in ULONG FirstChance
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::CreateThread(
+    THIS_
+    __in ULONG64 Handle,
+    __in ULONG64 DataOffset,
+    __in ULONG64 StartOffset
+    )
+{
+    //Debugger::ThreadInfo ti;
+    //ti.handle = Handle;
+    //ti.dataOffset = DataOffset;
+    //ti.startOffset = StartOffset;
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::ExitThread(
+    THIS_
+    __in ULONG ExitCode
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::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
+    )
+{
+    m_pDebugger->m_hDebuggeeProcess = (HANDLE)Handle;
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::ExitProcess(
+    THIS_
+    __in ULONG ExitCode
+    )
+{
+    m_pDebugger->m_hDebuggeeProcess = 0;
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::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
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::UnloadModule(
+    THIS_
+    __in_opt PCSTR ImageBaseName,
+    __in ULONG64 BaseOffset
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::SystemError(
+    THIS_
+    __in ULONG Error,
+    __in ULONG Level
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::SessionStatus(
+    THIS_
+    __in ULONG Status
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::ChangeDebuggeeState(
+    THIS_
+    __in ULONG Flags,
+    __in ULONG64 Argument
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::ChangeEngineState(
+    THIS_
+    __in ULONG Flags,
+    __in ULONG64 Argument
+    )
+{
+    return S_OK;
+}
+
+STDMETHODIMP WinDbgEventCallback::ChangeSymbolState(
+    THIS_
+    __in ULONG Flags,
+    __in ULONG64 Argument
+    )
+{
+    return S_OK;
+}
diff --git a/tests/manual/cdbdebugger/windbgeventcallback.h b/tests/manual/cdbdebugger/windbgeventcallback.h
new file mode 100644
index 00000000000..b405f77014a
--- /dev/null
+++ b/tests/manual/cdbdebugger/windbgeventcallback.h
@@ -0,0 +1,126 @@
+#pragma once
+
+#include <windows.h>
+#include <dbgeng.h>
+
+class Debugger;
+
+class WinDbgEventCallback : public IDebugEventCallbacks
+{
+public:
+    WinDbgEventCallback(Debugger* dbg)
+        : m_pDebugger(dbg)
+    {}
+
+    // 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
+        );
+
+    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
+        );
+
+    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:
+    Debugger*   m_pDebugger;
+};
+
diff --git a/tests/manual/cdbdebugger/windbgthread.cpp b/tests/manual/cdbdebugger/windbgthread.cpp
new file mode 100644
index 00000000000..a21014216eb
--- /dev/null
+++ b/tests/manual/cdbdebugger/windbgthread.cpp
@@ -0,0 +1,150 @@
+#include "windbgthread.h"
+#include <QDebug>
+
+#define DBGHELP_TRANSLATE_TCHAR
+#include <Dbghelp.h>
+
+WinDbgThread::WinDbgThread(QObject* parent)
+:   QThread(parent),
+    m_state(Idle)
+{
+}
+
+WinDbgThread::~WinDbgThread()
+{
+    stopProcess();
+}
+
+void WinDbgThread::startProcess(const QString& filename)
+{
+    stopProcess();
+    m_bOwnsProcess = true;
+    m_processFileName = filename;
+    m_pi.dwProcessId = 0;
+    QThread::start();
+}
+
+void WinDbgThread::stopProcess()
+{
+    if (!QThread::isRunning())
+        return;
+
+    switch (m_state)
+    {
+    case ProcessRunning:
+        if (m_bOwnsProcess) {
+            m_bOwnsProcess = false; // don't terminate in the loop again
+            TerminateProcess(m_pi.hProcess, 0);
+        }
+        // don't break here
+    case ProcessPaused:
+        m_bAbortEventPollingLoop = true;
+        resume();
+        break;
+    }
+    QThread::wait(5000);
+    if (QThread::isRunning()) {
+        qWarning("WinDbgThread still running... terminating!");
+        QThread::terminate();
+    }
+}
+
+void WinDbgThread::attachToProcess(DWORD processId)
+{
+    m_bOwnsProcess = false;
+    m_processFileName = QString();
+    m_pi.dwProcessId = processId;
+    QThread::start();
+}
+
+void WinDbgThread::run()
+{
+    qDebug() << "WinDbgThread started";
+    // start process or attach process
+    if (m_bOwnsProcess) {
+        // create new process
+        internalStartProcess();
+    } else {
+        // attach to process
+        qWarning("attach to process not yet implemented");
+        return;
+    }
+
+    m_hThisThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, GetCurrentThreadId());
+    if (!m_hThisThread) {
+        qWarning("WinDbgThread: can't open thread handle");
+        return;
+    }
+
+    DEBUG_EVENT debugEvent;
+    m_bAbortEventPollingLoop = false;
+    while (WaitForDebugEvent(&debugEvent, INFINITE)) {
+        setState(ProcessPaused);
+        emit debugEventOccured(&debugEvent);
+        suspend();
+        if (m_bAbortEventPollingLoop)
+            break;
+        ContinueDebugEvent(debugEvent.dwProcessId, debugEvent.dwThreadId, DBG_CONTINUE);
+        setState(ProcessRunning);
+    }
+
+    setState(Idle);
+    if (m_bOwnsProcess) {
+        TerminateProcess(m_pi.hProcess, 0);
+    }
+    CloseHandle(m_pi.hProcess);
+    CloseHandle(m_pi.hThread);
+    CloseHandle(m_hThisThread);
+
+    qDebug() << "WinDbgThread finished";
+}
+
+void WinDbgThread::continueProcess()
+{
+    if (m_state == ProcessPaused)
+        resume();
+}
+
+void WinDbgThread::pauseProcess()
+{
+    if (m_state == ProcessRunning)
+        DebugBreakProcess(m_pi.hProcess);
+}
+
+void WinDbgThread::internalStartProcess()
+{
+    BOOL bSuccess;
+
+    STARTUPINFO si;
+    ZeroMemory(&si, sizeof(si));
+    si.cb = sizeof(si);
+    si.wShowWindow = TRUE;
+
+    ZeroMemory(&m_pi, sizeof(m_pi));
+
+    DWORD dwCreationFlags = DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS;
+    bSuccess = CreateProcess(m_processFileName.utf16(), NULL, NULL, NULL, FALSE, 
+                             dwCreationFlags,
+                             NULL, NULL, &si, &m_pi
+                            );
+
+    if (bSuccess)
+        setState(ProcessRunning);
+    else
+        setState(Idle);
+}
+
+void WinDbgThread::setState(State s)
+{
+    m_state = s;
+}
+
+void WinDbgThread::suspend()
+{
+    SuspendThread(m_hThisThread);
+}
+
+void WinDbgThread::resume()
+{
+    ResumeThread(m_hThisThread);
+}
diff --git a/tests/manual/cdbdebugger/windbgthread.h b/tests/manual/cdbdebugger/windbgthread.h
new file mode 100644
index 00000000000..2e34fae8923
--- /dev/null
+++ b/tests/manual/cdbdebugger/windbgthread.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <QThread>
+#include <windows.h>
+
+class WinDbgThread : protected QThread
+{
+    Q_OBJECT
+public:
+    WinDbgThread(QObject* parent = 0);
+    ~WinDbgThread();
+
+    void startProcess(const QString& filename);
+    void attachToProcess(DWORD processId);
+    void continueProcess();
+    void pauseProcess();
+    void stopProcess();
+    const QString& processFileName() { return m_processFileName; }
+
+    QObject* asQObject() { return this; }
+    //using QThread::isRunning;
+
+signals:
+    void debugEventOccured(void*);
+
+protected:
+    void run();
+
+private:
+    void internalStartProcess();
+    void suspend();
+    void resume();
+
+    enum State {
+        Idle,
+        ProcessRunning,
+        ProcessPaused
+    };
+
+    void setState(State s);
+
+private:
+    State               m_state;
+    QString             m_processFileName;
+    HANDLE              m_hThisThread;
+    PROCESS_INFORMATION m_pi;
+    bool                m_bOwnsProcess;
+    bool                m_bAbortEventPollingLoop;
+};
-- 
GitLab