mirror of
https://github.com/wiiu-env/gdbstub_plugin.git
synced 2024-11-22 03:39:15 +01:00
1229 lines
33 KiB
Python
1229 lines
33 KiB
Python
|
|
from PyQt5.QtWidgets import *
|
|
from PyQt5.QtGui import *
|
|
from PyQt5.QtCore import *
|
|
import socket
|
|
import struct
|
|
import string
|
|
import sys
|
|
import os
|
|
|
|
import disassemble
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Signals
|
|
"""""""""""""""""""""""""""
|
|
|
|
class EventHolder(QObject):
|
|
Connected = pyqtSignal()
|
|
Closed = pyqtSignal()
|
|
BreakPointChanged = pyqtSignal()
|
|
Exception = pyqtSignal(object)
|
|
Continue = pyqtSignal(object)
|
|
Step = pyqtSignal(object)
|
|
events = EventHolder()
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Debugger client
|
|
"""""""""""""""""""""""""""
|
|
|
|
class ExceptionState:
|
|
|
|
exceptionNames = ["DSI", "ISI", "Program"]
|
|
|
|
def __init__(self, message):
|
|
#Convert tuple to list to make it mutable
|
|
data = message.data
|
|
self.gpr = list(struct.unpack_from(">32I", data, 8))
|
|
self.cr, self.lr, self.ctr, self.xer = struct.unpack_from(">4I", data, 0x88)
|
|
self.srr0, self.srr1, self.ex0, self.ex1 = struct.unpack_from(">4I", data, 0x98)
|
|
self.fpr = list(struct.unpack_from(">32d", data, 0xB8))
|
|
self.gqr = list(struct.unpack_from(">8I", data, 0x1BC))
|
|
self.psf = list(struct.unpack_from(">32d", data, 0x1E0))
|
|
|
|
self.thread = message.arg
|
|
|
|
self.exceptionName = self.exceptionNames[message.type]
|
|
|
|
self.stepping = False
|
|
|
|
def isBreakPoint(self):
|
|
return self.exceptionName == "Program" and self.srr1 & 0x20000
|
|
|
|
def isFatal(self):
|
|
return not self.isBreakPoint()
|
|
|
|
|
|
class Message:
|
|
#Server messages
|
|
DSI = 0
|
|
ISI = 1
|
|
Program = 2
|
|
|
|
#Client messages
|
|
Continue = 0
|
|
Step = 1
|
|
StepOver = 2
|
|
|
|
def __init__(self, type, data, arg):
|
|
self.type = type
|
|
self.data = data
|
|
self.arg = arg
|
|
|
|
|
|
class Thread:
|
|
|
|
cores = {
|
|
1: "Core 0",
|
|
2: "Core 1",
|
|
4: "Core 2"
|
|
}
|
|
|
|
def __init__(self, data, offs=0):
|
|
self.handle = struct.unpack_from(">I", data, offs)[0]
|
|
self.core = self.cores[struct.unpack_from(">I", data, offs + 4)[0]]
|
|
self.priority = struct.unpack_from(">I", data, offs + 8)[0]
|
|
self.stackBase = struct.unpack_from(">I", data, offs + 12)[0]
|
|
self.stackEnd = struct.unpack_from(">I", data, offs + 16)[0]
|
|
self.entryPoint = struct.unpack_from(">I", data, offs + 20)[0]
|
|
self.namelen = struct.unpack_from(">I", data, offs + 24)[0]
|
|
self.name = data[offs + 28 : offs + 28 + self.namelen].decode("shift-jis")
|
|
|
|
|
|
class Module:
|
|
def __init__(self, data, offs=0):
|
|
self.textAddr = struct.unpack_from(">I", data, offs)[0]
|
|
self.textSize = struct.unpack_from(">I", data, offs + 4)[0]
|
|
self.dataAddr = struct.unpack_from(">I", data, offs + 8)[0]
|
|
self.dataSize = struct.unpack_from(">I", data, offs + 12)[0]
|
|
self.entryPoint = struct.unpack_from(">I", data, offs + 16)[0]
|
|
|
|
self.namelen = struct.unpack_from(">I", data, offs + 20)[0]
|
|
self.name = data[offs + 24 : offs + 24 + self.namelen].decode("ascii")
|
|
|
|
|
|
COMMAND_CLOSE = 0
|
|
COMMAND_READ = 1
|
|
COMMAND_WRITE = 2
|
|
COMMAND_WRITE_CODE = 3
|
|
COMMAND_GET_MODULE_NAME = 4
|
|
COMMAND_GET_MODULE_LIST = 5
|
|
COMMAND_GET_THREAD_LIST = 6
|
|
COMMAND_GET_STACK_TRACE = 7
|
|
COMMAND_TOGGLE_BREAKPOINT = 8
|
|
COMMAND_POKE_REGISTERS = 9
|
|
COMMAND_RECEIVE_MESSAGES = 10
|
|
COMMAND_SEND_MESSAGE = 11
|
|
COMMAND_DISASM = 12
|
|
|
|
class Debugger:
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.reset()
|
|
|
|
self.messageHandlers = {
|
|
Message.DSI: self.handleException,
|
|
Message.ISI: self.handleException,
|
|
Message.Program: self.handleException
|
|
}
|
|
|
|
def reset(self):
|
|
self.connected = False
|
|
self.breakpoints = []
|
|
self.trapEvents = []
|
|
self.crashEvents = []
|
|
self.threads = []
|
|
|
|
def connect(self, host):
|
|
self.s = socket.socket()
|
|
self.s.settimeout(4)
|
|
self.s.connect((host, 1560))
|
|
self.connected = True
|
|
events.Connected.emit()
|
|
|
|
def handleClose(self):
|
|
self.reset()
|
|
events.Closed.emit()
|
|
|
|
def close(self):
|
|
self.sendbyte(COMMAND_CLOSE)
|
|
self.s.close()
|
|
self.handleClose()
|
|
|
|
def read(self, addr, num):
|
|
self.sendbyte(COMMAND_READ)
|
|
self.sendall(struct.pack(">II", addr, num))
|
|
data = self.recvall(num)
|
|
return data
|
|
|
|
def disasm(self, addr, num):
|
|
self.sendbyte(COMMAND_DISASM)
|
|
self.sendall(struct.pack(">II", addr, num))
|
|
length = struct.unpack(">I", self.recvall(4))[0]
|
|
return self.recvall(length).decode("ascii")
|
|
|
|
def write(self, addr, data):
|
|
self.sendbyte(COMMAND_WRITE)
|
|
self.sendall(struct.pack(">II", addr, len(data)))
|
|
self.sendall(data)
|
|
|
|
def writeCode(self, addr, data):
|
|
self.sendbyte(COMMAND_WRITE_CODE)
|
|
self.sendall(struct.pack(">II", addr, len(data)))
|
|
self.sendall(data)
|
|
|
|
def writeInstr(self, addr, value):
|
|
self.writeCode(addr, struct.pack(">I", value))
|
|
|
|
def getModuleName(self):
|
|
self.sendbyte(COMMAND_GET_MODULE_NAME)
|
|
length = struct.unpack(">I", self.recvall(4))[0]
|
|
return self.recvall(length).decode("ascii") + ".rpx"
|
|
|
|
def getModuleList(self):
|
|
self.sendbyte(COMMAND_GET_MODULE_LIST)
|
|
length = struct.unpack(">I", self.recvall(4))[0]
|
|
data = self.recvall(length)
|
|
|
|
offset = 0
|
|
modules = []
|
|
while offset < length:
|
|
module = Module(data, offset)
|
|
modules.append(module)
|
|
offset += 24 + len(module.name)
|
|
return sorted(modules, key=lambda m: m.textAddr)
|
|
|
|
def getThreadList(self):
|
|
self.sendbyte(COMMAND_GET_THREAD_LIST)
|
|
length = struct.unpack(">I", self.recvall(4))[0]
|
|
data = self.recvall(length)
|
|
|
|
offset = 0
|
|
self.threads = []
|
|
while offset < length:
|
|
thread = Thread(data, offset)
|
|
self.threads.append(thread)
|
|
offset += 28 + thread.namelen
|
|
return self.threads
|
|
|
|
def getStackTrace(self, state):
|
|
self.sendbyte(COMMAND_GET_STACK_TRACE)
|
|
self.sendall(struct.pack(">I", state.thread))
|
|
count = struct.unpack(">I", self.recvall(4))[0]
|
|
trace = struct.unpack(">%iI" %count, self.recvall(4 * count))
|
|
return trace
|
|
|
|
def toggleBreakPoint(self, addr):
|
|
if addr in self.breakpoints:
|
|
self.breakpoints.remove(addr)
|
|
else:
|
|
self.breakpoints.append(addr)
|
|
|
|
self.sendbyte(COMMAND_TOGGLE_BREAKPOINT)
|
|
self.sendall(struct.pack(">I", addr))
|
|
events.BreakPointChanged.emit()
|
|
|
|
def pokeRegisters(self, state):
|
|
self.sendbyte(COMMAND_POKE_REGISTERS)
|
|
self.sendall(struct.pack(">I", state.thread))
|
|
self.sendall(struct.pack(">32I32d", *state.gpr, *state.fpr))
|
|
|
|
def updateMessages(self):
|
|
self.sendbyte(COMMAND_RECEIVE_MESSAGES)
|
|
count = struct.unpack(">I", self.recvall(4))[0]
|
|
for i in range(count):
|
|
type, ptr, length, arg = struct.unpack(">IIII", self.recvall(16))
|
|
data = None
|
|
if length:
|
|
data = self.recvall(length)
|
|
self.messageHandlers[type](Message(type, data, arg))
|
|
|
|
def sendMessage(self, message, data0=0, data1=0, data2=0):
|
|
self.sendbyte(COMMAND_SEND_MESSAGE)
|
|
self.sendall(struct.pack(">IIII", message, data0, data1, data2))
|
|
|
|
def handleException(self, msg):
|
|
state = ExceptionState(msg)
|
|
events.Exception.emit(state)
|
|
|
|
def continueThread(self, state): self.sendCrashMessage(Message.Continue, state)
|
|
def stepInto(self, state): self.sendCrashMessage(Message.Step, state)
|
|
def stepOver(self, state): self.sendCrashMessage(Message.StepOver, state)
|
|
|
|
def sendCrashMessage(self, message, state):
|
|
self.sendMessage(message, state.thread)
|
|
if message == Message.Continue:
|
|
state.stepping = False
|
|
events.Continue.emit(state)
|
|
else:
|
|
state.stepping = True
|
|
events.Step.emit(state)
|
|
|
|
def _findThread(self, handle):
|
|
for thread in self.threads:
|
|
if thread.handle == handle:
|
|
return thread
|
|
|
|
def findThread(self, handle):
|
|
thread = self._findThread(handle)
|
|
if not thread:
|
|
self.getThreadList()
|
|
thread = self._findThread(handle)
|
|
return thread
|
|
|
|
def sendbyte(self, byte):
|
|
self.sendall(bytes([byte]))
|
|
|
|
def sendall(self, data):
|
|
if self.connected:
|
|
try:
|
|
self.s.sendall(data)
|
|
except socket.error:
|
|
self.handleClose()
|
|
|
|
def recvall(self, num):
|
|
if not self.connected:
|
|
return bytes(num)
|
|
|
|
try:
|
|
data = b""
|
|
while len(data) < num:
|
|
chunk = self.s.recv(num - len(data))
|
|
if not chunk:
|
|
self.handleClose()
|
|
return bytes(num)
|
|
data += chunk
|
|
except socket.error:
|
|
self.handleClose()
|
|
return bytes(num)
|
|
|
|
return data
|
|
debugger = Debugger()
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Custom widgets
|
|
"""""""""""""""""""""""""""
|
|
|
|
class HexSpinBox(QAbstractSpinBox):
|
|
def __init__(self, stepSize = 1):
|
|
super().__init__()
|
|
self._value = 0
|
|
self.stepSize = stepSize
|
|
|
|
def validate(self, text, pos):
|
|
if all([char in "0123456789abcdefABCDEF" for char in text]):
|
|
if not text:
|
|
return QValidator.Intermediate, text.upper(), pos
|
|
|
|
value = int(text, 16)
|
|
if value <= 0xFFFFFFFF:
|
|
self._value = value
|
|
if value % self.stepSize:
|
|
self._value -= value % self.stepSize
|
|
return QValidator.Acceptable, text.upper(), pos
|
|
return QValidator.Acceptable, text.upper(), pos
|
|
|
|
return QValidator.Invalid, text.upper(), pos
|
|
|
|
def stepBy(self, steps):
|
|
self._value = min(max(self._value + steps * self.stepSize, 0), 0x100000000 - self.stepSize)
|
|
self.lineEdit().setText("%X" %self._value)
|
|
|
|
def stepEnabled(self):
|
|
return QAbstractSpinBox.StepUpEnabled | QAbstractSpinBox.StepDownEnabled
|
|
|
|
def setValue(self, value):
|
|
self._value = value
|
|
self.lineEdit().setText("%X" %self._value)
|
|
|
|
def value(self):
|
|
return self._value
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Memory widgets
|
|
"""""""""""""""""""""""""""
|
|
|
|
def format_hex(blob, offs):
|
|
return "%02X" %blob[offs]
|
|
|
|
def format_ascii(blob, offs):
|
|
char = chr(blob[offs])
|
|
if char in string.ascii_letters + string.digits + string.punctuation + " ":
|
|
return char
|
|
return "?"
|
|
|
|
def format_float(blob, offs):
|
|
value = struct.unpack_from(">f", blob, offs)[0]
|
|
if abs(value) >= 1000000 or 0 < abs(value) < 0.000001:
|
|
return "%e" %value
|
|
return ("%.8f" %value).rstrip("0")
|
|
|
|
|
|
class MemoryViewer(QWidget):
|
|
|
|
class Format:
|
|
Hex = 0
|
|
Ascii = 1
|
|
Float = 2
|
|
|
|
Width = 1, 1, 4
|
|
Funcs = format_hex, format_ascii, format_float
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.layout = QGridLayout()
|
|
|
|
for i in range(16):
|
|
self.layout.addWidget(QLabel("%X" %i, self), 0, i + 1)
|
|
self.addrLabels = []
|
|
for i in range(16):
|
|
label = QLabel("%X" %(i * 0x10), self)
|
|
self.layout.addWidget(label, i + 1, 0)
|
|
self.addrLabels.append(label)
|
|
self.dataCells = []
|
|
|
|
self.base = 0
|
|
self.format = self.Format.Hex
|
|
self.updateData()
|
|
|
|
self.setLayout(self.layout)
|
|
|
|
events.Connected.connect(self.connected)
|
|
|
|
def connected(self):
|
|
self.setBase(0x10000000)
|
|
|
|
def setFormat(self, format):
|
|
self.format = format
|
|
self.updateData()
|
|
|
|
def setBase(self, base):
|
|
window.mainWidget.tabWidget.memoryTab.memoryInfo.baseBox.setValue(base)
|
|
self.base = base
|
|
for i in range(16):
|
|
self.addrLabels[i].setText("%X" %(self.base + i * 0x10))
|
|
self.updateData()
|
|
|
|
def updateData(self):
|
|
for cell in self.dataCells:
|
|
self.layout.removeWidget(cell)
|
|
cell.setParent(None)
|
|
|
|
if debugger.connected:
|
|
blob = debugger.read(self.base, 0x100)
|
|
else:
|
|
blob = b"\x00" * 0x100
|
|
|
|
width = self.Width[self.format]
|
|
func = self.Funcs[self.format]
|
|
for i in range(16 // width):
|
|
for j in range(16):
|
|
label = QLabel(func(blob, j * 0x10 + i * width), self)
|
|
self.layout.addWidget(label, j + 1, i * width + 1, 1, width)
|
|
self.dataCells.append(label)
|
|
|
|
|
|
class MemoryInfo(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.dataTypeLabel = QLabel("Data type:")
|
|
self.dataTypeBox = QComboBox()
|
|
self.dataTypeBox.addItems(["Hex", "Ascii", "Float"])
|
|
self.dataTypeBox.currentIndexChanged.connect(self.updateDataType)
|
|
|
|
self.baseLabel = QLabel("Address:")
|
|
self.baseBox = HexSpinBox(0x10)
|
|
self.baseButton = QPushButton("Update", self)
|
|
self.baseButton.clicked.connect(self.updateMemoryBase)
|
|
|
|
self.pokeAddr = HexSpinBox(4)
|
|
self.pokeValue = HexSpinBox()
|
|
self.pokeButton = QPushButton("Poke", self)
|
|
self.pokeButton.clicked.connect(self.pokeMemory)
|
|
|
|
self.layout = QGridLayout()
|
|
self.layout.addWidget(self.baseLabel, 0, 0)
|
|
self.layout.addWidget(self.baseBox, 0, 1)
|
|
self.layout.addWidget(self.baseButton, 0, 2)
|
|
self.layout.addWidget(self.pokeAddr, 1, 0)
|
|
self.layout.addWidget(self.pokeValue, 1, 1)
|
|
self.layout.addWidget(self.pokeButton, 1, 2)
|
|
self.layout.addWidget(self.dataTypeLabel, 2, 0)
|
|
self.layout.addWidget(self.dataTypeBox, 2, 1, 1, 2)
|
|
self.setLayout(self.layout)
|
|
|
|
def updateDataType(self, index):
|
|
window.mainWidget.tabWidget.memoryTab.memoryViewer.setFormat(index)
|
|
|
|
def updateMemoryBase(self):
|
|
window.mainWidget.tabWidget.memoryTab.memoryViewer.setBase(self.baseBox.value())
|
|
|
|
def pokeMemory(self):
|
|
debugger.write(self.pokeAddr.value(), struct.pack(">I", self.pokeValue.value()))
|
|
window.mainWidget.tabWidget.memoryTab.memoryViewer.updateData()
|
|
|
|
|
|
class MemoryTab(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.memoryInfo = MemoryInfo()
|
|
self.memoryViewer = MemoryViewer()
|
|
self.layout = QHBoxLayout()
|
|
self.layout.addWidget(self.memoryInfo)
|
|
self.layout.addWidget(self.memoryViewer)
|
|
self.setLayout(self.layout)
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Disassembly widgets
|
|
"""""""""""""""""""""""""""
|
|
|
|
class DisassemblyWidget(QTextEdit):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setTextInteractionFlags(Qt.NoTextInteraction)
|
|
|
|
self.setMinimumWidth(500)
|
|
|
|
self.currentInstruction = None
|
|
self.selectedAddress = 0
|
|
self.setBase(0)
|
|
|
|
events.Closed.connect(self.updateHighlight)
|
|
events.BreakPointChanged.connect(self.updateHighlight)
|
|
|
|
def setCurrentInstruction(self, instr):
|
|
self.currentInstruction = instr
|
|
if instr is not None:
|
|
self.setBase(instr - 0x20)
|
|
else:
|
|
self.updateHighlight()
|
|
|
|
def setBase(self, base):
|
|
self.base = base
|
|
self.updateText()
|
|
self.updateHighlight()
|
|
|
|
def updateText(self):
|
|
text = ""
|
|
if debugger.connected:
|
|
text = debugger.disasm(self.base, 0x60)
|
|
|
|
self.setPlainText(text)
|
|
|
|
def updateHighlight(self):
|
|
selections = []
|
|
for i in range(24):
|
|
address = self.base + i * 4
|
|
|
|
color = self.getColor(address)
|
|
if color:
|
|
cursor = self.textCursor()
|
|
cursor.movePosition(QTextCursor.Down, n=i)
|
|
cursor.select(QTextCursor.LineUnderCursor)
|
|
format = QTextCharFormat()
|
|
format.setBackground(QBrush(QColor(color)))
|
|
selection = QTextEdit.ExtraSelection()
|
|
selection.cursor = cursor
|
|
selection.format = format
|
|
selections.append(selection)
|
|
self.setExtraSelections(selections)
|
|
|
|
def getColor(self, addr):
|
|
colors = []
|
|
if addr in debugger.breakpoints:
|
|
colors.append((255, 0, 0))
|
|
if addr == self.currentInstruction:
|
|
colors.append((0, 255, 0))
|
|
if addr == self.selectedAddress:
|
|
colors.append((128, 128, 255))
|
|
|
|
if not colors:
|
|
return None
|
|
|
|
color = [sum(l)//len(colors) for l in zip(*colors)]
|
|
return "#%02X%02X%02X" %tuple(color)
|
|
|
|
def mousePressEvent(self, e):
|
|
super().mousePressEvent(e)
|
|
line = self.cursorForPosition(e.pos()).blockNumber()
|
|
self.selectedAddress = self.base + line * 4
|
|
if e.button() == Qt.MidButton:
|
|
debugger.toggleBreakPoint(self.selectedAddress)
|
|
self.updateHighlight()
|
|
|
|
|
|
class DisassemblyInfo(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.baseLabel = QLabel("Address:")
|
|
self.baseBox = HexSpinBox(4)
|
|
self.baseButton = QPushButton("Update", self)
|
|
self.baseButton.clicked.connect(self.updateDisassemblyBase)
|
|
|
|
self.pokeBox = HexSpinBox()
|
|
self.pokeButton = QPushButton("Poke", self)
|
|
self.pokeButton.clicked.connect(self.poke)
|
|
|
|
self.layout = QGridLayout()
|
|
self.layout.addWidget(self.baseLabel, 0, 0)
|
|
self.layout.addWidget(self.baseBox, 0, 1)
|
|
self.layout.addWidget(self.baseButton, 0, 2)
|
|
self.layout.addWidget(self.pokeBox, 1, 0)
|
|
self.layout.addWidget(self.pokeButton, 1, 1, 1, 2)
|
|
self.setLayout(self.layout)
|
|
self.setMinimumWidth(300)
|
|
|
|
def updateDisassemblyBase(self):
|
|
window.mainWidget.tabWidget.disassemblyTab.disassemblyWidget.setBase(self.baseBox.value())
|
|
|
|
def poke(self):
|
|
disassembly = window.mainWidget.tabWidget.disassemblyTab.disassemblyWidget
|
|
if disassembly.selectedAddress:
|
|
debugger.writeInstr(disassembly.selectedAddress, self.pokeBox.value())
|
|
disassembly.updateText()
|
|
|
|
|
|
class DisassemblyTab(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.disassemblyInfo = DisassemblyInfo()
|
|
self.disassemblyWidget = DisassemblyWidget()
|
|
self.layout = QHBoxLayout()
|
|
self.layout.addWidget(self.disassemblyInfo)
|
|
self.layout.addWidget(self.disassemblyWidget)
|
|
self.setLayout(self.layout)
|
|
|
|
events.Connected.connect(self.connected)
|
|
|
|
def connected(self):
|
|
self.disassemblyWidget.setBase(0x10000000)
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Thread widgets
|
|
"""""""""""""""""""""""""""
|
|
|
|
class ThreadList(QTableWidget):
|
|
def __init__(self):
|
|
super().__init__(0, 5)
|
|
self.setHorizontalHeaderLabels(["Name", "Priority", "Core", "Stack", "Entry Point"])
|
|
self.setEditTriggers(self.NoEditTriggers)
|
|
self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
|
|
events.Connected.connect(self.updateThreads)
|
|
|
|
def updateThreads(self):
|
|
threads = debugger.getThreadList()
|
|
self.setRowCount(len(threads))
|
|
for i in range(len(threads)):
|
|
thread = threads[i]
|
|
self.setItem(i, 0, QTableWidgetItem(thread.name))
|
|
self.setItem(i, 1, QTableWidgetItem(str(thread.priority)))
|
|
self.setItem(i, 2, QTableWidgetItem(thread.core))
|
|
self.setItem(i, 3, QTableWidgetItem("0x%X - 0x%X" %(thread.stackEnd, thread.stackBase)))
|
|
self.setItem(i, 4, QTableWidgetItem("0x%X" %thread.entryPoint))
|
|
|
|
|
|
class ThreadingTab(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.threadList = ThreadList()
|
|
self.updateButton = QPushButton("Update", self)
|
|
self.updateButton.clicked.connect(self.threadList.updateThreads)
|
|
self.layout = QVBoxLayout()
|
|
self.layout.addWidget(self.threadList)
|
|
self.layout.addWidget(self.updateButton)
|
|
self.setLayout(self.layout)
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Module widgets
|
|
"""""""""""""""""""""""""""
|
|
|
|
class ModuleList(QTableWidget):
|
|
def __init__(self):
|
|
super().__init__(0, 4)
|
|
self.setHorizontalHeaderLabels(["Name", "Code", "Data", "Entry Point"])
|
|
self.setEditTriggers(self.NoEditTriggers)
|
|
self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
|
|
events.Connected.connect(self.updateModules)
|
|
|
|
def updateModules(self):
|
|
modules = debugger.getModuleList()
|
|
self.setRowCount(len(modules))
|
|
for i in range(len(modules)):
|
|
module = modules[i]
|
|
textEnd = module.textAddr + module.textSize
|
|
dataEnd = module.dataAddr + module.dataSize
|
|
self.setItem(i, 0, QTableWidgetItem(module.name))
|
|
self.setItem(i, 1, QTableWidgetItem("%X - %X" %(module.textAddr, textEnd)))
|
|
self.setItem(i, 2, QTableWidgetItem("%X - %X" %(module.dataAddr, dataEnd)))
|
|
self.setItem(i, 3, QTableWidgetItem("%X" %module.entryPoint))
|
|
|
|
|
|
class ModuleTab(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.moduleList = ModuleList()
|
|
self.updateButton = QPushButton("Update", self)
|
|
self.updateButton.clicked.connect(self.moduleList.updateModules)
|
|
self.layout = QVBoxLayout()
|
|
self.layout.addWidget(self.moduleList)
|
|
self.layout.addWidget(self.updateButton)
|
|
self.setLayout(self.layout)
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Breakpoint widgets
|
|
"""""""""""""""""""""""""""
|
|
|
|
class BreakPointList(QListWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.itemDoubleClicked.connect(self.goToDisassembly)
|
|
events.BreakPointChanged.connect(self.updateList)
|
|
|
|
def updateList(self):
|
|
self.clear()
|
|
for bp in debugger.breakpoints:
|
|
self.addItem("0x%08X" %bp)
|
|
|
|
def goToDisassembly(self, item):
|
|
address = debugger.breakpoints[self.row(item)]
|
|
window.mainWidget.tabWidget.disassemblyTab.disassemblyWidget.setBase(address)
|
|
window.mainWidget.tabWidget.setCurrentIndex(1)
|
|
|
|
|
|
class BreakPointTab(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.list = BreakPointList()
|
|
self.button = QPushButton("Remove")
|
|
self.button.clicked.connect(self.removeBreakPoint)
|
|
self.layout = QVBoxLayout()
|
|
self.layout.addWidget(self.list)
|
|
self.layout.addWidget(self.button)
|
|
self.setLayout(self.layout)
|
|
|
|
def removeBreakPoint(self):
|
|
if self.list.currentRow() != -1:
|
|
debugger.toggleBreakPoint(debugger.breakpoints[self.list.currentRow()])
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Exception widgets
|
|
"""""""""""""""""""""""""""
|
|
|
|
class ExceptionThreadList(QTableWidget):
|
|
|
|
itemSelected = pyqtSignal(object)
|
|
|
|
def __init__(self, title):
|
|
super().__init__(0, 1)
|
|
|
|
self.setHorizontalHeaderLabels([title])
|
|
|
|
self.setEditTriggers(self.NoEditTriggers)
|
|
self.setSelectionMode(QAbstractItemView.NoSelection)
|
|
self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
|
self.verticalHeader().hide()
|
|
|
|
self.currentItem = None
|
|
self.itemDoubleClicked.connect(self.selectItem)
|
|
|
|
self.threads = []
|
|
|
|
events.Closed.connect(self.handleClose)
|
|
|
|
def getItem(self, state):
|
|
if state.thread in self.threads:
|
|
return self.item(self.threads.index(state.thread), 0)
|
|
|
|
def addItem(self, state):
|
|
if state.thread not in self.threads:
|
|
thread = debugger.findThread(state.thread)
|
|
if thread and thread.name:
|
|
name = thread.name
|
|
else:
|
|
name = "<%08X>"
|
|
item = QTableWidgetItem(name)
|
|
item.state = state
|
|
self.setRowCount(self.rowCount() + 1)
|
|
self.setItem(self.rowCount() - 1, 0, item)
|
|
self.threads.append(state.thread)
|
|
return item
|
|
else:
|
|
item = self.getItem(state)
|
|
item.state = state
|
|
return item
|
|
|
|
def removeItem(self, state):
|
|
if state.thread in self.threads:
|
|
index = self.threads.index(state.thread)
|
|
item = self.item(index, 0)
|
|
if item == self.currentItem:
|
|
self.currentItem = None
|
|
self.itemSelected.emit(None)
|
|
self.removeRow(self.threads.index(state.thread))
|
|
self.threads.remove(state.thread)
|
|
|
|
def selectItem(self, item):
|
|
if item != self.currentItem:
|
|
self.resetSelection()
|
|
item.setBackground(QBrush(QColor(128, 128, 255)))
|
|
self.currentItem = item
|
|
self.itemSelected.emit(item)
|
|
|
|
def resetSelection(self):
|
|
if self.currentItem:
|
|
self.currentItem.setBackground(QBrush())
|
|
self.currentItem = None
|
|
|
|
def handleClose(self):
|
|
self.threads = []
|
|
self.currentItem = None
|
|
self.clearContents()
|
|
self.setRowCount(0)
|
|
|
|
|
|
class ExceptionThreads(QWidget):
|
|
|
|
itemSelected = pyqtSignal(object)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.pausedThreads = ExceptionThreadList("Paused threads")
|
|
self.pausedThreads.itemSelected.connect(self.handlePauseSelect)
|
|
self.crashedThreads = ExceptionThreadList("Crashed threads")
|
|
self.crashedThreads.itemSelected.connect(self.handleCrashSelect)
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.addWidget(self.pausedThreads)
|
|
layout.addWidget(self.crashedThreads)
|
|
|
|
events.Exception.connect(self.handleException)
|
|
events.Continue.connect(self.handleContinue)
|
|
|
|
def handlePauseSelect(self, item):
|
|
if item:
|
|
self.crashedThreads.resetSelection()
|
|
self.itemSelected.emit(item)
|
|
|
|
def handleCrashSelect(self, item):
|
|
if item:
|
|
self.pausedThreads.resetSelection()
|
|
self.itemSelected.emit(item)
|
|
|
|
def handleException(self, state):
|
|
if state.isFatal():
|
|
self.pausedThreads.removeItem(state)
|
|
item = self.crashedThreads.addItem(state)
|
|
self.crashedThreads.selectItem(item)
|
|
else:
|
|
self.crashedThreads.removeItem(state)
|
|
item = self.pausedThreads.addItem(state)
|
|
self.pausedThreads.selectItem(item)
|
|
|
|
def handleContinue(self, state):
|
|
self.pausedThreads.removeItem(state)
|
|
self.crashedThreads.removeItem(state)
|
|
|
|
|
|
class ExceptionInfo(QGroupBox):
|
|
def __init__(self):
|
|
super().__init__("Info")
|
|
self.typeLabel = QLabel()
|
|
|
|
self.layout = QVBoxLayout(self)
|
|
self.layout.addWidget(self.typeLabel)
|
|
|
|
def setState(self, state):
|
|
self.typeLabel.setText("Type: %s" %state.exceptionName)
|
|
|
|
|
|
class SpecialRegisters(QGroupBox):
|
|
def __init__(self):
|
|
super().__init__("Special registers")
|
|
self.cr = QLabel()
|
|
self.lr = QLabel()
|
|
self.ctr = QLabel()
|
|
self.xer = QLabel()
|
|
self.srr0 = QLabel()
|
|
self.srr1 = QLabel()
|
|
self.ex0 = QLabel()
|
|
self.ex1 = QLabel()
|
|
|
|
self.userLayout = QFormLayout()
|
|
self.kernelLayout = QFormLayout()
|
|
|
|
self.userLayout.addRow("CR:", self.cr)
|
|
self.userLayout.addRow("LR:", self.lr)
|
|
self.userLayout.addRow("CTR:", self.ctr)
|
|
self.userLayout.addRow("XER:", self.xer)
|
|
|
|
self.kernelLayout = QFormLayout()
|
|
self.kernelLayout.addRow("SRR0:", self.srr0)
|
|
self.kernelLayout.addRow("SRR1:", self.srr1)
|
|
self.kernelLayout.addRow("EX0:", self.ex0)
|
|
self.kernelLayout.addRow("EX1:", self.ex1)
|
|
|
|
self.layout = QHBoxLayout(self)
|
|
self.layout.addLayout(self.userLayout)
|
|
self.layout.addLayout(self.kernelLayout)
|
|
|
|
def setState(self, state):
|
|
self.cr.setText("%08X" %state.cr)
|
|
self.lr.setText("%08X" %state.lr)
|
|
self.ctr.setText("%08X" %state.ctr)
|
|
self.xer.setText("%08X" %state.xer)
|
|
self.srr0.setText("%08X" %state.srr0)
|
|
self.srr1.setText("%08X" %state.srr1)
|
|
self.ex0.setText("%08X" %state.ex0)
|
|
self.ex1.setText("%08X" %state.ex1)
|
|
|
|
|
|
class ExceptionInfoTab(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.exceptionInfo = ExceptionInfo()
|
|
self.specialRegisters = SpecialRegisters()
|
|
|
|
self.layout = QHBoxLayout(self)
|
|
self.layout.addWidget(self.exceptionInfo)
|
|
self.layout.addWidget(self.specialRegisters)
|
|
|
|
def setState(self, state):
|
|
self.exceptionInfo.setState(state)
|
|
self.specialRegisters.setState(state)
|
|
|
|
|
|
class RegisterTab(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.gprLabels = []
|
|
self.gprBoxes = []
|
|
self.fprLabels = []
|
|
self.fprBoxes = []
|
|
for i in range(32):
|
|
self.gprLabels.append(QLabel("r%i" %i))
|
|
self.fprLabels.append(QLabel("f%i" % i))
|
|
gprBox = HexSpinBox()
|
|
fprBox = QDoubleSpinBox()
|
|
fprBox.setRange(float("-inf"), float("inf"))
|
|
self.gprBoxes.append(gprBox)
|
|
self.fprBoxes.append(fprBox)
|
|
|
|
self.layout = QGridLayout(self)
|
|
for i in range(32):
|
|
self.layout.addWidget(self.gprLabels[i], i % 16, i // 16 * 2)
|
|
self.layout.addWidget(self.gprBoxes[i], i % 16, i // 16 * 2 + 1)
|
|
self.layout.addWidget(self.fprLabels[i], i % 16, i // 16 * 2 + 4)
|
|
self.layout.addWidget(self.fprBoxes[i], i % 16, i // 16 * 2 + 5)
|
|
|
|
self.pokeButton = QPushButton("Poke")
|
|
self.resetButton = QPushButton("Reset")
|
|
self.pokeButton.clicked.connect(self.pokeRegisters)
|
|
self.resetButton.clicked.connect(self.updateRegisters)
|
|
self.layout.addWidget(self.pokeButton, 16, 0, 1, 4)
|
|
self.layout.addWidget(self.resetButton, 16, 4, 1, 4)
|
|
|
|
self.setEditEnabled(False)
|
|
|
|
events.Step.connect(self.handleStep)
|
|
|
|
def setState(self, state):
|
|
self.state = state
|
|
self.updateRegisters()
|
|
self.setEditEnabled(state.isBreakPoint() and not state.stepping)
|
|
|
|
def handleStep(self, state):
|
|
if state == self.state:
|
|
self.setEditEnabled(False)
|
|
|
|
def setEditEnabled(self, enabled):
|
|
for i in range(32):
|
|
self.gprBoxes[i].setEnabled(enabled)
|
|
self.fprBoxes[i].setEnabled(enabled)
|
|
self.pokeButton.setEnabled(enabled)
|
|
self.resetButton.setEnabled(enabled)
|
|
|
|
def updateRegisters(self):
|
|
for i in range(32):
|
|
self.gprBoxes[i].setValue(self.state.gpr[i])
|
|
self.fprBoxes[i].setValue(self.state.fpr[i])
|
|
|
|
def pokeRegisters(self):
|
|
for i in range(32):
|
|
self.state.gpr[i] = self.gprBoxes[i].value()
|
|
self.state.fpr[i] = self.fprBoxes[i].value()
|
|
debugger.pokeRegisters(self.state)
|
|
|
|
|
|
class StackTrace(QListWidget):
|
|
def setState(self, state):
|
|
self.clear()
|
|
stackTrace = debugger.getStackTrace(state)
|
|
for address in (state.srr0, state.lr) + stackTrace:
|
|
self.addItem("%X" %address)
|
|
|
|
class StackTraceTab(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.stackTrace = StackTrace()
|
|
self.disassembly = DisassemblyWidget()
|
|
|
|
layout = QHBoxLayout(self)
|
|
layout.addWidget(self.stackTrace)
|
|
layout.addWidget(self.disassembly)
|
|
|
|
self.stackTrace.itemDoubleClicked.connect(self.jumpDisassembly)
|
|
|
|
events.Step.connect(self.handleStep)
|
|
|
|
def handleStep(self, state):
|
|
self.disassembly.setCurrentInstruction(None)
|
|
|
|
def setState(self, state):
|
|
self.stackTrace.setState(state)
|
|
if not state.stepping:
|
|
self.disassembly.setCurrentInstruction(state.srr0)
|
|
else:
|
|
self.disassembly.setCurrentInstruction(None)
|
|
|
|
def jumpDisassembly(self, item):
|
|
self.disassembly.setCurrentInstruction(int(item.text(), 16))
|
|
|
|
|
|
class ExceptionStateTabs(QTabWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.infoTab = ExceptionInfoTab()
|
|
self.registerTab = RegisterTab()
|
|
self.stackTab = StackTraceTab()
|
|
self.addTab(self.infoTab, "General")
|
|
self.addTab(self.registerTab, "Registers")
|
|
self.addTab(self.stackTab, "Stack trace")
|
|
|
|
def setState(self, state):
|
|
self.infoTab.setState(state)
|
|
self.registerTab.setState(state)
|
|
self.stackTab.setState(state)
|
|
|
|
|
|
class BreakpointActions(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.continueButton = QPushButton("Continue")
|
|
self.continueButton.clicked.connect(self.continueThread)
|
|
self.stepButton = QPushButton("Step")
|
|
self.stepButton.clicked.connect(self.stepInto)
|
|
self.stepOverButton = QPushButton("Step over")
|
|
self.stepOverButton.clicked.connect(self.stepOver)
|
|
|
|
layout = QHBoxLayout(self)
|
|
layout.addWidget(self.continueButton)
|
|
layout.addWidget(self.stepButton)
|
|
layout.addWidget(self.stepOverButton)
|
|
|
|
self.state = None
|
|
|
|
def setState(self, state):
|
|
self.state = state
|
|
if state.isFatal():
|
|
self.continueButton.hide()
|
|
self.stepButton.hide()
|
|
self.stepOverButton.hide()
|
|
else:
|
|
self.continueButton.show()
|
|
self.stepButton.show()
|
|
self.stepOverButton.show()
|
|
|
|
def continueThread(self): debugger.continueThread(self.state)
|
|
def stepInto(self): debugger.stepInto(self.state)
|
|
def stepOver(self): debugger.stepOver(self.state)
|
|
|
|
|
|
class ExceptionStateWidget(QTabWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.exceptionTabs = ExceptionStateTabs()
|
|
self.breakpointActions = BreakpointActions()
|
|
|
|
layout = QVBoxLayout(self)
|
|
layout.addWidget(self.exceptionTabs)
|
|
layout.addWidget(self.breakpointActions)
|
|
|
|
events.Step.connect(self.handleStep)
|
|
|
|
def setState(self, state):
|
|
self.exceptionTabs.setState(state)
|
|
self.breakpointActions.setState(state)
|
|
self.breakpointActions.setEnabled(not state.stepping)
|
|
|
|
def handleStep(self, state):
|
|
self.breakpointActions.setEnabled(False)
|
|
|
|
|
|
class ExceptionTab(QSplitter):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.threadList = ExceptionThreads()
|
|
self.threadList.itemSelected.connect(self.handleItemSelected)
|
|
self.stateWidget = ExceptionStateWidget()
|
|
self.stateWidget.setEnabled(False)
|
|
self.addWidget(self.threadList)
|
|
self.addWidget(self.stateWidget)
|
|
|
|
self.setSizes([100, 400])
|
|
|
|
events.Closed.connect(self.handleClose)
|
|
|
|
def handleItemSelected(self, item):
|
|
if item:
|
|
self.stateWidget.setState(item.state)
|
|
self.stateWidget.setEnabled(True)
|
|
else:
|
|
self.stateWidget.setEnabled(False)
|
|
|
|
def handleClose(self):
|
|
self.stateWidget.setEnabled(False)
|
|
|
|
|
|
"""""""""""""""""""""""""""
|
|
Main widgets
|
|
"""""""""""""""""""""""""""
|
|
|
|
class DebuggerTabs(QTabWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.memoryTab = MemoryTab()
|
|
self.disassemblyTab = DisassemblyTab()
|
|
self.threadingTab = ThreadingTab()
|
|
self.moduleTab = ModuleTab()
|
|
self.breakPointTab = BreakPointTab()
|
|
self.exceptionTab = ExceptionTab()
|
|
self.addTab(self.memoryTab, "Memory")
|
|
self.addTab(self.disassemblyTab, "Disassembly")
|
|
self.addTab(self.threadingTab, "Threads")
|
|
self.addTab(self.moduleTab, "Modules")
|
|
self.addTab(self.breakPointTab, "Breakpoints")
|
|
self.addTab(self.exceptionTab, "Exceptions")
|
|
|
|
events.Exception.connect(self.exceptionOccurred)
|
|
events.Connected.connect(self.connected)
|
|
events.Closed.connect(self.disconnected)
|
|
|
|
def exceptionOccurred(self, state):
|
|
self.setCurrentIndex(5) #Exceptions
|
|
|
|
def connected(self):
|
|
self.setEnabled(True)
|
|
|
|
def disconnected(self):
|
|
self.setEnabled(False)
|
|
|
|
|
|
class StatusWidget(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.serverLabel = QLabel("Wii U IP:")
|
|
self.serverBox = QLineEdit(self)
|
|
self.serverBox.returnPressed.connect(self.connect)
|
|
self.connectButton = QPushButton("Connect", self)
|
|
self.connectButton.clicked.connect(self.connect)
|
|
self.disconnectButton = QPushButton("Disconnect", self)
|
|
self.disconnectButton.clicked.connect(debugger.close)
|
|
self.disconnectButton.setEnabled(False)
|
|
|
|
self.statusInfo = QLabel("Disconnected", self)
|
|
|
|
self.layout = QGridLayout()
|
|
self.layout.addWidget(self.serverLabel, 0, 0)
|
|
self.layout.addWidget(self.serverBox, 1, 0)
|
|
self.layout.addWidget(self.connectButton, 0, 1)
|
|
self.layout.addWidget(self.disconnectButton, 1, 1)
|
|
self.layout.addWidget(self.statusInfo, 3, 0, 1, 2)
|
|
self.setLayout(self.layout)
|
|
|
|
events.Connected.connect(self.connected)
|
|
events.Closed.connect(self.disconnected)
|
|
|
|
def connect(self):
|
|
try:
|
|
address = self.serverBox.text()
|
|
debugger.connect(address)
|
|
except:
|
|
pass
|
|
|
|
def connected(self):
|
|
self.statusInfo.setText("Connected")
|
|
self.connectButton.setEnabled(False)
|
|
self.serverBox.setEnabled(False)
|
|
self.disconnectButton.setEnabled(True)
|
|
|
|
def disconnected(self):
|
|
self.statusInfo.setText("Disconnected")
|
|
self.connectButton.setEnabled(True)
|
|
self.serverBox.setEnabled(True)
|
|
self.disconnectButton.setEnabled(False)
|
|
|
|
|
|
class MainWidget(QWidget):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.tabWidget = DebuggerTabs()
|
|
self.statusWidget = StatusWidget()
|
|
self.layout = QVBoxLayout()
|
|
self.layout.addWidget(self.tabWidget)
|
|
self.layout.addWidget(self.statusWidget)
|
|
self.tabWidget.setEnabled(False)
|
|
self.setLayout(self.layout)
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle("DiiBugger")
|
|
self.resize(1080, 720)
|
|
|
|
self.mainWidget = MainWidget()
|
|
self.setCentralWidget(self.mainWidget)
|
|
|
|
self.timer = QTimer(self)
|
|
self.timer.setInterval(100)
|
|
self.timer.timeout.connect(self.updateMessages)
|
|
self.timer.start()
|
|
|
|
events.Connected.connect(self.updateTitle)
|
|
events.Closed.connect(self.updateTitle)
|
|
|
|
def updateTitle(self):
|
|
if debugger.connected:
|
|
name = debugger.getModuleName()
|
|
self.setWindowTitle("DiiBugger - %s" %name)
|
|
else:
|
|
self.setWindowTitle("DiiBugger")
|
|
|
|
def updateMessages(self):
|
|
if debugger.connected:
|
|
debugger.updateMessages()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app = QApplication(sys.argv)
|
|
app.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont))
|
|
window = MainWindow()
|
|
window.show()
|
|
app.exec()
|