// license:BSD-3-Clause // copyright-holders:Andrew Gardner #include "emu.h" #include "memorywindow.h" #include "debug/dvmemory.h" #include "debug/debugcon.h" #include "debug/debugcpu.h" #include "util/xmlfile.h" #include #include #include #include #include #include #include #include #include #include #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0) #define horizontalAdvance width #endif namespace osd::debugger::qt { MemoryWindow::MemoryWindow(running_machine &machine, QWidget *parent) : WindowQt(machine, nullptr) { setWindowTitle("Debug: Memory View"); if (parent) { QPoint parentPos = parent->pos(); setGeometry(parentPos.x()+100, parentPos.y()+100, 800, 400); } // // The main frame and its input and log widgets // QFrame *mainWindowFrame = new QFrame(this); // The top frame & groupbox that contains the input widgets QFrame *topSubFrame = new QFrame(mainWindowFrame); // The input edit m_inputEdit = new QLineEdit(topSubFrame); connect(m_inputEdit, &QLineEdit::returnPressed, this, &MemoryWindow::expressionSubmitted); // The memory space combo box m_memoryComboBox = new QComboBox(topSubFrame); m_memoryComboBox->setObjectName("memoryregion"); m_memoryComboBox->setMinimumWidth(300); connect(m_memoryComboBox, static_cast(&QComboBox::currentIndexChanged), this, &MemoryWindow::memoryRegionChanged); // The main memory window m_memTable = new DebuggerMemView(DVT_MEMORY, m_machine, this); // Layout QHBoxLayout *subLayout = new QHBoxLayout(topSubFrame); subLayout->addWidget(m_inputEdit); subLayout->addWidget(m_memoryComboBox); subLayout->setSpacing(3); subLayout->setContentsMargins(2,2,2,2); QVBoxLayout *vLayout = new QVBoxLayout(mainWindowFrame); vLayout->setSpacing(3); vLayout->setContentsMargins(2,2,2,2); vLayout->addWidget(topSubFrame); vLayout->addWidget(m_memTable); setCentralWidget(mainWindowFrame); // // Menu bars // // Create a data format group QActionGroup *dataFormat = new QActionGroup(this); dataFormat->setObjectName("dataformat"); QAction *formatActOne = new QAction("1-byte Chunks (Hex)", this); QAction *formatActTwo = new QAction("2-byte Chunks (Hex)", this); QAction *formatActFour = new QAction("4-byte Chunks (Hex)", this); QAction *formatActEight = new QAction("8-byte Chunks (Hex)", this); QAction *formatActOneOctal = new QAction("1-byte Chunks (Octal)", this); QAction *formatActTwoOctal = new QAction("2-byte Chunks (Octal)", this); QAction *formatActFourOctal = new QAction("4-byte Chunks (Octal)", this); QAction *formatActEightOctal = new QAction("8-byte Chunks (Octal)", this); QAction *formatAct32bitFloat = new QAction("32-bit Floating Point", this); QAction *formatAct64bitFloat = new QAction("64-bit Floating Point", this); QAction *formatAct80bitFloat = new QAction("80-bit Floating Point", this); formatActOne->setData(int(debug_view_memory::data_format::HEX_8BIT)); formatActTwo->setData(int(debug_view_memory::data_format::HEX_16BIT)); formatActFour->setData(int(debug_view_memory::data_format::HEX_32BIT)); formatActEight->setData(int(debug_view_memory::data_format::HEX_64BIT)); formatActOneOctal->setData(int(debug_view_memory::data_format::OCTAL_8BIT)); formatActTwoOctal->setData(int(debug_view_memory::data_format::OCTAL_16BIT)); formatActFourOctal->setData(int(debug_view_memory::data_format::OCTAL_32BIT)); formatActEightOctal->setData(int(debug_view_memory::data_format::OCTAL_64BIT)); formatAct32bitFloat->setData(int(debug_view_memory::data_format::FLOAT_32BIT)); formatAct64bitFloat->setData(int(debug_view_memory::data_format::FLOAT_64BIT)); formatAct80bitFloat->setData(int(debug_view_memory::data_format::FLOAT_80BIT)); formatActOne->setCheckable(true); formatActTwo->setCheckable(true); formatActFour->setCheckable(true); formatActEight->setCheckable(true); formatActOneOctal->setCheckable(true); formatActTwoOctal->setCheckable(true); formatActFourOctal->setCheckable(true); formatActEightOctal->setCheckable(true); formatAct32bitFloat->setCheckable(true); formatAct64bitFloat->setCheckable(true); formatAct80bitFloat->setCheckable(true); formatActOne->setActionGroup(dataFormat); formatActTwo->setActionGroup(dataFormat); formatActFour->setActionGroup(dataFormat); formatActEight->setActionGroup(dataFormat); formatActOneOctal->setActionGroup(dataFormat); formatActTwoOctal->setActionGroup(dataFormat); formatActFourOctal->setActionGroup(dataFormat); formatActEightOctal->setActionGroup(dataFormat); formatAct32bitFloat->setActionGroup(dataFormat); formatAct64bitFloat->setActionGroup(dataFormat); formatAct80bitFloat->setActionGroup(dataFormat); formatActOne->setShortcut(QKeySequence("Ctrl+1")); formatActTwo->setShortcut(QKeySequence("Ctrl+2")); formatActFour->setShortcut(QKeySequence("Ctrl+4")); formatActEight->setShortcut(QKeySequence("Ctrl+8")); formatActOneOctal->setShortcut(QKeySequence("Ctrl+3")); formatActTwoOctal->setShortcut(QKeySequence("Ctrl+5")); formatActFourOctal->setShortcut(QKeySequence("Ctrl+7")); formatActEightOctal->setShortcut(QKeySequence("Ctrl+9")); formatAct32bitFloat->setShortcut(QKeySequence("Ctrl+Shift+F")); formatAct64bitFloat->setShortcut(QKeySequence("Ctrl+Shift+D")); formatAct80bitFloat->setShortcut(QKeySequence("Ctrl+Shift+E")); formatActOne->setChecked(true); connect(dataFormat, &QActionGroup::triggered, this, &MemoryWindow::formatChanged); // Create an address display group QActionGroup *addressGroup = new QActionGroup(this); addressGroup->setObjectName("addressgroup"); QAction *addressActLogical = new QAction("Logical Addresses", this); QAction *addressActPhysical = new QAction("Physical Addresses", this); addressActLogical->setData(false); addressActPhysical->setData(true); addressActLogical->setCheckable(true); addressActPhysical->setCheckable(true); addressActLogical->setActionGroup(addressGroup); addressActPhysical->setActionGroup(addressGroup); addressActLogical->setShortcut(QKeySequence("Ctrl+G")); addressActPhysical->setShortcut(QKeySequence("Ctrl+Y")); addressActLogical->setChecked(true); connect(addressGroup, &QActionGroup::triggered, this, &MemoryWindow::addressChanged); // Create an address radix group QActionGroup *radixGroup = new QActionGroup(this); radixGroup->setObjectName("radixgroup"); QAction *radixActHexadecimal = new QAction("Hexadecimal Addresses", this); QAction *radixActDecimal = new QAction("Decimal Addresses", this); QAction *radixActOctal = new QAction("Octal Addresses", this); radixActHexadecimal->setData(16); radixActDecimal->setData(10); radixActOctal->setData(8); radixActHexadecimal->setCheckable(true); radixActDecimal->setCheckable(true); radixActOctal->setCheckable(true); radixActHexadecimal->setActionGroup(radixGroup); radixActDecimal->setActionGroup(radixGroup); radixActOctal->setActionGroup(radixGroup); radixActHexadecimal->setShortcut(QKeySequence("Ctrl+Shift+H")); radixActOctal->setShortcut(QKeySequence("Ctrl+Shift+O")); radixActHexadecimal->setChecked(true); connect(radixGroup, &QActionGroup::triggered, this, &MemoryWindow::radixChanged); // Create a reverse view radio QAction *reverseAct = new QAction("Reverse View", this); reverseAct->setObjectName("reverse"); reverseAct->setCheckable(true); reverseAct->setShortcut(QKeySequence("Ctrl+R")); connect(reverseAct, &QAction::toggled, this, &MemoryWindow::reverseChanged); // Create increase and decrease bytes-per-line actions QAction *increaseBplAct = new QAction("Increase Bytes Per Line", this); QAction *decreaseBplAct = new QAction("Decrease Bytes Per Line", this); increaseBplAct->setShortcut(QKeySequence("Ctrl+P")); decreaseBplAct->setShortcut(QKeySequence("Ctrl+O")); connect(increaseBplAct, &QAction::triggered, this, &MemoryWindow::increaseBytesPerLine); connect(decreaseBplAct, &QAction::triggered, this, &MemoryWindow::decreaseBytesPerLine); // Assemble the options menu QMenu *optionsMenu = menuBar()->addMenu("&Options"); optionsMenu->addActions(dataFormat->actions()); optionsMenu->addSeparator(); optionsMenu->addActions(addressGroup->actions()); optionsMenu->addSeparator(); optionsMenu->addActions(radixGroup->actions()); optionsMenu->addSeparator(); optionsMenu->addAction(reverseAct); optionsMenu->addSeparator(); optionsMenu->addAction(increaseBplAct); optionsMenu->addAction(decreaseBplAct); // // Initialize // populateComboBox(); // Set to the current CPU's memory view setToCurrentCpu(); } MemoryWindow::~MemoryWindow() { } void MemoryWindow::saveConfigurationToNode(util::xml::data_node &node) { WindowQt::saveConfigurationToNode(node); node.set_attribute_int(ATTR_WINDOW_TYPE, WINDOW_TYPE_MEMORY_VIEWER); debug_view_memory &memView = *m_memTable->view(); node.set_attribute_int(ATTR_WINDOW_MEMORY_REGION, m_memTable->sourceIndex()); node.set_attribute_int(ATTR_WINDOW_MEMORY_REVERSE_COLUMNS, memView.reverse() ? 1 : 0); node.set_attribute_int(ATTR_WINDOW_MEMORY_ADDRESS_MODE, memView.physical() ? 1 : 0); node.set_attribute_int(ATTR_WINDOW_MEMORY_ADDRESS_RADIX, memView.address_radix()); node.set_attribute_int(ATTR_WINDOW_MEMORY_DATA_FORMAT, int(memView.get_data_format())); node.set_attribute_int(ATTR_WINDOW_MEMORY_ROW_CHUNKS, memView.chunks_per_row()); node.add_child(NODE_WINDOW_EXPRESSION, memView.expression()); } void MemoryWindow::memoryRegionChanged(int index) { if (index < m_memTable->view()->source_count()) { m_memTable->view()->set_source(*m_memTable->view()->source(index)); m_memTable->viewport()->update(); // Update the data format radio buttons to the memory region's default debug_view_memory *const memView = m_memTable->view(); QActionGroup *const dataFormat = findChild("dataformat"); for (QAction *action : dataFormat->actions()) { if (debug_view_memory::data_format(action->data().toInt()) == memView->get_data_format()) { action->setChecked(true); break; } } QActionGroup *radixGroup = findChild("radixgroup"); for (QAction *action : radixGroup->actions()) { if (action->data().toInt() == memView->address_radix()) { action->setChecked(true); break; } } } } void MemoryWindow::expressionSubmitted() { const QString expression = m_inputEdit->text(); m_memTable->view()->set_expression(expression.toLocal8Bit().data()); // Make the cursor pop m_memTable->view()->set_cursor_visible(true); // Check where the cursor is and adjust the scroll accordingly debug_view_xy cursorPosition = m_memTable->view()->cursor_position(); // TODO: check if the region is already visible? m_memTable->verticalScrollBar()->setValue(cursorPosition.y); m_memTable->update(); m_memTable->viewport()->update(); } void MemoryWindow::formatChanged(QAction* changedTo) { debug_view_memory *const memView = m_memTable->view(); memView->set_data_format(debug_view_memory::data_format(changedTo->data().toInt())); m_memTable->viewport()->update(); } void MemoryWindow::addressChanged(QAction* changedTo) { debug_view_memory *const memView = m_memTable->view(); memView->set_physical(changedTo->data().toBool()); m_memTable->viewport()->update(); } void MemoryWindow::radixChanged(QAction* changedTo) { debug_view_memory *const memView = m_memTable->view(); memView->set_address_radix(changedTo->data().toInt()); m_memTable->viewport()->update(); } void MemoryWindow::reverseChanged(bool changedTo) { debug_view_memory *const memView = m_memTable->view(); memView->set_reverse(changedTo); m_memTable->viewport()->update(); } void MemoryWindow::increaseBytesPerLine(bool changedTo) { debug_view_memory *const memView = m_memTable->view(); memView->set_chunks_per_row(memView->chunks_per_row() + 1); m_memTable->viewport()->update(); } void MemoryWindow::decreaseBytesPerLine(bool checked) { debug_view_memory *const memView = m_memTable->view(); memView->set_chunks_per_row(memView->chunks_per_row() - 1); m_memTable->viewport()->update(); } void MemoryWindow::populateComboBox() { if (!m_memTable) return; m_memoryComboBox->clear(); for (auto &source : m_memTable->view()->source_list()) m_memoryComboBox->addItem(source->name()); } void MemoryWindow::setToCurrentCpu() { device_t *curCpu = m_machine.debugger().console().get_visible_cpu(); if (curCpu) { const debug_view_source *source = m_memTable->view()->source_for_device(curCpu); if (source) { const int listIndex = m_memTable->view()->source_index(*source); m_memoryComboBox->setCurrentIndex(listIndex); } } } //========================================================================= // DebuggerMemView //========================================================================= void DebuggerMemView::addItemsToContextMenu(QMenu *menu) { DebuggerView::addItemsToContextMenu(menu); if (view()->cursor_visible()) { debug_view_memory &memView = *view(); debug_view_memory_source const &source = downcast(*memView.source()); address_space *const addressSpace = source.space(); if (addressSpace) { // get the last known PC to write to this memory location debug_view_xy const pos = memView.cursor_position(); offs_t const address = addressSpace->byte_to_address(memView.addressAtCursorPosition(pos)); offs_t a = address & addressSpace->logaddrmask(); bool good = false; if (!addressSpace->device().memory().translate(addressSpace->spacenum(), TRANSLATE_READ_DEBUG, a)) { m_lastPc = "Bad address"; } else { uint64_t memValue = addressSpace->unmap(); auto dis = addressSpace->device().machine().disable_side_effects(); switch (addressSpace->data_width()) { case 8: memValue = addressSpace->read_byte(a); break; case 16: memValue = addressSpace->read_word_unaligned(a); break; case 32: memValue = addressSpace->read_dword_unaligned(a); break; case 64: memValue = addressSpace->read_qword_unaligned(a); break; } offs_t const pc = source.device()->debug()->track_mem_pc_from_space_address_data( addressSpace->spacenum(), address, memValue); if (pc != offs_t(-1)) { if (addressSpace->is_octal()) m_lastPc = QString("Address %1 written at PC=%2").arg(address, 2, 8).arg(pc, 2, 8); else m_lastPc = QString("Address %1 written at PC=%2").arg(address, 2, 16).arg(pc, 2, 16); good = true; } else { m_lastPc = "Unknown PC"; } } if (!menu->isEmpty()) menu->addSeparator(); QAction *const act = new QAction(m_lastPc, menu); act->setEnabled(good); connect(act, &QAction::triggered, this, &DebuggerMemView::copyLastPc); menu->addAction(act); } } } void DebuggerMemView::copyLastPc() { QApplication::clipboard()->setText(m_lastPc); } //========================================================================= // MemoryWindowQtConfig //========================================================================= void MemoryWindowQtConfig::applyToQWidget(QWidget *widget) { WindowQtConfig::applyToQWidget(widget); MemoryWindow *window = dynamic_cast(widget); QComboBox *memoryRegion = window->findChild("memoryregion"); memoryRegion->setCurrentIndex(m_memoryRegion); QAction *reverse = window->findChild("reverse"); if (m_reverse) reverse->trigger(); QActionGroup *const addressGroup = window->findChild("addressgroup"); for (QAction *action : addressGroup->actions()) { if (action->data().toBool() == m_addressMode) { action->trigger(); break; } } QActionGroup *const radixGroup = window->findChild("radixgroup"); for (QAction *action : radixGroup->actions()) { if (action->data().toInt() == m_addressRadix) { action->trigger(); break; } } QActionGroup *const dataFormat = window->findChild("dataformat"); for (QAction *action : dataFormat->actions()) { if (action->data().toInt() == m_dataFormat) { action->trigger(); break; } } } void MemoryWindowQtConfig::recoverFromXmlNode(util::xml::data_node const &node) { WindowQtConfig::recoverFromXmlNode(node); m_memoryRegion = node.get_attribute_int(ATTR_WINDOW_MEMORY_REGION, m_memoryRegion); m_reverse = node.get_attribute_int(ATTR_WINDOW_MEMORY_REVERSE_COLUMNS, m_reverse); m_addressMode = node.get_attribute_int(ATTR_WINDOW_MEMORY_ADDRESS_MODE, m_addressMode); m_addressRadix = node.get_attribute_int(ATTR_WINDOW_MEMORY_ADDRESS_RADIX, m_addressRadix); m_dataFormat = node.get_attribute_int(ATTR_WINDOW_MEMORY_DATA_FORMAT, m_dataFormat); } } // namespace osd::debugger::qt