Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Tobias Hunger
qt-creator
Commits
d0865ed2
Commit
d0865ed2
authored
Feb 09, 2009
by
Banana Joe
Browse files
Initial revision of the cdbdebugger playground.
parent
6672f89f
Changes
13
Hide whitespace changes
Inline
Side-by-side
tests/manual/cdbdebugger/cdbdebugger.pro
0 → 100644
View file @
d0865ed2
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
tests/manual/cdbdebugger/debugger.cpp
0 → 100644
View file @
d0865ed2
#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);
}
tests/manual/cdbdebugger/debugger.h
0 → 100644
View file @
d0865ed2
#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
;
};
tests/manual/cdbdebugger/main.cpp
0 → 100644
View file @
d0865ed2
#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
();
}
tests/manual/cdbdebugger/mainwindow.cpp
0 → 100644
View file @
d0865ed2
#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
);
}
}
tests/manual/cdbdebugger/mainwindow.h
0 → 100644
View file @
d0865ed2
#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
tests/manual/cdbdebugger/mainwindow.ui
0 → 100644
View file @
d0865ed2
<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" >