[0.1.x] added : simple "simple debugger"

git-svn-id: https://pykd.svn.codeplex.com/svn@75462 9b283d60-5439-405e-af05-b73fd8c4d996
This commit is contained in:
SND\kernelnet_cp 2012-04-11 16:00:56 +00:00 committed by Mikhail I. Izmestev
parent b16dc51fd0
commit 78fbe47e76
14 changed files with 2288 additions and 0 deletions

View File

@ -0,0 +1,242 @@
# Copyright (c) 2009-2010, Cloud Matrix Pty. Ltd.
# All rights reserved; available under the terms of the BSD License.
"""
PySideKick.Call: helpers for managing function calls
=====================================================
This module defines a collection of helpers for managing function calls in
cooperation with the Qt event loop. We have:
* qCallAfter: call function after current event has been processed
* qCallLater: call function after sleeping for some interval
* qCallInMainThread: (blockingly) call function in the main GUI thread
* qCallInWorkerThread: (nonblockingly) call function in worker thread
There is also a decorator to apply these helpers to all calls to a function:
* qCallUsing(helper): route all calls to a function through the helper
"""
import sys
import thread
import threading
from functools import wraps
import Queue
import PySideKick
from PySideKick import QtCore, qIsMainThread
class qCallAfter(QtCore.QObject):
"""Call the given function on a subsequent iteration of the event loop.
This helper arranges for the given function to be called on a subsequent
iteration of the main event loop. It's most useful inside event handlers,
where you may want to defer some work until after the event has finished
being processed.
The implementation is as a singleton QObject subclass. It maintains a
queue of functions to the called, and posts an event to itself whenever
a new function is queued.
"""
def __init__(self):
super(qCallAfter,self).__init__(None)
self.app = None
self.event_id = QtCore.QEvent.registerEventType()
self.event_type = QtCore.QEvent.Type(self.event_id)
self.pending_func_queue = Queue.Queue()
self.func_queue = Queue.Queue()
def customEvent(self,event):
if event.type() == self.event_type:
self._popCall()
def __call__(self,func,*args,**kwds):
global qCallAfter
# If the app is running, dispatch the event directly.
if self.app is not None:
self.func_queue.put((func,args,kwds))
self._postEvent()
return
# Otherwise, we have some bootstrapping to do!
# Before dispatching, there must be a running app and we must
# be on the same thread as it.
app = QtCore.QCoreApplication.instance()
if app is None or not qIsMainThread():
self.pending_func_queue.put((func,args,kwds))
return
# This is the first call with a running app and from the
# main thread. If it turns out we're not on the main thread,
# replace ourselves with a fresh instance.
if hasattr(self,"thread"):
if self.thread() is not QtCore.QThread.currentThread():
qCallAfter = self.__class__()
else:
self.app = app
else:
self.app = app
# OK, we now have the official qCallAfter instance.
# Flush all pending events.
try:
while True:
(pfunc,pargs,pkwds) = self.pending_func_queue.get(False)
qCallAfter(pfunc,*pargs,**pkwds)
except Queue.Empty:
pass
qCallAfter(func,*args,**kwds)
def _popCall(self):
(func,args,kwds) = self.func_queue.get(False)
func(*args,**kwds)
def _postEvent(self):
event = QtCore.QEvent(self.event_type)
try:
self.app.postEvent(self,event)
except RuntimeError:
# This can happen if the app has been destroyed.
# Immediately empty the queue.
try:
while True:
self._popCall()
except Queue.Empty:
pass
# Optimistically create the singleton instance of qCallAfter.
# If this module is imported from a non-gui thread then this instance will
# eventually be replaced, but usually it'll be the correct one.
qCallAfter = qCallAfter()
class Future(object):
"""Primative "future" class for executing functions in another thread.
Instances of this class represent a compuation sent to another thread.
Call the "get_result" method to wait until completion and get the result
(or raise the exception).
Existing Future objects are recycled to avoid the overhead of allocating
a new lock primitive for each async call.
"""
_READY_INSTANCES = []
def __init__(self):
self.ready = threading.Event()
self.result = None
self.exception = None
@classmethod
def get_or_create(cls):
try:
return cls._READY_INSTANCES.pop(0)
except IndexError:
return cls()
def recycle(self):
self.result = None
self.exception = None
self.ready.clear()
self._READY_INSTANCES.append(self)
def call_function(self,func,*args,**kwds):
try:
self.result = func(*args,**kwds)
except Exception:
self.exception = sys.exc_info()
finally:
self.ready.set()
def get_result(self):
self.ready.wait()
try:
if self.exception is None:
return self.result
else:
raise self.exception[0], \
self.exception[1], \
self.exception[2]
finally:
self.recycle()
def is_ready(self):
return self.read.isSet()
def qCallInMainThread(func,*args,**kwds):
"""Synchronously call the given function in the main thread.
This helper arranges for the given function to be called in the main
event loop, then blocks and waits for the result. It's a simple way to
call functions that manipulate the GUI from a non-GUI thread.
"""
if qIsMainThread():
func(*args,**kwds)
else:
future = Future.get_or_create()
qCallAfter(future.call_function,func,*args,**kwds)
return future.get_result()
def qCallInWorkerThread(func,*args,**kwds):
"""Asynchronously call the given function in a background worker thread.
This helper arranges for the given function to be executed by a background
worker thread. Eventually it'll get a thread pool; for now each task
spawns a new background thread.
If you need to know the result of the function call, this helper returns
a Future object; use f.is_ready() to test whether it's ready and call
f.get_result() to get the return value or raise the exception.
"""
future = Future.get_or_create()
def runit():
future.call_function(func,*args,**kwds)
threading.Thread(target=runit).start()
return future
def qCallLater(interval,func,*args,**kwds):
"""Asynchronously call the given function after a timeout.
This helper is similar to qCallAfter, but it waits at least 'interval'
seconds before executing the function. To cancel the call before the
sleep interval has expired, call 'cancel' on the returned object.
Currently this is a thin wrapper around threading.Timer; eventually it
will be integrated with Qt's own timer mechanisms.
"""
def runit():
qCallAfter(func,*args,**kwds)
t = threading.Timer(interval,runit)
t.start()
return t
def qCallUsing(helper):
"""Function/method decorator to always apply a function call helper.
This decorator can be used to ensure that a function is always called
using one of the qCall helpers. For example, the following function can
be safely called from any thread, as it will transparently apply the
qCallInMainThread helper whenever it is called:
@qCallUsing(qCallInMainThread)
def prompt_for_input(msg):
# ... pop up a dialog, return input
"""
def decorator(func):
@wraps(func)
def wrapper(*args,**kwds):
return helper(func,*args,**kwds)
return wrapper
return decorator

View File

@ -0,0 +1,143 @@
# Copyright (c) 2009-2010, Cloud Matrix Pty. Ltd.
# All rights reserved; available under the terms of the BSD License.
"""
PySideKick.Console: a simple embeddable python shell
=====================================================
This module provides the call QPythonConsole, a python shell that can be
embedded in your GUI.
"""
import sys
from code import InteractiveConsole as _InteractiveConsole
from PySideKick import QtCore, QtGui
try:
from cStringIO import StringIO
except ImportError:
from StringIO import StringIO
class _QPythonConsoleInterpreter(_InteractiveConsole):
"""InteractiveConsole subclass that sends all output to the GUI."""
def __init__(self,ui,locals=None):
_InteractiveConsole.__init__(self,locals)
self.ui = ui
def write(self,data):
if data:
if data[-1] == "\n":
data = data[:-1]
self.ui.output.appendPlainText(data)
def runsource(self,source,filename="<input>",symbol="single"):
old_stdout = sys.stdout
old_stderr = sys.stderr
sys.stdout = sys.stderr = collector = StringIO()
try:
more = _InteractiveConsole.runsource(self,source,filename,symbol)
finally:
if sys.stdout is collector:
sys.stdout = old_stdout
if sys.stderr is collector:
sys.stderr = old_stderr
self.write(collector.getvalue())
return more
class _QPythonConsoleUI(object):
"""UI layout container for QPythonConsole."""
def __init__(self,parent):
if parent.layout() is None:
parent.setLayout(QtGui.QHBoxLayout())
layout = QtGui.QVBoxLayout()
layout.setSpacing(0)
# Output console: a fixed-pitch-font text area.
self.output = QtGui.QPlainTextEdit(parent)
self.output.setReadOnly(True)
self.output.setUndoRedoEnabled(False)
self.output.setMaximumBlockCount(5000)
fmt = QtGui.QTextCharFormat()
fmt.setFontFixedPitch(True)
self.output.setCurrentCharFormat(fmt)
layout.addWidget(self.output)
parent.layout().addLayout(layout)
# Input console, a prompt displated next to a lineedit
layout2 = QtGui.QHBoxLayout()
self.prompt = QtGui.QLabel(parent)
self.prompt.setText(">>> ")
layout2.addWidget(self.prompt)
self.input = QtGui.QLineEdit(parent)
layout2.addWidget(self.input)
layout.addLayout(layout2)
class QPythonConsole(QtGui.QWidget):
"""A simple python console to embed in your GUI.
This widget provides a simple interactive python console that you can
embed in your GUI (e.g. for debugging purposes). Use it like so:
self.debug_window.layout().addWidget(QPythonConsole())
You can customize the variables that are available in the shell by
passing a dict as the "locals" argument.
"""
def __init__(self,parent=None,locals=None):
super(QPythonConsole,self).__init__(parent)
self.ui = _QPythonConsoleUI(self)
self.interpreter = _QPythonConsoleInterpreter(self.ui,locals)
self.ui.input.returnPressed.connect(self._on_enter_line)
self.ui.input.installEventFilter(self)
self.history = []
self.history_pos = 0
def _on_enter_line(self):
line = self.ui.input.text()
self.ui.input.setText("")
self.interpreter.write(self.ui.prompt.text() + line)
more = self.interpreter.push(line)
if line:
self.history.append(line)
self.history_pos = len(self.history)
while len(self.history) > 100:
self.history = self.history[1:]
self.history_pos -= 1
if more:
self.ui.prompt.setText("... ")
else:
self.ui.prompt.setText(">>> ")
def eventFilter(self,obj,event):
if event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key_Up:
self.go_history(-1)
elif event.key() == QtCore.Qt.Key_Down:
self.go_history(1)
return False
def go_history(self,offset):
if offset < 0:
self.history_pos = max(0,self.history_pos + offset)
elif offset > 0:
self.history_pos = min(len(self.history),self.history_pos + offset)
try:
line = self.history[self.history_pos]
except IndexError:
line = ""
self.ui.input.setText(line)
if __name__ == "__main__":
import sys, os
app = QtGui.QApplication(sys.argv)
win = QtGui.QMainWindow()
win.setCentralWidget(QPythonConsole())
win.show()
app.exec_()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
# Copyright (c) 2009-2010, Cloud Matrix Pty. Ltd.
# All rights reserved; available under the terms of the BSD License.
"""
PySideKick: helpful utilities for working with PySide
======================================================
This package is a rather ad-hoc collection of helpers, utilities and custom
widgets for building applications with PySide. So far we have:
* PySideKick.Call: helpers for calling functions in a variety of ways,
e.g. qCallAfter, qCallInMainThread
* PySideKick.Console: a simple interactive console to embed in your
application
* PySideKick.Hatchet: a tool for hacking frozen PySide apps down to size,
by rebuilding PySide with a minimal set of classes
"""
__ver_major__ = 0
__ver_minor__ = 2
__ver_patch__ = 3
__ver_sub__ = ""
__ver_tuple__ = (__ver_major__,__ver_minor__,__ver_patch__,__ver_sub__)
__version__ = "%d.%d.%d%s" % __ver_tuple__
import thread
from PySide import QtCore, QtGui
from PySide.QtCore import Qt
# Older versions of PySide don't expose the 'thread' attribute of QObject.
# In this case, assume the thread importing this module is the main thread.
if hasattr(QtCore.QCoreApplication,"thread"):
def qIsMainThread():
app = QtCore.QCoreApplication.instance()
if app is None:
return False
return QtCore.QThread.currentThread() is app.thread()
else:
_MAIN_THREAD_ID = thread.get_ident()
def qIsMainThread():
return thread.get_ident() == _MAIN_THREAD_ID
# PySideKick.Call needs to create a singleton object in the main gui thread.
# Since this is *usually* the thread that imports this module, loading it here
# will provide a small speedup in the common case.
import PySideKick.Call

View File

View File

@ -0,0 +1,54 @@
from widget import *
class DebuggerController(BaseController):
def __init__(self,dbgCore,mainWindow):
BaseController.__init__(self,dbgCore,mainWindow)
debugMenu = QMenu( "Debug" )
self.breakAction = QAction("Break", debugMenu )
self.breakAction.triggered.connect( self.onBreak )
self.breakAction.setDisabled(True)
debugMenu.addAction( self.breakAction )
self.goAction = QAction("Go", debugMenu )
self.goAction.triggered.connect( self.onGo )
self.goAction.setDisabled(True)
debugMenu.addAction( self.goAction )
self.stepAction = QAction("Step", debugMenu )
self.stepAction.triggered.connect( self.onStep )
self.stepAction.setDisabled(True)
debugMenu.addAction( self.stepAction )
mainWindow.menuBar().addMenu( debugMenu )
def onBreak( self ):
self.dbgCore.breakin()
def onGo( self ):
self.dbgCore.go()
def onStep( self ):
self.dbgCore.step()
def onDbgBreak(self):
self.breakAction.setDisabled(True)
self.goAction.setDisabled(False)
self.stepAction.setDisabled(False)
def onDbgRun(self):
self.breakAction.setDisabled(False)
self.goAction.setDisabled(True)
self.stepAction.setDisabled(True)
def onDbgAttach(self):
self.breakAction.setDisabled(False)
self.goAction.setDisabled(False)
self.stepAction.setDisabled(False)
def onDbgDetach(self):
self.breakAction.setDisabled(False)
self.goAction.setDisabled(False)
self.stepAction.setDisabled(False)

View File

@ -0,0 +1,38 @@
from widget import *
class ProcessController(BaseController):
def __init__(self, dbgCore, mainWindow):
BaseController.__init__(self,dbgCore,mainWindow)
self.openProcessAction = QAction( "Open process...", mainWindow.fileMenu )
self.openProcessAction.triggered.connect(self.onOpenProcess)
mainWindow.fileMenu.addAction(self.openProcessAction)
self.detachProcessAction = QAction( "Detach process", mainWindow.fileMenu )
self.detachProcessAction.triggered.connect(self.onDetachProcess)
self.detachProcessAction.setDisabled(True)
mainWindow.fileMenu.addAction(self.detachProcessAction)
def onOpenProcess(self):
fileDlg = QFileDialog( self.mainWnd )
fileDlg.setNameFilter( "Executable (*.exe)" )
self.dbgCore.openProcess( fileDlg.getOpenFileName()[0] )
def onDetachProcess(self):
self.dbgCore.detachProcess()
def onDbgAttach(self):
self.openProcessAction.setDisabled(True)
self.detachProcessAction.setDisabled(True)
def onDbgDetach(self):
self.openProcessAction.setDisabled(False)
self.detachProcessAction.setDisabled(True)
def onDbgBreak(self):
self.detachProcessAction.setDisabled(False)
def onDbgRun(self):
self.detachProcessAction.setDisabled(True)

40
samples/dbg/dbg.py Normal file
View File

@ -0,0 +1,40 @@
from PySide.QtCore import *
from PySide.QtGui import *
from dbgcore import DbgCore
from settings import settings
class MainForm(QMainWindow):
def __init__(self):
QMainWindow.__init__(self, None)
self.resize( 800, 600 )
self.setWindowTitle("Pykd Debugger Sample")
self.setDockNestingEnabled( True )
self.dbgCore = DbgCore()
self.fileMenu = QMenu( "&File" )
self.menuBar().addMenu( self.fileMenu )
self.viewMenu = QMenu( "View" )
self.menuBar().addMenu( self.viewMenu )
self.widgets = settings["default"](self.dbgCore, self )
self.fileMenu.addAction( "Exit", self.onExit )
def onExit(self):
self.dbgCore.close()
self.close()
def main():
app = QApplication( [] )
mainForm = MainForm()
mainForm.show()
exitres = app.exec_()
if __name__ == "__main__":
main()

70
samples/dbg/dbgcore.py Normal file
View File

@ -0,0 +1,70 @@
import pykd
from PySide.QtCore import QThread
from PySide.QtCore import QObject
from PySide.QtCore import Signal
class DbgThread( QThread ):
def __init__(self, func):
QThread.__init__(self)
self.func = func
def run(self):
self.func()
self.exit()
class DbgCore( QObject ):
targetBreak = Signal()
targetRunning = Signal()
targetAttached = Signal()
targetDetached = Signal()
def close(self):
if self.processOpened:
if self.thread != None:
self.breakin()
def openProcess( self, name ):
pykd.startProcess( name )
self.processOpened = True
self.targetAttached.emit()
self.targetBreak.emit()
def detachProcess(self):
pykd.detachProcess()
self.processOpened = False
self.targetDetached.emit()
def killProcess(self):
pykd.killProcess()
self.processOpened = False
self.targetDetached.emit()
def breakin( self ):
pykd.breakin()
def go( self ):
self.thread = DbgThread( pykd.go )
self.thread.finished.connect( self.onDebugStop )
self.targetRunning.emit()
self.thread.start()
def step( self ):
self.thread = DbgThread( pykd.step )
self.thread.finished.connect( self.onDebugStop )
self.targetRunning.emit()
self.thread.start()
def onDebugStop(self):
self.thread.wait(100)
self.thread = None
self.targetBreak.emit()
def __init__(self):
QObject.__init__(self)
self.thread = None
self.processOpened = False

18
samples/dbg/settings.py Normal file
View File

@ -0,0 +1,18 @@
from commands.processcmd import ProcessController
from commands.dbgcmd import DebuggerController
from widgets.registers import RegisterWidget
from widgets.cmd import CmdWidget
class DefaultSettings:
def __init__( self, dbgcore, parentwnd ):
self.processCmd = ProcessController( dbgcore, parentwnd )
self.debugCmd = DebuggerController( dbgcore, parentwnd )
self.regWidget = RegisterWidget( dbgcore, parentwnd )
self.cmdWidget = CmdWidget( dbgcore, parentwnd )
settings = { "default" : DefaultSettings }

97
samples/dbg/widget.py Normal file
View File

@ -0,0 +1,97 @@
from PySide.QtCore import *
from PySide.QtGui import *
class BaseWidget( QDockWidget ):
def __init__( self, dbgCore, mainWindow, title = "", visible = False ):
QDockWidget.__init__( self )
self.setWindowTitle( title )
self.setVisible( visible )
mainWindow.addDockWidget( Qt.LeftDockWidgetArea, self )
self.dbgCore=dbgCore
self.mainWnd = mainWindow
dbgCore.targetBreak.connect( self.onDbgBreak )
dbgCore.targetRunning.connect( self.onDbgRun )
dbgCore.targetAttached.connect( self.onDbgAttach )
dbgCore.targetDetached.connect( self.onDbgDetach )
def addMenuTriggerAction( self, actionName ):
self.action = QAction( actionName, self.mainWnd )
self.action.triggered.connect(self.onTriggerAction)
self.action.setDisabled( True )
self.mainWnd.viewMenu.addAction(self.action)
def onDbgBreak(self):
pass
def onDbgRun(self):
pass
def onDbgAttach(self):
pass
def onDbgDetach(self):
pass
def onTriggerAction(self):
self.setVisible( not self.isVisible() )
class DebugWidget( BaseWidget ):
def __init__( self, dbgCore, mainWindow, title = "", visible = False ):
BaseWidget.__init__( self, dbgCore, mainWindow, title, visible )
self.action = None
def onDbgAttach(self):
if self.action != None:
self.action.setDisabled( True )
self.setDisabled( True )
def onDbgDetach(self):
if self.action != None:
self.action.setDisabled( True )
self.setDisabled( True )
def onDbgBreak(self):
if self.action != None:
self.action.setDisabled( False )
self.setDisabled( False )
self.updateView()
def onDbgRun(self):
if self.action != None:
self.action.setDisabled( True )
self.setDisabled( True )
def updateView(self):
pass
class BaseController(QObject):
def __init__(self,dbgCore,mainWindow):
QObject.__init__(self,mainWindow)
self.dbgCore=dbgCore
self.mainWnd = mainWindow
dbgCore.targetBreak.connect( self.onDbgBreak )
dbgCore.targetRunning.connect( self.onDbgRun )
dbgCore.targetAttached.connect( self.onDbgAttach )
dbgCore.targetDetached.connect( self.onDbgDetach )
def onDbgBreak(self):
self.onStateChange()
def onDbgRun(self):
self.onStateChange()
def onDbgAttach(self):
self.onStateChange()
def onDbgDetach(self):
self.onStateChange()
def onStateChange(self):
pass

View File

View File

@ -0,0 +1,23 @@
from widget import *
from PySideKick.Console import QPythonConsole
class CmdWidget( DebugWidget ):
def __init__(self, dbgCore, mainWindow, visible = False ):
BaseWidget.__init__( self, dbgCore, mainWindow, "Commands", visible )
self.addMenuTriggerAction( "Commands" )
self.console = QPythonConsole()
self.setWidget( self.console )
self.console.interpreter.push("from pykd import *")

View File

@ -0,0 +1,40 @@
from widget import *
import pykd
class RegisterWidget( DebugWidget ):
def __init__(self, dbgCore, mainWindow, visible = False ):
BaseWidget.__init__( self, dbgCore, mainWindow, "Registers", visible )
self.addMenuTriggerAction( "Registers" )
self.textArea = QTextEdit()
self.setWidget( self.textArea )
def updateView(self):
s = ""
try:
i = 0
while True:
reg = pykd.reg(i)
s += "%s %x ( %d )\r\n" % ( reg.name(), reg, reg )
i += 1
except pykd.BaseException:
pass
self.textArea.setPlainText( s )