bsnes-plus/bsnes/ui-qt/debugger/disassembler/disassemblerview.cpp

744 lines
22 KiB
C++

#include "disassemblerview.moc"
// ------------------------------------------------------------------------
DisassemblerView::DisassemblerView(DisasmProcessor *processor) : hasValidAddress(false), processor(processor) {
addressWidth = 6;
setFont(QFont(Style::Monospace));
setMouseTracking(true);
setContextMenuPolicy(Qt::CustomContextMenu);
_addressAreaColor = this->palette().alternateBase().color();
_selectionColor = this->palette().highlight().color();
_breakpointColor = QColor(255, 0, 0, 255);
connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onScroll()));
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(showContextMenu(const QPoint &)));
init();
}
// ------------------------------------------------------------------------
void DisassemblerView::init() {
verticalScrollBar()->setValue(0);
mouseState = NO_STATE;
emptyRowsAround = 5;
currentRangeStartAddress = 0;
currentRangeEndAddress = 0;
currentAddress = 0;
mouseX = 0;
mouseY = 0;
}
// ------------------------------------------------------------------------
void DisassemblerView::setFont(const QFont &font) {
QWidget::setFont(font);
charWidth = fontMetrics().width(QLatin1Char('2'));
charHeight = fontMetrics().height() + 1;
charPadding = charWidth / 2;
headerHeight = charHeight + 3;
lineOffset = 3;
columnSizes[0] = charWidth * addressWidth + charWidth;
columnSizes[1] = columnSizes[0] + 30 * charWidth;
adjust();
viewport()->update();
}
// ------------------------------------------------------------------------
void DisassemblerView::setAddressWidth(uint32_t width) {
addressWidth = width;
// adjust text dimensions
setFont(font());
}
// ------------------------------------------------------------------------
void DisassemblerView::adjust() {
rowsShown = ((viewport()->height() - 4) / charHeight) + 1;
if (rowsShown == 0) {
rowsShown = 1;
}
columnPositions[0] = 0;
columnPositions[1] = columnPositions[0] + columnSizes[0];
columnPositions[2] = columnPositions[1] + columnSizes[1];
columnSizes[2] = width() - columnPositions[2];
}
// ------------------------------------------------------------------------
void DisassemblerView::onScroll() {
updateVisibleLines();
}
// ------------------------------------------------------------------------
void DisassemblerView::refresh(uint32_t address) {
currentAddress = address;
currentPcAddress = processor->getCurrentAddress();
hasValidAddress = true;
processor->analyze(address);
updateLines();
viewport()->update();
updateCurrentMousePosition();
}
// ------------------------------------------------------------------------
void DisassemblerView::createLoopUpwards(uint32_t line, uint32_t targetAddress) {
int32_t topLine = line;
while (topLine >= 0) {
const RenderableDisassemblerLine &rop = lines[topLine];
if (rop.line.address <= targetAddress) {
// Do not highlight if we end inside another loop
if (rop.depth > 0) {
return;
}
break;
}
// Do not highlight if there is a return inside
if (rop.isReturn()) {
return;
}
--topLine;
}
int32_t currentLine = topLine;
if (currentLine >= 0) {
lines[currentLine].flags |= RenderableDisassemblerLine::FLAG_START_BRA;
lines[currentLine].numStarts++;
}
while (++currentLine <= line) {
lines[currentLine].depth++;
}
lines[line].braRelativeLine = topLine - line;
lines[line].flags |= RenderableDisassemblerLine::FLAG_END_BRA;
}
// ------------------------------------------------------------------------
void DisassemblerView::updateVisibleLines() {
uint32_t topLine = verticalScrollBar()->value();
uint32_t maxLines = currentRangeLineNumbers + emptyRowsAround + emptyRowsAround;
uint32_t address = processor->findStartLineAddress(currentRangeStartAddress, topLine < emptyRowsAround ? 0 : topLine - emptyRowsAround);
bool stopped = false;
uint32_t maxDisplayLines = rowsShown;
if (maxLines - topLine < maxDisplayLines) {
maxDisplayLines = maxLines - topLine;
}
lines.reset();
lines.reserve(maxDisplayLines);
bool first = true;
uint32_t line = topLine;
for (uint32_t index=0; index<maxDisplayLines; index++, line++) {
if (stopped || line < emptyRowsAround || line >= maxLines - emptyRowsAround) {
lines[index].line.setEmpty();
} else {
uint32_t currentAddress = address;
if (first) {
topLineAddress = address;
first = false;
}
RenderableDisassemblerLine &rop = lines[index];
processor->getLine(rop.line, address);
const DisassemblerLine &op = rop.line;
if (op.isReturn()) {
rop.flags |= RenderableDisassemblerLine::FLAG_RETURN;
}
if (op.isBra() && op.targetAddress < currentAddress && op.targetAddress >= currentRangeStartAddress) {
createLoopUpwards(index, op.targetAddress);
}
bottomLineAddress = address;
if (address == currentAddress) {
stopped = true; // no next instruction found
}
}
}
}
// ------------------------------------------------------------------------
void DisassemblerView::updateLines() {
if (!hasValidAddress) {
verticalScrollBar()->setRange(0, 1);
verticalScrollBar()->setPageStep(1);
return;
}
if (currentAddress < currentRangeStartAddress || currentAddress >= currentRangeEndAddress) {
updateLineRange();
} else if (lines.size()) {
if (currentAddress <= topLineAddress || currentAddress >= bottomLineAddress) {
updateLineRange();
}
}
updateVisibleLines();
}
// ------------------------------------------------------------------------
void DisassemblerView::updateLineRange() {
uint32_t currentAddressLine;
processor->findKnownRange(currentAddress, currentRangeStartAddress, currentRangeEndAddress, currentAddressLine, currentRangeLineNumbers);
verticalScrollBar()->setRange(0, currentRangeLineNumbers + emptyRowsAround + emptyRowsAround - rowsShown);
verticalScrollBar()->setPageStep(rowsShown);
verticalScrollBar()->setValue(currentAddressLine - emptyRowsAround);
lines.reset();
}
// ------------------------------------------------------------------------
void DisassemblerView::resizeEvent(QResizeEvent *) {
adjust();
updateLines();
}
// ------------------------------------------------------------------------
void DisassemblerView::setSymbol() {
if (!processor->getSymbols()) {
return;
}
uint32_t address = mouseStateValue;
Symbol symbol = processor->getSymbols()->getSymbol(address);
QString currentSymbol("");
if (symbol.isSymbol()) {
currentSymbol = (const char*)symbol.name;
}
bool ok;
QString value = QInputDialog::getText(this, "Symbol", "Enter name for address " + nall::hex(address), QLineEdit::Normal, currentSymbol, &ok);
string s;
s = qPrintable(value);
if (ok) {
if (symbol.isSymbol()) {
processor->getSymbols()->removeSymbol(address, Symbol::LOCATION);
}
if (s.length()) {
processor->getSymbols()->addLocation(address, s);
}
}
viewport()->update();
}
// ------------------------------------------------------------------------
void DisassemblerView::setComment() {
if (!processor->getSymbols()) {
return;
}
uint32_t address = mouseStateValue;
Symbol comment = processor->getSymbols()->getComment(address);
QString currentComment("");
if (comment.isComment()) {
currentComment = (const char*)comment.name;
}
bool ok;
QString value = QInputDialog::getText(this, "Comment", "Enter comment for address " + nall::hex(address), QLineEdit::Normal, currentComment, &ok);
string s;
s = qPrintable(value);
if (ok) {
if (comment.isComment()) {
processor->getSymbols()->removeSymbol(address, Symbol::COMMENT);
}
if (s.length()) {
processor->getSymbols()->addComment(address, s);
}
}
viewport()->update();
}
// ------------------------------------------------------------------------
void DisassemblerView::toggleBreakpoint() {
uint32_t address = mouseStateValue;
int32_t breakpoint = breakpointEditor->indexOfBreakpointExec(address, processor->getBreakpointBusName());
if (breakpoint >= 0) {
breakpointEditor->removeBreakpoint(breakpoint);
} else {
breakpointEditor->addBreakpoint(nall::hex(address), "x", processor->getBreakpointBusName());
}
viewport()->update();
}
// ------------------------------------------------------------------------
void DisassemblerView::mouseReleaseEvent(QMouseEvent *event) {
switch (mouseState) {
case STATE_RESIZING_COLUMN:
mouseState = STATE_RESIZE_COLUMN;
mouseMoveEvent(event);
break;
}
}
// ------------------------------------------------------------------------
void DisassemblerView::mousePressEvent(QMouseEvent * event) {
bool left = event->button() & Qt::LeftButton;
switch (mouseState) {
case STATE_RESIZE_COLUMN:
if (left) {
mouseState = STATE_RESIZING_COLUMN;
mouseStateValue2 = columnSizes[mouseStateValue];
}
break;
case STATE_JUMP_TO_ADDRESS:
if (left) {
refresh(mouseStateValue);
}
break;
case STATE_LINE:
currentAddress = mouseStateValue;
viewport()->update();
break;
case STATE_SET_COMMENT:
if (left) {
setComment();
}
break;
case STATE_TOGGLE_BREAKPOINT:
if (left) {
toggleBreakpoint();
}
break;
}
}
// ------------------------------------------------------------------------
void DisassemblerView::jumpToPc() {
refresh(processor->getCurrentAddress());
}
// ------------------------------------------------------------------------
void DisassemblerView::jumpToAddress() {
bool ok;
QString value = QInputDialog::getText(this, "Jump to address", "Enter address to jump to", QLineEdit::Normal, hex(currentPcAddress), &ok);
string s;
s = qPrintable(value);
if (ok) {
refresh(hex(s) & (processor->getBusSize() - 1));
}
}
// ------------------------------------------------------------------------
void DisassemblerView::showLineContextMenu(const QPoint &point) {
QMenu contextMenu("Context menu", this);
QAction setCommentAction("Set comment", this);
connect(&setCommentAction, SIGNAL(triggered()), this, SLOT(setComment()));
QAction setSymbolAction("Set symbol", this);
connect(&setSymbolAction, SIGNAL(triggered()), this, SLOT(setSymbol()));
QAction setBreakpoint("Toggle breakpoint", this);
connect(&setBreakpoint, SIGNAL(triggered()), this, SLOT(toggleBreakpoint()));
contextMenu.addAction(&setBreakpoint);
QAction jumpToPcAction("Jump to PC", this);
connect(&jumpToPcAction, SIGNAL(triggered()), this, SLOT(jumpToPc()));
QAction jumpToAddressAction("Jump to address", this);
connect(&jumpToAddressAction, SIGNAL(triggered()), this, SLOT(jumpToAddress()));
if (processor->getSymbols() != NULL) {
contextMenu.addAction(&setCommentAction);
contextMenu.addAction(&setSymbolAction);
}
contextMenu.addSeparator();
contextMenu.addAction(&jumpToPcAction);
contextMenu.addAction(&jumpToAddressAction);
contextMenu.exec(mapToGlobal(point));
}
// ------------------------------------------------------------------------
void DisassemblerView::showContextMenu(const QPoint &point) {
switch (mouseState) {
case STATE_SET_COMMENT:
case STATE_TOGGLE_BREAKPOINT:
case STATE_JUMP_TO_ADDRESS:
case STATE_LINE:
showLineContextMenu(point);
break;
}
}
// ------------------------------------------------------------------------
void DisassemblerView::mouseMoveEvent(QMouseEvent *e) {
switch (mouseState) {
case STATE_RESIZING_COLUMN:
{
int32_t newSize = mouseStateValue2 + (e->x() - mouseX);
if (newSize < 10) {
newSize= 10;
}
columnSizes[mouseStateValue] = newSize;
adjust();
viewport()->update();
}
break;
default:
mouseX = e->x();
mouseY = e->y();
updateCurrentMousePosition();
}
}
// ------------------------------------------------------------------------
void DisassemblerView::updateCurrentMousePosition() {
MouseState oldState = mouseState;
int32_t row = (mouseY - lineOffset + charHeight) / charHeight;
mouseState = NO_STATE;
if (mouseY < headerHeight) {
for (uint32_t i=1; i<NUM_COLUMNS; i++) {
if (mouseX < columnPositions[i] - 5 || mouseX > columnPositions[i] + 5) {
continue;
}
mouseState = STATE_RESIZE_COLUMN;
mouseStateValue = i - 1;
break;
}
} else if (row > 0 && row < lines.size()) {
const DisassemblerLine &line = lines[row].line;
if (!line.isEmpty()) {
if (mouseX < columnSizes[COLUMN_ADDRESS]) {
if (line.hasAddress()) {
mouseState = STATE_TOGGLE_BREAKPOINT;
mouseStateValue = line.address;
}
} else if (mouseX >= columnPositions[COLUMN_COMMENT]) {
mouseState = STATE_SET_COMMENT;
mouseStateValue = line.address;
} else if (line.hasAddress() && line.isBra() && mouseX >= lines[row].addressPosX && mouseX < lines[row].addressPosX + lines[row].addressSizeX) {
mouseState = STATE_JUMP_TO_ADDRESS;
mouseStateValue = line.targetAddress;
} else {
mouseState = STATE_LINE;
mouseStateValue = line.address;
}
}
}
if (mouseState != oldState) {
switch (mouseState) {
case STATE_RESIZE_COLUMN:
setCursor(Qt::SplitHCursor);
break;
case STATE_JUMP_TO_ADDRESS:
case STATE_TOGGLE_BREAKPOINT:
setCursor(Qt::PointingHandCursor);
break;
case NO_STATE:
default:
unsetCursor();
break;
}
}
}
#define SET_CLIPPING(region) \
painter.setClipping(true); \
painter.setClipRegion(QRect(columnPositions[region], 0, columnSizes[region], height()));
#define NO_CLIPPING() \
painter.setClipping(false)
// ------------------------------------------------------------------------
void DisassemblerView::paintOpcode(QPainter &painter, RenderableDisassemblerLine &line, int y) {
QString address;
QColor addressColor, opColor, textColor;
QColor paramImmediateColor, paramAddressColor, paramSymbolColor;
NO_CLIPPING();
if (line.line.address == currentPcAddress) {
painter.fillRect(QRect(0, y - charHeight + lineOffset, viewport()->width(), charHeight), _selectionColor);
addressColor = viewport()->palette().highlightedText().color();
textColor = addressColor;
opColor = addressColor;
paramImmediateColor = addressColor;
paramAddressColor = addressColor;
paramSymbolColor = addressColor;
} else {
if (line.line.address == currentAddress) {
painter.fillRect(QRect(0, y - charHeight + lineOffset, viewport()->width(), charHeight), _addressAreaColor);
}
addressColor = Qt::gray;
textColor = viewport()->palette().color(QPalette::WindowText);
opColor = QColor(0x00, 0x00, 0x88, 0xff);
paramImmediateColor = QColor(0x00, 0x88, 0x00, 0xff);
paramAddressColor = QColor(0x88, 0x00, 0x00, 0xff);
paramSymbolColor = QColor(0xFF, 0x00, 0x00, 0xff);
}
if (breakpointEditor->indexOfBreakpointExec(line.line.address, processor->getBreakpointBusName()) >= 0) {
painter.fillRect(QRect(0, y - charHeight + lineOffset, columnSizes[COLUMN_ADDRESS], charHeight), _breakpointColor);
addressColor = Qt::white;
}
SymbolMap *symbols = processor->getSymbols();
Symbol currentRow = symbols ? symbols->getSymbol(line.line.address) : Symbol::createInvalid();
if (line.isReturn()) {
painter.setPen(Qt::gray);
painter.drawLine(0, y + lineOffset, width(), y + lineOffset);
}
if (currentRow.type != Symbol::INVALID) {
painter.setPen(Qt::gray);
painter.drawLine(0, y - charHeight + lineOffset, width(), y - charHeight + lineOffset);
painter.setPen(paramAddressColor);
SET_CLIPPING(COLUMN_COMMENT);
painter.drawText(columnPositions[COLUMN_COMMENT] + charPadding, y, currentRow.name);
} else {
currentRow = symbols ? symbols->getComment(line.line.address) : Symbol::createInvalid();
painter.setPen(Qt::gray);
SET_CLIPPING(COLUMN_COMMENT);
if (currentRow.isComment()) {
painter.drawText(columnPositions[COLUMN_COMMENT] + charPadding, y, currentRow.name);
} else if (line.isReturn()) {
painter.drawText(columnPositions[COLUMN_COMMENT] + charPadding, y, "Return");
}
}
int x = columnPositions[1] + charPadding + line.depth * 2 * charWidth;
SET_CLIPPING(COLUMN_DISASM);
if (line.isStartBra()) {
x += line.numStarts * 2 * charWidth;
}
if (line.isEndBra()) {
int l = x - charWidth - charWidth + 1;
int r = x - charPadding;
int w = 4;
int hw = w >> 1;
int ap = 2;
int ar = r + 2;
int al = ar - (ap + hw);
int b = y + lineOffset + hw - (charHeight >> 1);
int t = b - w + (line.braRelativeLine) * charHeight - 1;
QPainterPath path;
path.moveTo(al, t-ap);
path.lineTo(ar, t + hw);
path.lineTo(al, t + w + ap);
painter.setPen(Qt::black);
painter.setBrush(Qt::black);
painter.drawEllipse(QPoint(l + hw, b - hw), hw, hw);
painter.drawEllipse(QPoint(l + hw, t + hw), hw, hw);
painter.drawRect(l, t + hw, w, b - t - w);
painter.drawRect(l + hw, b - w, r - l - hw, w);
painter.drawRect(l + hw, t, al - l - hw, w);
painter.drawPath(path);
}
address = QString("%1").arg(line.line.address, addressWidth, 16, QChar('0'));
SET_CLIPPING(COLUMN_ADDRESS);
painter.setPen(addressColor);
painter.drawText(0, y, address);
SET_CLIPPING(COLUMN_DISASM);
painter.setPen(opColor);
painter.drawText(x, y, line.line.text);
QString directComment;
if (line.line.paramFormat) {
x += (line.line.text.length() + 1) * charWidth;
painter.setPen(textColor);
int left = 0;
int textLength = line.line.paramFormat.length();
for (int i=0; i<textLength; i++) {
if (line.line.paramFormat[i] == '%') {
if (left < i) {
painter.drawText(x, y, nall::substr(line.line.paramFormat, left, i - left));
x += (i - left) * charWidth;
}
uint8_t argNum = line.line.paramFormat[i+1] - '1';
uint8_t argType = line.line.paramFormat[i+2];
uint8_t argLength = line.line.paramFormat[i+3] - '0';
if (line.line.params.size() <= argNum) {
painter.setPen(paramAddressColor);
painter.drawText(x, y, "???");
x += 3 * charWidth;
} else {
const DisassemblerParam &param = line.line.params[argNum];
switch (param.type) {
case DisassemblerParam::Value:
painter.setPen(paramImmediateColor);
x += renderValue(painter, x, y, argType, argLength, param.value);
break;
case DisassemblerParam::Address:
line.addressPosX = x;
if (symbols) {
Symbol sym = symbols->getSymbol(param.address);
if (sym.type != Symbol::INVALID) {
QString text = QString("<%1>").arg(sym.name);
painter.setPen(paramSymbolColor);
painter.drawText(x, y, text);
x += text.length() * charWidth;
} else {
painter.setPen(paramAddressColor);
x += renderValue(painter, x, y, argType, argLength, param.value);
}
} else {
painter.setPen(paramAddressColor);
x += renderValue(painter, x, y, argType, argLength, param.value);
}
line.addressSizeX = x - line.addressPosX;
directComment += QString("[%1]").arg(param.targetAddress, addressWidth, 16, QChar('0'));
break;
default:
painter.drawText(x, y, "???");
x += 3 * charWidth;
break;
}
}
painter.setPen(textColor);
i += 3;
left = i + 1;
}
}
if (left < textLength) {
painter.drawText(x, y, nall::substr(line.line.paramFormat, left, textLength - left));
x += (textLength - left) * charWidth;
}
if (directComment.length()) {
x += 2 * charWidth;
uint32_t right = columnPositions[COLUMN_DISASM + 1] - (directComment.length() + 1) * charWidth;
if (x < right) {
x = right;
}
painter.setPen(Qt::gray);
painter.drawText(x, y, directComment);
}
}
}
// ------------------------------------------------------------------------
int DisassemblerView::renderValue(QPainter &painter, int x, int y, uint8_t type, uint8_t size, uint32_t value) {
QString text;
switch (type) {
case 'X':
text = QString("$%1").arg(value, size, 16, QChar('0'));
break;
default:
text = "???";
break;
}
painter.drawText(x, y, text);
return text.length() * charWidth;
}
// ------------------------------------------------------------------------
void DisassemblerView::paintHeader(QPainter &painter) {
painter.fillRect(0, 0, width(), headerHeight, _addressAreaColor);
painter.setPen(Qt::gray);
painter.drawLine(0, headerHeight, width(), headerHeight);
for (uint8_t i=1; i<NUM_COLUMNS; i++) {
painter.drawLine(columnPositions[i], 0, columnPositions[i], headerHeight);
}
painter.setPen(Qt::black);
SET_CLIPPING(1);
painter.drawText(columnPositions[1] + charPadding, headerHeight - charPadding, "Disassemble");
SET_CLIPPING(2);
painter.drawText(columnPositions[2] + charPadding, headerHeight - charPadding, "Comment");
NO_CLIPPING();
}
// ------------------------------------------------------------------------
void DisassemblerView::paintEvent(QPaintEvent *event) {
QPainter painter(viewport());
painter.fillRect(event->rect(), viewport()->palette().color(QPalette::Base));
painter.fillRect(QRect(0, 0, columnSizes[COLUMN_ADDRESS], height()), _addressAreaColor);
painter.setPen(Qt::gray);
painter.drawLine(columnPositions[2], event->rect().top(), columnPositions[2], height());
int y = 0;
for (uint32_t index=0; index<lines.size(); index++, y+=charHeight) {
RenderableDisassemblerLine &line = lines[index];
switch (line.line.type) {
case DisassemblerLine::Empty:
break;
case DisassemblerLine::Opcode:
paintOpcode(painter, line, y);
break;
}
}
NO_CLIPPING();
paintHeader(painter);
}