debuggermodels.cpp (13071B)
1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "debuggermodels.h" 5 6 #include "core/cpu_core.h" 7 #include "core/cpu_core_private.h" 8 #include "core/cpu_disasm.h" 9 10 #include "common/small_string.h" 11 12 #include <QtGui/QColor> 13 #include <QtGui/QIcon> 14 #include <QtGui/QPalette> 15 #include <QtWidgets/QApplication> 16 #include <QtWidgets/QMessageBox> 17 #include <QtWidgets/QPushButton> 18 19 static constexpr int NUM_COLUMNS = 5; 20 static constexpr int STACK_RANGE = 128; 21 static constexpr u32 STACK_VALUE_SIZE = sizeof(u32); 22 23 DebuggerCodeModel::DebuggerCodeModel(QObject* parent /*= nullptr*/) : QAbstractTableModel(parent) 24 { 25 resetCodeView(0); 26 m_pc_pixmap = QIcon(QStringLiteral(":/icons/debug-pc.png")).pixmap(QSize(12, 12)); 27 m_breakpoint_pixmap = QIcon(QStringLiteral(":/icons/media-record.png")).pixmap(QSize(12, 12)); 28 } 29 30 DebuggerCodeModel::~DebuggerCodeModel() 31 { 32 } 33 34 int DebuggerCodeModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const 35 { 36 return static_cast<int>((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE); 37 } 38 39 int DebuggerCodeModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const 40 { 41 return NUM_COLUMNS; 42 } 43 44 int DebuggerCodeModel::getRowForAddress(VirtualMemoryAddress address) const 45 { 46 return static_cast<int>((address - m_code_region_start) / CPU::INSTRUCTION_SIZE); 47 } 48 49 VirtualMemoryAddress DebuggerCodeModel::getAddressForRow(int row) const 50 { 51 return m_code_region_start + (static_cast<u32>(row) * CPU::INSTRUCTION_SIZE); 52 } 53 54 VirtualMemoryAddress DebuggerCodeModel::getAddressForIndex(QModelIndex index) const 55 { 56 return getAddressForRow(index.row()); 57 } 58 59 int DebuggerCodeModel::getRowForPC() const 60 { 61 return getRowForAddress(m_last_pc); 62 } 63 64 QVariant DebuggerCodeModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const 65 { 66 if (index.column() < 0 || index.column() >= NUM_COLUMNS) 67 return QVariant(); 68 69 if (role == Qt::DisplayRole) 70 { 71 const VirtualMemoryAddress address = getAddressForRow(index.row()); 72 switch (index.column()) 73 { 74 case 0: 75 // breakpoint 76 return QVariant(); 77 78 case 1: 79 { 80 // Address 81 return QVariant(QString::asprintf("0x%08X", address)); 82 } 83 84 case 2: 85 { 86 // Bytes 87 u32 instruction_bits; 88 if (!CPU::SafeReadInstruction(address, &instruction_bits)) 89 return tr("<invalid>"); 90 91 return QString::asprintf("%08X", instruction_bits); 92 } 93 94 case 3: 95 { 96 // Instruction 97 u32 instruction_bits; 98 if (!CPU::SafeReadInstruction(address, &instruction_bits)) 99 return tr("<invalid>"); 100 101 SmallString str; 102 CPU::DisassembleInstruction(&str, address, instruction_bits); 103 return QString::fromUtf8(str.c_str(), static_cast<int>(str.length())); 104 } 105 106 case 4: 107 { 108 // Comment 109 if (address != m_last_pc) 110 return QVariant(); 111 112 u32 instruction_bits; 113 if (!CPU::SafeReadInstruction(address, &instruction_bits)) 114 return tr("<invalid>"); 115 116 TinyString str; 117 CPU::DisassembleInstructionComment(&str, address, instruction_bits); 118 return QString::fromUtf8(str.c_str(), static_cast<int>(str.length())); 119 } 120 121 default: 122 return QVariant(); 123 } 124 } 125 else if (role == Qt::DecorationRole) 126 { 127 if (index.column() == 0) 128 { 129 // breakpoint 130 const VirtualMemoryAddress address = getAddressForRow(index.row()); 131 if (m_last_pc == address) 132 return m_pc_pixmap; 133 else if (hasBreakpointAtAddress(address)) 134 return m_breakpoint_pixmap; 135 } 136 137 return QVariant(); 138 } 139 else if (role == Qt::BackgroundRole) 140 { 141 const VirtualMemoryAddress address = getAddressForRow(index.row()); 142 143 // breakpoint 144 if (hasBreakpointAtAddress(address)) 145 return QVariant(QColor(171, 97, 107)); 146 147 // if (address == m_last_pc) 148 // return QApplication::palette().toolTipBase(); 149 if (address == m_last_pc) 150 return QColor(100, 100, 0); 151 else 152 return QVariant(); 153 } 154 else if (role == Qt::ForegroundRole) 155 { 156 const VirtualMemoryAddress address = getAddressForRow(index.row()); 157 158 // if (address == m_last_pc) 159 // return QApplication::palette().toolTipText(); 160 if (address == m_last_pc || hasBreakpointAtAddress(address)) 161 return QColor(Qt::white); 162 else 163 return QVariant(); 164 } 165 else 166 { 167 return QVariant(); 168 } 169 } 170 171 QVariant DebuggerCodeModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const 172 { 173 if (orientation != Qt::Horizontal) 174 return QVariant(); 175 176 if (role != Qt::DisplayRole) 177 return QVariant(); 178 179 switch (section) 180 { 181 case 1: 182 return tr("Address"); 183 case 2: 184 return tr("Bytes"); 185 case 3: 186 return tr("Instruction"); 187 case 4: 188 return tr("Comment"); 189 case 0: 190 default: 191 return QVariant(); 192 } 193 } 194 195 bool DebuggerCodeModel::updateRegion(VirtualMemoryAddress address) 196 { 197 CPU::Segment segment = CPU::GetSegmentForAddress(address); 198 std::optional<Bus::MemoryRegion> region = Bus::GetMemoryRegionForAddress(CPU::VirtualAddressToPhysical(address)); 199 if (!region.has_value() || (address >= m_code_region_start && address < m_code_region_end)) 200 return false; 201 202 static constexpr unsigned NUM_INSTRUCTIONS_BEFORE = 4096; 203 static constexpr unsigned NUM_INSTRUCTIONS_AFTER = 4096; 204 static constexpr unsigned NUM_BYTES_BEFORE = NUM_INSTRUCTIONS_BEFORE * sizeof(CPU::Instruction); 205 static constexpr unsigned NUM_BYTES_AFTER = NUM_INSTRUCTIONS_AFTER * sizeof(CPU::Instruction); 206 207 const VirtualMemoryAddress start_address = 208 CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionStart(region.value()), segment); 209 const VirtualMemoryAddress end_address = 210 CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionEnd(region.value()), segment); 211 212 beginResetModel(); 213 m_code_region_start = ((address - start_address) < NUM_BYTES_BEFORE) ? start_address : (address - NUM_BYTES_BEFORE); 214 m_code_region_end = ((end_address - address) < NUM_BYTES_AFTER) ? end_address : (address + NUM_BYTES_AFTER); 215 m_current_segment = segment; 216 m_current_code_region = region.value(); 217 endResetModel(); 218 return true; 219 } 220 221 bool DebuggerCodeModel::emitDataChangedForAddress(VirtualMemoryAddress address) 222 { 223 CPU::Segment segment = CPU::GetSegmentForAddress(address); 224 std::optional<Bus::MemoryRegion> region = Bus::GetMemoryRegionForAddress(CPU::VirtualAddressToPhysical(address)); 225 if (!region.has_value() || segment != m_current_segment || region != m_current_code_region) 226 return false; 227 228 const int row = getRowForAddress(address); 229 emit dataChanged(index(row, 0), index(row, NUM_COLUMNS - 1)); 230 return true; 231 } 232 233 bool DebuggerCodeModel::hasBreakpointAtAddress(VirtualMemoryAddress address) const 234 { 235 return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end(); 236 } 237 238 void DebuggerCodeModel::resetCodeView(VirtualMemoryAddress start_address) 239 { 240 updateRegion(start_address); 241 } 242 243 void DebuggerCodeModel::setPC(VirtualMemoryAddress pc) 244 { 245 const VirtualMemoryAddress prev_pc = m_last_pc; 246 247 m_last_pc = pc; 248 if (!updateRegion(pc)) 249 { 250 emitDataChangedForAddress(prev_pc); 251 emitDataChangedForAddress(pc); 252 } 253 } 254 255 void DebuggerCodeModel::ensureAddressVisible(VirtualMemoryAddress address) 256 { 257 updateRegion(address); 258 } 259 260 void DebuggerCodeModel::setBreakpointList(std::vector<VirtualMemoryAddress> bps) 261 { 262 clearBreakpoints(); 263 264 m_breakpoints = std::move(bps); 265 for (VirtualMemoryAddress bp : m_breakpoints) 266 emitDataChangedForAddress(bp); 267 } 268 269 void DebuggerCodeModel::clearBreakpoints() 270 { 271 std::vector<VirtualMemoryAddress> old_bps(std::move(m_breakpoints)); 272 273 for (VirtualMemoryAddress old_bp : old_bps) 274 emitDataChangedForAddress(old_bp); 275 } 276 277 void DebuggerCodeModel::setBreakpointState(VirtualMemoryAddress address, bool enabled) 278 { 279 if (enabled) 280 { 281 if (std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end()) 282 return; 283 284 m_breakpoints.push_back(address); 285 emitDataChangedForAddress(address); 286 } 287 else 288 { 289 auto it = std::find(m_breakpoints.begin(), m_breakpoints.end(), address); 290 if (it == m_breakpoints.end()) 291 return; 292 293 m_breakpoints.erase(it); 294 emitDataChangedForAddress(address); 295 } 296 } 297 298 DebuggerRegistersModel::DebuggerRegistersModel(QObject* parent /*= nullptr*/) : QAbstractListModel(parent) 299 { 300 } 301 302 DebuggerRegistersModel::~DebuggerRegistersModel() 303 { 304 } 305 306 int DebuggerRegistersModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const 307 { 308 return static_cast<int>(CPU::NUM_DEBUGGER_REGISTER_LIST_ENTRIES); 309 } 310 311 int DebuggerRegistersModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const 312 { 313 return 2; 314 } 315 316 QVariant DebuggerRegistersModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const 317 { 318 u32 reg_index = static_cast<u32>(index.row()); 319 if (reg_index >= CPU::NUM_DEBUGGER_REGISTER_LIST_ENTRIES) 320 return QVariant(); 321 322 if (index.column() < 0 || index.column() > 1) 323 return QVariant(); 324 325 switch (index.column()) 326 { 327 case 0: // address 328 { 329 if (role == Qt::DisplayRole) 330 return QString::fromUtf8(CPU::g_debugger_register_list[reg_index].name); 331 } 332 break; 333 334 case 1: // data 335 { 336 if (role == Qt::DisplayRole) 337 { 338 return QString::asprintf("0x%08X", m_reg_values[reg_index]); 339 } 340 else if (role == Qt::ForegroundRole) 341 { 342 if (m_reg_values[reg_index] != m_old_reg_values[reg_index]) 343 return QColor(255, 50, 50); 344 } 345 } 346 break; 347 348 default: 349 break; 350 } 351 352 return QVariant(); 353 } 354 355 QVariant DebuggerRegistersModel::headerData(int section, Qt::Orientation orientation, 356 int role /*= Qt::DisplayRole*/) const 357 { 358 if (orientation != Qt::Horizontal) 359 return QVariant(); 360 361 if (role != Qt::DisplayRole) 362 return QVariant(); 363 364 switch (section) 365 { 366 case 0: 367 return tr("Register"); 368 case 1: 369 return tr("Value"); 370 default: 371 return QVariant(); 372 } 373 } 374 375 void DebuggerRegistersModel::updateValues() 376 { 377 beginResetModel(); 378 379 for (u32 i = 0; i < CPU::NUM_DEBUGGER_REGISTER_LIST_ENTRIES; i++) 380 m_reg_values[i] = *CPU::g_debugger_register_list[i].value_ptr; 381 382 endResetModel(); 383 } 384 385 void DebuggerRegistersModel::saveCurrentValues() 386 { 387 m_old_reg_values = m_reg_values; 388 } 389 390 DebuggerStackModel::DebuggerStackModel(QObject* parent /*= nullptr*/) : QAbstractListModel(parent) 391 { 392 } 393 394 DebuggerStackModel::~DebuggerStackModel() 395 { 396 } 397 398 int DebuggerStackModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const 399 { 400 return STACK_RANGE * 2; 401 } 402 403 int DebuggerStackModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const 404 { 405 return 2; 406 } 407 408 QVariant DebuggerStackModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const 409 { 410 if (index.column() < 0 || index.column() > 1) 411 return QVariant(); 412 413 if (role != Qt::DisplayRole) 414 return QVariant(); 415 416 const u32 sp = CPU::g_state.regs.sp; 417 const VirtualMemoryAddress address = 418 (sp - static_cast<u32>(STACK_RANGE * STACK_VALUE_SIZE)) + static_cast<u32>(index.row()) * STACK_VALUE_SIZE; 419 420 if (index.column() == 0) 421 return QString::asprintf("0x%08X", address); 422 423 u32 value; 424 if (!CPU::SafeReadMemoryWord(address, &value)) 425 return tr("<invalid>"); 426 427 return QString::asprintf("0x%08X", ZeroExtend32(value)); 428 } 429 430 QVariant DebuggerStackModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const 431 { 432 if (orientation != Qt::Horizontal) 433 return QVariant(); 434 435 if (role != Qt::DisplayRole) 436 return QVariant(); 437 438 switch (section) 439 { 440 case 0: 441 return tr("Address"); 442 case 1: 443 return tr("Value"); 444 default: 445 return QVariant(); 446 } 447 } 448 449 void DebuggerStackModel::invalidateView() 450 { 451 beginResetModel(); 452 endResetModel(); 453 } 454 455 DebuggerAddBreakpointDialog::DebuggerAddBreakpointDialog(QWidget* parent /*= nullptr*/) : QDialog(parent) 456 { 457 m_ui.setupUi(this); 458 connect(m_ui.buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, 459 &DebuggerAddBreakpointDialog::okClicked); 460 } 461 462 DebuggerAddBreakpointDialog::~DebuggerAddBreakpointDialog() = default; 463 464 void DebuggerAddBreakpointDialog::okClicked() 465 { 466 const QString address_str = m_ui.address->text(); 467 m_address = 0; 468 bool ok = false; 469 470 if (!address_str.isEmpty()) 471 { 472 if (address_str.startsWith("0x")) 473 m_address = address_str.mid(2).toUInt(&ok, 16); 474 else 475 m_address = address_str.toUInt(&ok, 16); 476 477 if (!ok) 478 { 479 QMessageBox::critical( 480 this, qApp->translate("DebuggerWindow", "Error"), 481 qApp->translate("DebuggerWindow", "Invalid address. It should be in hex (0x12345678 or 12345678)")); 482 return; 483 } 484 485 if (m_ui.read->isChecked()) 486 m_type = CPU::BreakpointType::Read; 487 else if (m_ui.write->isChecked()) 488 m_type = CPU::BreakpointType::Write; 489 else 490 m_type = CPU::BreakpointType::Execute; 491 492 accept(); 493 } 494 }