diff --git a/.github/workflows/build.linux.workflow.yml b/.github/workflows/build.linux.workflow.yml index 4dd2f01d4..24e7fb680 100644 --- a/.github/workflows/build.linux.workflow.yml +++ b/.github/workflows/build.linux.workflow.yml @@ -6,7 +6,7 @@ permissions: on: push: - branches: [ master, test-ci ] + branches: [ master, test-ci, feature/source-level-debugging ] pull_request: branches: [ master ] diff --git a/.github/workflows/build.mac.workflow.yml b/.github/workflows/build.mac.workflow.yml index 48ff0a3e7..d5987ed3f 100644 --- a/.github/workflows/build.mac.workflow.yml +++ b/.github/workflows/build.mac.workflow.yml @@ -6,7 +6,7 @@ permissions: on: push: - branches: [ master, test-ci ] + branches: [ master, test-ci, feature/source-level-debugging ] pull_request: branches: [ master ] diff --git a/.github/workflows/build.windows.workflow.yml b/.github/workflows/build.windows.workflow.yml index b01ab09e6..15493e50c 100644 --- a/.github/workflows/build.windows.workflow.yml +++ b/.github/workflows/build.windows.workflow.yml @@ -6,7 +6,7 @@ permissions: on: push: - branches: [ master, test-ci ] + branches: [ master, test-ci, feature/source-level-debugging ] pull_request: branches: [ master ] diff --git a/core/cpu.c b/core/cpu.c index 026cbe464..b435e04f8 100644 --- a/core/cpu.c +++ b/core/cpu.c @@ -459,10 +459,15 @@ static void cpu_trap(void) { cpu_trap_rewind(1); } -static void cpu_jump(uint32_t address, bool mode) { +static void cpu_jump(uint32_t address, bool mode, bool absolute) { cpu_prefetch(address, mode); #ifdef DEBUG_SUPPORT - debug_record_ret(cpu.registers.PC, mode); + debug_record_jump(address); + if (absolute) { + debug_record_ret(cpu.registers.PC, mode); + } +#else + (void)absolute; #endif } @@ -480,7 +485,7 @@ static void cpu_return(void) { } else { address = cpu_pop_word(); } - cpu_jump(address, mode); + cpu_jump(address, mode, true); } static void cpu_execute_alu(int i, uint8_t v) { @@ -654,10 +659,14 @@ static void cpu_execute_bli() { uint_fast8_t xp = cpu.context.x << 2 | cpu.context.p; int_fast8_t delta = cpu.context.q ? -1 : 1; bool repeat = (cpu.context.x | cpu.context.p) & 1; +#ifdef DEBUG_SUPPORT + bool first = true; +#endif do { #ifdef DEBUG_SUPPORT if (cpu.inBlock) { - debug_inst_repeat(); + debug_inst_repeat(first); + first = false; } #endif switch (cpu.context.z) { @@ -973,12 +982,12 @@ void cpu_execute(void) { s = cpu_fetch_offset(); if (--r->B) { cpu.cycles++; - cpu_prefetch(cpu_mask_mode(r->PC + s, cpu.L), cpu.ADL); + cpu_jump(cpu_mask_mode(r->PC + s, cpu.L), cpu.ADL, false); } break; case 3: /* JR d */ s = cpu_fetch_offset(); - cpu_prefetch(cpu_mask_mode(r->PC + s, cpu.L), cpu.ADL); + cpu_jump(cpu_mask_mode(r->PC + s, cpu.L), cpu.ADL, false); break; case 4: case 5: @@ -987,7 +996,7 @@ void cpu_execute(void) { s = cpu_fetch_offset(); if (cpu_read_cc(context.y - 4)) { cpu.cycles++; - cpu_prefetch(cpu_mask_mode(r->PC + s, cpu.L), cpu.ADL); + cpu_jump(cpu_mask_mode(r->PC + s, cpu.L), cpu.ADL, false); } break; } @@ -1208,7 +1217,7 @@ void cpu_execute(void) { break; case 2: /* JP (rr) */ cpu_prefetch_discard(); - cpu_jump(cpu_read_index(), cpu.L); + cpu_jump(cpu_read_index(), cpu.L, true); break; case 3: /* LD SP, HL */ cpu_write_sp(cpu_read_index()); @@ -1220,7 +1229,7 @@ void cpu_execute(void) { case 2: /* JP cc[y], nn */ if (cpu_read_cc(context.y)) { cpu.cycles++; - cpu_jump(cpu_fetch_word_no_prefetch(), cpu.L); + cpu_jump(cpu_fetch_word_no_prefetch(), cpu.L, true); } else { cpu_fetch_word(); } @@ -1233,7 +1242,7 @@ void cpu_execute(void) { switch (context.y) { case 0: /* JP nn */ cpu.cycles++; - cpu_jump(cpu_fetch_word_no_prefetch(), cpu.L); + cpu_jump(cpu_fetch_word_no_prefetch(), cpu.L, true); break; case 1: /* 0xCB prefixed opcodes */ w = cpu_index_address(); diff --git a/core/debug/debug.c b/core/debug/debug.c index a41b6507e..0d8f5a9cd 100644 --- a/core/debug/debug.c +++ b/core/debug/debug.c @@ -4,8 +4,12 @@ #include "../atomics.h" #include "../mem.h" #include "../emu.h" + #include "../cpu.h" +#include "../defines.h" +#include "../emu.h" #include "../flash.h" +#include "../mem.h" #include "../vat.h" #include @@ -171,19 +175,38 @@ void debug_flag(int mask, bool set) { void debug_step(int mode, uint32_t addr) { switch (mode) { case DBG_STEP_IN: + case DBG_RUN_IN: debug.step = true; + debug.stepOver = false; + debug.stepOut = ~0u; break; case DBG_STEP_OVER: + case DBG_RUN_OVER: debug.step = true; debug.stepOver = true; + debug.stepOut = ~0u; + break; + case DBG_RUN_AGAIN: + gui_debug_close(); + debug.step = true; + debug.stepOut = ~0u; break; case DBG_STEP_OUT: + debug.step = debug.stepOver = false; + fallthrough; + case DBG_RUN_OUT: gui_debug_close(); debug.stepOut = debug.stackIndex; break; + } + switch (mode) { case DBG_STEP_NEXT: case DBG_RUN_UNTIL: gui_debug_close(); + fallthrough; + case DBG_RUN_IN: + case DBG_RUN_OVER: + case DBG_RUN_OUT: debug.tempExec = addr; break; case DBG_BASIC_STEP_IN: @@ -196,6 +219,9 @@ void debug_step(int mode, uint32_t addr) { debug.stepBasicEnd = addr >> 16; debug_get_executing_basic_prgm(debug.stepBasicPrgm); break; + default: + debug.tempExec = ~0u; + break; } } @@ -212,7 +238,7 @@ void debug_clear_basic_step(void) { void debug_inst_start(void) { uint32_t pc = cpu.registers.PC; debug.addr[pc] |= DBG_INST_START_MARKER; - if (debug.step && !(debug.addr[pc] & DBG_MASK_EXEC) && pc != debug.tempExec) { + if (debug.step && !(debug.addr[pc] & DBG_MASK_EXEC) && debug.tempExec == ~0u) { debug.step = debug.stepOver = false; debug_open(DBG_STEP, cpu.registers.PC); } @@ -228,10 +254,12 @@ void debug_inst_fetch(void) { } } -void debug_inst_repeat(void) { +void debug_inst_repeat(bool first) { if (debug.step) { - if (debug.stepOver) { - gui_debug_close(); + if (debug.stepOver || debug.tempExec != ~0u) { + if (first) { + gui_debug_close(); + } } else { debug.step = false; debug_open(DBG_STEP, cpu.registers.PC); @@ -239,6 +267,13 @@ void debug_inst_repeat(void) { } } +void debug_record_jump(uint32_t addr) { + (void)addr; + if (debug.step) { + debug.tempExec = ~0u; + } +} + void debug_record_call(uint32_t retAddr, bool mode) { uint32_t stack = cpu_address_mode(cpu.registers.stack[mode].hl, mode); uint32_t index = (debug.stackIndex + 1) & DBG_STACK_MASK; @@ -252,10 +287,14 @@ void debug_record_call(uint32_t retAddr, bool mode) { if (debug.stackSize < DBG_STACK_SIZE) { debug.stackSize++; } - if (debug.stepOver) { - gui_debug_close(); - debug.step = debug.stepOver = false; - debug.stepOut = index; + if (debug.step) { + if (debug.stepOver) { + gui_debug_close(); + debug.step = false; + debug.stepOut = index; + } else { + debug.tempExec = ~0u; + } } } diff --git a/core/debug/debug.h b/core/debug/debug.h index 3a4fd1cba..6dc0544a0 100644 --- a/core/debug/debug.h +++ b/core/debug/debug.h @@ -68,7 +68,8 @@ void debug_free(void); /* call after emulation end void debug_set_pc(uint32_t addr); /* when in gui debug set program counter */ void debug_inst_start(void); void debug_inst_fetch(void); -void debug_inst_repeat(void); +void debug_inst_repeat(bool first); +void debug_record_jump(uint32_t addr); void debug_record_call(uint32_t retAddr, bool stack); void debug_record_ret(uint32_t retAddr, bool stack); void debug_watch(uint32_t addr, int mask, bool set); /* set a breakpoint or a watchpoint */ @@ -175,6 +176,10 @@ enum { DBG_STEP_OVER, DBG_STEP_NEXT, DBG_RUN_UNTIL, + DBG_RUN_IN, + DBG_RUN_OVER, + DBG_RUN_AGAIN, + DBG_RUN_OUT, DBG_BASIC_STEP_IN, DBG_BASIC_STEP_NEXT, }; diff --git a/gui/qt/CEmu.pro b/gui/qt/CEmu.pro index f31365b75..29ffe8677 100644 --- a/gui/qt/CEmu.pro +++ b/gui/qt/CEmu.pro @@ -82,12 +82,13 @@ CONFIG(release, debug|release) { DEFINES += QT_NO_DEBUG_OUTPUT NDEBUG } else { #This is a debug build + QT += testlib GLOBAL_FLAGS += -g3 } # GCC/clang flags if (!win32-msvc*) { - GLOBAL_FLAGS += -W -Wall -Wextra -Wunused-function -Werror=write-strings -Werror=redundant-decls -Werror=format -Werror=format-security -Werror=return-type -Werror=pointer-arith -Winit-self -Wimplicit-fallthrough + GLOBAL_FLAGS += -W -Wall -Wextra -Wunused-function -Werror=write-strings -Werror=redundant-decls -Werror=format -Werror=format-security -Werror=return-type -Werror=pointer-arith -Winit-self -Wimplicit-fallthrough -Wno-profile-instr-out-of-date -Wno-profile-instr-unprofiled GLOBAL_FLAGS += -ffunction-sections -fdata-sections -fno-strict-overflow QMAKE_CFLAGS += -std=gnu11 -Werror=implicit-function-declaration -Werror=missing-prototypes @@ -145,13 +146,13 @@ if (!win32-msvc*) { } # Do we have a flag specifying use of libpng-apng from vcpkg? - equals(LIBPNG_APNG_FROM_VCPKG, 1) { - warning("Enabled using libpng-apng from vcpkg. Note that if you do not have vcpkg integrated into MSVC, and/or do not have libpng-apng installed within vcpkg, the build will likely fail.") - # This is a bad hack, but MOC kinda needs it to work correctly... - QMAKE_MOC_OPTIONS += -DPNG_WRITE_APNG_SUPPORTED - } + equals(LIBPNG_APNG_FROM_VCPKG, 1) { + warning("Enabled using libpng-apng from vcpkg. Note that if you do not have vcpkg integrated into MSVC, and/or do not have libpng-apng installed within vcpkg, the build will likely fail.") + # This is a bad hack, but MOC kinda needs it to work correctly... + QMAKE_MOC_OPTIONS += -DPNG_WRITE_APNG_SUPPORTED + } - # Otherwise... + # Otherwise... !equals(LIBPNG_APNG_FROM_VCPKG, 1) { # If we're not using vcpkg, we rely on manual variables to find needed # libpng-apng components. @@ -251,6 +252,12 @@ SOURCES += \ basiccodeviewerwindow.cpp \ sendinghandler.cpp \ debugger.cpp \ + debugger/cdebughighlighter.cpp \ + debugger/debuginfo.cpp \ + debugger/disasm.cpp \ + debugger/hexwidget.cpp \ + debugger/sourceswidget.cpp \ + debugger/visualizerdisplaywidget.cpp \ settings.cpp \ capture/animated-png.c \ keypad/qtkeypadbridge.cpp \ @@ -258,8 +265,6 @@ SOURCES += \ keypad/keypadwidget.cpp \ keypad/rectkey.cpp \ keypad/arrowkey.cpp \ - debugger/hexwidget.cpp \ - debugger/disasm.cpp \ tivars_lib_cpp/src/tivarslib_utils.cpp \ tivars_lib_cpp/src/BinaryFile.cpp \ tivars_lib_cpp/src/TIVarFile.cpp \ @@ -285,7 +290,6 @@ SOURCES += \ tivars_lib_cpp/src/TypeHandlers/STH_FP.cpp \ vartablemodel.cpp \ visualizerwidget.cpp \ - debugger/visualizerdisplaywidget.cpp \ memorywidget.cpp \ archive/extractor.c \ ../../core/bus.c \ @@ -373,8 +377,12 @@ HEADERS += \ keypad/operkey.h \ keypad/arrowkey.h \ capture/animated-png.h \ - debugger/hexwidget.h \ + debugger/cdebughighlighter.h \ + debugger/debuginfo.h \ debugger/disasm.h \ + debugger/hexwidget.h \ + debugger/sourceswidget.h \ + debugger/visualizerdisplaywidget.h \ tivars_lib_cpp/src/tivarslib_utils.h \ tivars_lib_cpp/src/CommonTypes.h \ tivars_lib_cpp/src/BinaryFile.h \ @@ -386,7 +394,6 @@ HEADERS += \ tivars_lib_cpp/src/TypeHandlers/TypeHandlers.h \ vartablemodel.h \ visualizerwidget.h \ - debugger/visualizerdisplaywidget.h \ archive/extractor.h \ ../../core/bus.h \ keyhistorywidget.h \ diff --git a/gui/qt/CMakeLists.txt b/gui/qt/CMakeLists.txt index 27079500c..72c88080f 100644 --- a/gui/qt/CMakeLists.txt +++ b/gui/qt/CMakeLists.txt @@ -131,6 +131,9 @@ set(CEmu_Sources cemuopts.h datawidget.cpp datawidget.h debugger.cpp + debugger/cdebughighlighter.cpp debugger/cdebughighlighter.h + debugger/debuginfo.cpp debugger/debuginfo.h + debugger/sourceswidget.cpp debugger/sourceswidget.h debugger/disasm.cpp debugger/disasm.h debugger/hexwidget.cpp debugger/hexwidget.h debugger/visualizerdisplaywidget.cpp debugger/visualizerdisplaywidget.h diff --git a/gui/qt/debugger.cpp b/gui/qt/debugger.cpp index 8699144ea..bcd9d7f63 100644 --- a/gui/qt/debugger.cpp +++ b/gui/qt/debugger.cpp @@ -105,16 +105,35 @@ void MainWindow::debugEnable() { } void MainWindow::debugStep(int mode) { - if (mode == DBG_RUN_UNTIL) { - debug_step(mode, m_runUntilAddr); - } else { - disasm.base = static_cast(cpu.registers.PC); - disasmGet(true); + uint32_t addr; + switch (mode) { + default: + disasm.base = int32_t(cpu.registers.PC); - m_stepCtx.active = true; - m_stepCtx.seqNext = static_cast(disasm.next); - debug_step(mode, static_cast(disasm.next)); + disasmGet(true); + addr = uint32_t(disasm.next); + m_runLoc = {}; + break; + case DBG_RUN_UNTIL: + addr = m_runUntilAddr; + m_runLoc = {}; + break; + case DBG_RUN_IN: + case DBG_RUN_OVER: + m_runLoc = ui->sources->getLocInfo(cpu.registers.PC); + if (m_runLoc.invalid()) { + debugStep(DBG_STEP_OUT); + return; + } + [[gnu::fallthrough]]; + case DBG_RUN_AGAIN: + case DBG_RUN_OUT: + addr = m_runLoc.end(); + break; } + m_stepCtx.active = true; + m_stepCtx.seqNext = static_cast(disasm.next); + debug_step(mode, addr); emu.resume(); } @@ -145,7 +164,7 @@ void MainWindow::debugImportFile(const QString &file) { goto error; } for (i = 0; i < breakLabel.size(); i++) { - breakAdd(breakLabel.at(i), static_cast(hex2int(breakAddr.at(i))), breakSet.at(i) == TXT_YES, false, false); + breakAdd(breakLabel.at(i), uint32_t(hex2int(breakAddr.at(i))), breakSet.at(i) == TXT_YES, false, false); } // load the watchpoint information @@ -160,8 +179,8 @@ void MainWindow::debugImportFile(const QString &file) { for (i = 0; i < watchLabel.size(); i++) { int mask = (watchR.at(i) == TXT_YES ? DBG_MASK_READ : DBG_MASK_NONE) | (watchW.at(i) == TXT_YES ? DBG_MASK_WRITE : DBG_MASK_NONE); - watchAdd(watchLabel.at(i), static_cast(hex2int(watchLow.at(i))), - static_cast(hex2int(watchHigh.at(i))), mask, false, false); + watchAdd(watchLabel.at(i), uint32_t(hex2int(watchLow.at(i))), + uint32_t(hex2int(watchHigh.at(i))), mask, false, false); } // load the port monitor information @@ -176,7 +195,7 @@ void MainWindow::debugImportFile(const QString &file) { int mask = (portR.at(i) == TXT_YES ? DBG_MASK_PORT_READ : DBG_MASK_NONE) | (portW.at(i) == TXT_YES ? DBG_MASK_PORT_WRITE : DBG_MASK_NONE) | (portF.at(i) == TXT_YES ? DBG_MASK_PORT_FREEZE : DBG_MASK_NONE); - portAdd(static_cast(hex2int(portAddr.at(i))), mask, false); + portAdd(uint16_t(hex2int(portAddr.at(i))), mask, false); } // add all the equate files and load them in @@ -410,7 +429,7 @@ void MainWindow::debugCommand(int reason, uint32_t data) { // This means the program is trying to send us a debug command. Let's see what we can do with that information if (reason >= DBG_NUMBER) { - debugExecute(static_cast(reason - DBG_PORT_RANGE), static_cast(data)); + debugExecute(uint32_t(reason - DBG_PORT_RANGE), uint8_t(data)); return; } @@ -439,13 +458,13 @@ void MainWindow::debugCommand(int reason, uint32_t data) { case DBG_WATCHPOINT_READ: case DBG_WATCHPOINT_WRITE: input = int2hex(data, 6); - addr = static_cast(hex2int(input)); + addr = uint32_t(hex2int(input)); type = (reason == DBG_WATCHPOINT_READ) ? tr("read") : tr("write"); text = tr("Hit ") + type + tr(" watchpoint ") + input; for (int i = 0; i < m_watchpoints->rowCount(); i++) { - uint32_t low = static_cast(hex2int(m_watchpoints->item(i, WATCH_LOW_COL)->text())); - uint32_t high = static_cast(hex2int(m_watchpoints->item(i, WATCH_HIGH_COL)->text())); + uint32_t low = uint32_t(hex2int(m_watchpoints->item(i, WATCH_LOW_COL)->text())); + uint32_t high = uint32_t(hex2int(m_watchpoints->item(i, WATCH_HIGH_COL)->text())); if (addr >= low && addr <= high) { label = m_watchpoints->item(row, WATCH_NAME_COL)->text(); valid = true; @@ -461,7 +480,7 @@ void MainWindow::debugCommand(int reason, uint32_t data) { // Raise the debugger before going to the memory address so the memory docks are updated ui->debuggerLabel->setText(text); debugRaise(); - gotoMemAddrNoRaise(static_cast(hex2int(input))); + gotoMemAddrNoRaise(uint32_t(hex2int(input))); return; case DBG_PORT_READ: case DBG_PORT_WRITE: @@ -491,6 +510,20 @@ void MainWindow::debugCommand(int reason, uint32_t data) { } debugBasicRaise(); return; + case DBG_STEP: + if (!m_runLoc.invalid()) { + auto info = ui->sources->getLocInfo(data); + if (info.invalid()) { + debugStep(DBG_RUN_OUT); + return; + } + if (info.unknown() || info.sameSourceLoc(m_runLoc)) { + m_runLoc.end(info.end()); + debugStep(DBG_RUN_AGAIN); + return; + } + } + [[gnu::fallthrough]]; case DBG_USER: default: debugRaise(); @@ -510,26 +543,26 @@ void MainWindow::debugSync() const { } // Update all the changes in the core - cpu.registers.AF = static_cast(hex2int(ui->afregView->text())); - cpu.registers.HL = static_cast(hex2int(ui->hlregView->text())); - cpu.registers.DE = static_cast(hex2int(ui->deregView->text())); - cpu.registers.BC = static_cast(hex2int(ui->bcregView->text())); - cpu.registers.IX = static_cast(hex2int(ui->ixregView->text())); - cpu.registers.IY = static_cast(hex2int(ui->iyregView->text())); - - cpu.registers._AF = static_cast(hex2int(ui->af_regView->text())); - cpu.registers._HL = static_cast(hex2int(ui->hl_regView->text())); - cpu.registers._DE = static_cast(hex2int(ui->de_regView->text())); - cpu.registers._BC = static_cast(hex2int(ui->bc_regView->text())); - - cpu.registers.SPL = static_cast(hex2int(ui->splregView->text())); - cpu.registers.SPS = static_cast(hex2int(ui->spsregView->text())); - - cpu.registers.MBASE = static_cast(hex2int(ui->mbregView->text())); - cpu.registers.I = static_cast(hex2int(ui->iregView->text())); + cpu.registers.AF = uint16_t(hex2int(ui->afregView->text())); + cpu.registers.HL = uint32_t(hex2int(ui->hlregView->text())); + cpu.registers.DE = uint32_t(hex2int(ui->deregView->text())); + cpu.registers.BC = uint32_t(hex2int(ui->bcregView->text())); + cpu.registers.IX = uint32_t(hex2int(ui->ixregView->text())); + cpu.registers.IY = uint32_t(hex2int(ui->iyregView->text())); + + cpu.registers._AF = uint16_t(hex2int(ui->af_regView->text())); + cpu.registers._HL = uint32_t(hex2int(ui->hl_regView->text())); + cpu.registers._DE = uint32_t(hex2int(ui->de_regView->text())); + cpu.registers._BC = uint32_t(hex2int(ui->bc_regView->text())); + + cpu.registers.SPL = uint32_t(hex2int(ui->splregView->text())); + cpu.registers.SPS = uint16_t(hex2int(ui->spsregView->text())); + + cpu.registers.MBASE = uint8_t(hex2int(ui->mbregView->text())); + cpu.registers.I = uint16_t(hex2int(ui->iregView->text())); uint8_t r = hex2int(ui->rregView->text()); cpu.registers.R = uint8_t(r << 1 | r >> 7); - cpu.IM = static_cast(hex2int(ui->imregView->text())); + cpu.IM = uint8_t(hex2int(ui->imregView->text())); cpu.IM += !!cpu.IM; cpu.registers.flags.Z = ui->checkZ->isChecked(); @@ -551,7 +584,7 @@ void MainWindow::debugSync() const { debug.totalCycles = ui->cycleView->text().toLongLong() + (m_ignoreDmaCycles ? debug.dmaCycles : 0); debug.flashCacheMisses = ui->flashMissesView->text().toUInt(); - uint32_t uiPC = static_cast(hex2int(ui->pcregView->text())); + uint32_t uiPC = uint32_t(hex2int(ui->pcregView->text())); if (cpu.registers.PC != uiPC) { cpu_flush(uiPC, ui->checkADL->isChecked()); } @@ -560,11 +593,11 @@ void MainWindow::debugSync() const { backlight.factor = (310u - backlight.brightness) / 160.0f; panel.gammaDirty = true; - lcd.upbase = static_cast(hex2int(ui->lcdbaseView->text())); - lcd.upcurr = static_cast(hex2int(ui->lcdcurrView->text())); + lcd.upbase = uint32_t(hex2int(ui->lcdbaseView->text())); + lcd.upcurr = uint32_t(hex2int(ui->lcdcurrView->text())); lcd.control &= ~14u; - lcd.control |= static_cast(ui->bppView->currentIndex() << 1); + lcd.control |= unsigned(ui->bppView->currentIndex() << 1); set_reset(ui->checkPowered->isChecked(), 0x800u, lcd.control); set_reset(ui->checkBEPO->isChecked(), 0x400u, lcd.control); @@ -582,7 +615,7 @@ void MainWindow::debugSync() const { panel_update_clock_rate(); } - set_cpu_clock(static_cast(ui->freqView->text().toDouble() * 1e6)); + set_cpu_clock(uint32_t(ui->freqView->text().toDouble() * 1e6)); lcd_update(); @@ -854,7 +887,8 @@ void MainWindow::debugPopulate() { osUpdate(); stackUpdate(); - disasmUpdateAddr(m_prevDisasmAddr = cpu.registers.PC, true); + disasmUpdateAddr(cpu.registers.PC, true); + ui->sources->updatePC(); // Track step navigation: append on control-flow (branch taken), // replace on linear advance. Non-step stops do not modify history if (m_stepCtx.active) { @@ -911,9 +945,9 @@ void MainWindow::breakSetPrev(QTableWidgetItem *current, [[maybe_unused]] QTable } void MainWindow::breakRemoveRow(int row) { - uint32_t address = static_cast(hex2int(m_breakpoints->item(row, BREAK_ADDR_COL)->text())); + uint32_t address = uint32_t(hex2int(m_breakpoints->item(row, BREAK_ADDR_COL)->text())); - debug_watch(address, DBG_MASK_EXEC, false); + changeDebugPoint(address, DBG_MASK_EXEC, false); if (!m_guiAdd && !m_useSoftCom) { disasmUpdate(); memUpdate(); @@ -932,7 +966,7 @@ void MainWindow::breakRemoveSelected() { void MainWindow::breakRemove(uint32_t address) { for (int row = 0; row < m_breakpoints->rowCount(); row++) { - uint32_t test = static_cast(hex2int(m_breakpoints->item(row, BREAK_ADDR_COL)->text())); + uint32_t test = uint32_t(hex2int(m_breakpoints->item(row, BREAK_ADDR_COL)->text())); if (address == test) { breakRemoveRow(row); break; @@ -982,7 +1016,7 @@ void MainWindow::breakModified(QTableWidgetItem *item) { return; } - addr = static_cast(hex2int(QString::fromStdString(s))); + addr = uint32_t(hex2int(QString::fromStdString(s))); addrStr = int2hex(addr, 6); m_breakpoints->blockSignals(true); @@ -999,10 +1033,10 @@ void MainWindow::breakModified(QTableWidgetItem *item) { mask = breakGetMask(row); if (m_prevBreakAddr != DEBUG_UNSET_ADDR) { - debug_watch(hex2int(m_prevBreakAddr), DBG_MASK_EXEC, false); + changeDebugPoint(hex2int(m_prevBreakAddr), DBG_MASK_EXEC, false); } item->setText(addrStr); - debug_watch(addr, mask, true); + changeDebugPoint(addr, mask, true); m_breakpoints->blockSignals(false); } disasmUpdate(); @@ -1021,17 +1055,26 @@ void MainWindow::breakAddSlot() { breakAdd(breakNextLabel(), 0, true, false, true); } +void MainWindow::breakToggle(quint32 address, bool gui) { + m_guiAdd = gui; + + breakAdd(breakNextLabel(), address, true, true, false); + + m_guiAdd = false; +} + +void MainWindow::changeDebugPoint(quint32 address, unsigned type, bool state) { + debug_watch(address, type, state); + emit debugPointChanged(address, type, state); +} + void MainWindow::breakAddGui() { - uint32_t address = static_cast(hex2int(m_disasm->getSelectedAddr())); + uint32_t address = uint32_t(hex2int(m_disasm->getSelectedAddr())); QTextCursor c = m_disasm->textCursor(); c.setCharFormat(m_disasm->currentCharFormat()); - m_guiAdd = true; - - breakAdd(breakNextLabel(), address, true, true, false); - - m_guiAdd = false; + breakToggle(address); int32_t base = disasm.base; int32_t next = disasm.next; @@ -1104,9 +1147,9 @@ bool MainWindow::breakAdd(const QString &label, uint32_t addr, bool enabled, boo connect(btnRemove, &QToolButton::clicked, this, &MainWindow::breakRemoveSelected); connect(btnEnable, &QToolButton::clicked, [this, btnEnable, itemAddr](bool checked) { - uint32_t addr = static_cast(hex2int(itemAddr->text())); + uint32_t addr = uint32_t(hex2int(itemAddr->text())); btnEnable->setIcon(checked ? m_iconCheck : m_iconCheckGray); - debug_watch(addr, DBG_MASK_EXEC, checked); + changeDebugPoint(addr, DBG_MASK_EXEC, checked); disasmUpdate(); memUpdate(); }); @@ -1121,7 +1164,7 @@ bool MainWindow::breakAdd(const QString &label, uint32_t addr, bool enabled, boo m_breakpoints->setCurrentCell(row, BREAK_REMOVE_COL); if (addrStr != DEBUG_UNSET_ADDR) { - debug_watch(addr, DBG_MASK_EXEC, enabled); + changeDebugPoint(addr, DBG_MASK_EXEC, enabled); } if (!m_guiAdd && !m_useSoftCom) { @@ -1157,7 +1200,7 @@ void MainWindow::portSetPrev(QTableWidgetItem *current, [[maybe_unused]] QTableW } void MainWindow::portRemoveRow(int row) const { - uint16_t port = static_cast(hex2int(m_ports->item(row, PORT_ADDR_COL)->text())); + uint16_t port = uint16_t(hex2int(m_ports->item(row, PORT_ADDR_COL)->text())); debug_ports(port, ~DBG_MASK_NONE, false); m_ports->removeRow(row); } @@ -1172,8 +1215,8 @@ void MainWindow::portRemoveSelected() { } void MainWindow::portPopulate(int currRow) const { - uint16_t port = static_cast(hex2int(m_ports->item(currRow, PORT_ADDR_COL)->text())); - uint8_t read = static_cast(port_peek_byte(port)); + uint16_t port = uint16_t(hex2int(m_ports->item(currRow, PORT_ADDR_COL)->text())); + uint8_t read = uint8_t(port_peek_byte(port)); m_ports->item(currRow, PORT_VALUE_COL)->setText(int2hex(read, 2)); } @@ -1281,7 +1324,7 @@ void MainWindow::portModified(QTableWidgetItem *item) { int col = item->column(); if (col == PORT_READ_COL || col == PORT_WRITE_COL || col == PORT_FREEZE_COL) { - uint16_t port = static_cast(hex2int(m_ports->item(row, PORT_ADDR_COL)->text())); + uint16_t port = uint16_t(hex2int(m_ports->item(row, PORT_ADDR_COL)->text())); unsigned int mask = DBG_MASK_NONE; if (col == PORT_READ_COL) { // Break on read @@ -1303,7 +1346,7 @@ void MainWindow::portModified(QTableWidgetItem *item) { return; } - uint16_t port = static_cast(hex2int(QString::fromStdString(s))); + uint16_t port = uint16_t(hex2int(QString::fromStdString(s))); uint8_t data = port_peek_byte(port); QString portStr = int2hex(port, 4); @@ -1328,8 +1371,8 @@ void MainWindow::portModified(QTableWidgetItem *item) { m_ports->item(row, PORT_VALUE_COL)->setText(int2hex(data, 2)); } else if (col == PORT_VALUE_COL) { if (m_ports->item(row, PORT_ADDR_COL)->text() != DEBUG_UNSET_PORT) { - uint8_t pdata = static_cast(hex2int(item->text())); - uint16_t port = static_cast(hex2int(m_ports->item(row, PORT_ADDR_COL)->text())); + uint8_t pdata = uint8_t(hex2int(item->text())); + uint16_t port = uint16_t(hex2int(m_ports->item(row, PORT_ADDR_COL)->text())); port_poke_byte(port, pdata); item->setText(int2hex(port_peek_byte(port), 2)); @@ -1358,11 +1401,11 @@ void MainWindow::watchSetPrev(QTableWidgetItem *current, [[maybe_unused]] QTable void MainWindow::watchRemoveRow(int row) { if (m_watchpoints->item(row, WATCH_LOW_COL)->text() != DEBUG_UNSET_ADDR && m_watchpoints->item(row, WATCH_HIGH_COL)->text() != DEBUG_UNSET_ADDR) { - uint32_t low = static_cast(hex2int(m_watchpoints->item(row, WATCH_LOW_COL)->text())); - uint32_t high = static_cast(hex2int(m_watchpoints->item(row, WATCH_HIGH_COL)->text())); + uint32_t low = uint32_t(hex2int(m_watchpoints->item(row, WATCH_LOW_COL)->text())); + uint32_t high = uint32_t(hex2int(m_watchpoints->item(row, WATCH_HIGH_COL)->text())); for (uint32_t addr = low; addr <= high; addr++) { - debug_watch(addr, DBG_MASK_READ | DBG_MASK_WRITE, false); + changeDebugPoint(addr, DBG_MASK_READ | DBG_MASK_WRITE, false); } if (!m_guiAdd && !m_useSoftCom) { @@ -1385,7 +1428,7 @@ void MainWindow::watchRemoveSelected() { void MainWindow::watchRemove(uint32_t address) { for (int row = 0; row < m_watchpoints->rowCount(); row++) { - uint32_t test = static_cast(hex2int(m_watchpoints->item(row, WATCH_LOW_COL)->text())); + uint32_t test = uint32_t(hex2int(m_watchpoints->item(row, WATCH_LOW_COL)->text())); if (address == test) { watchRemoveRow(row); break; @@ -1410,7 +1453,7 @@ void MainWindow::watchAddGuiRW() { void MainWindow::watchAddGui() { int mask = m_watchGUIMask; - uint32_t addr = static_cast(hex2int(m_disasm->getSelectedAddr())); + uint32_t addr = uint32_t(hex2int(m_disasm->getSelectedAddr())); QTextCursor c = m_disasm->textCursor(); c.setCharFormat(m_disasm->currentCharFormat()); @@ -1424,7 +1467,7 @@ void MainWindow::watchAddGui() { int32_t base = disasm.base; int32_t next = disasm.next; - disasm.base = static_cast(addr); + disasm.base = int32_t(addr); disasm.highlight.watchR = false; disasm.highlight.watchW = false; disasmGet(); @@ -1470,12 +1513,12 @@ void MainWindow::watchUpdate() { for (int row = 0; row < m_watchpoints->rowCount(); row++) { if (m_watchpoints->item(row, WATCH_LOW_COL)->text() != DEBUG_UNSET_ADDR && m_watchpoints->item(row, WATCH_HIGH_COL)->text() != DEBUG_UNSET_ADDR) { - uint32_t low = static_cast(hex2int(m_watchpoints->item(row, WATCH_LOW_COL)->text())); - uint32_t high = static_cast(hex2int(m_watchpoints->item(row, WATCH_HIGH_COL)->text())); + uint32_t low = uint32_t(hex2int(m_watchpoints->item(row, WATCH_LOW_COL)->text())); + uint32_t high = uint32_t(hex2int(m_watchpoints->item(row, WATCH_HIGH_COL)->text())); int mask = watchGetMask(row); for (uint32_t addr = low; addr <= high; addr++) { - debug_watch(addr, mask, true); + changeDebugPoint(addr, mask, true); } } } @@ -1490,11 +1533,11 @@ void MainWindow::watchUpdateRow(QTableWidgetItem *itemLow, QTableWidgetItem *ite // this is needed in the case of overlapping address spaces if (itemLow->text() != DEBUG_UNSET_ADDR && itemHigh->text() != DEBUG_UNSET_ADDR) { - uint32_t low = static_cast(hex2int(itemLow->text())); - uint32_t high = static_cast(hex2int(itemHigh->text())); + uint32_t low = uint32_t(hex2int(itemLow->text())); + uint32_t high = uint32_t(hex2int(itemHigh->text())); for (uint32_t addr = low; addr <= high; addr++) { - debug_watch(addr, DBG_MASK_READ | DBG_MASK_WRITE, false); + changeDebugPoint(addr, DBG_MASK_READ | DBG_MASK_WRITE, false); } } @@ -1649,11 +1692,11 @@ void MainWindow::watchModified(QTableWidgetItem *item) { m_watchpoints->blockSignals(false); } - addr = static_cast(hex2int(QString::fromStdString(s))); + addr = uint32_t(hex2int(QString::fromStdString(s))); highStr = m_watchpoints->item(row, WATCH_HIGH_COL)->text(); if (isNotValidHex(s) || s.length() > 6 || - (highStr != DEBUG_UNSET_ADDR && addr > static_cast(hex2int(highStr)))) { + (highStr != DEBUG_UNSET_ADDR && addr > uint32_t(hex2int(highStr)))) { item->setText(m_prevWatchLow); m_watchpoints->blockSignals(false); return; @@ -1673,11 +1716,11 @@ void MainWindow::watchModified(QTableWidgetItem *item) { } if (m_prevWatchLow != DEBUG_UNSET_ADDR) { - uint32_t low = static_cast(hex2int(m_prevWatchLow)); - uint32_t high = static_cast(hex2int(m_watchpoints->item(row, WATCH_HIGH_COL)->text())); + uint32_t low = uint32_t(hex2int(m_prevWatchLow)); + uint32_t high = uint32_t(hex2int(m_watchpoints->item(row, WATCH_HIGH_COL)->text())); for (uint32_t watch_addr = low; watch_addr <= high; watch_addr++) { - debug_watch(watch_addr, DBG_MASK_READ | DBG_MASK_WRITE, false); + changeDebugPoint(watch_addr, DBG_MASK_READ | DBG_MASK_WRITE, false); } } @@ -1700,11 +1743,11 @@ void MainWindow::watchModified(QTableWidgetItem *item) { m_watchpoints->blockSignals(false); } - addr = static_cast(hex2int(QString::fromStdString(s))); + addr = uint32_t(hex2int(QString::fromStdString(s))); lowStr = m_watchpoints->item(row, WATCH_LOW_COL)->text(); if (isNotValidHex(s) || s.length() > 6 || - (lowStr != DEBUG_UNSET_ADDR && addr < static_cast(hex2int(lowStr)))) { + (lowStr != DEBUG_UNSET_ADDR && addr < uint32_t(hex2int(lowStr)))) { item->setText(m_prevWatchLow); m_watchpoints->blockSignals(false); return; @@ -1724,11 +1767,11 @@ void MainWindow::watchModified(QTableWidgetItem *item) { } if (m_prevWatchHigh != DEBUG_UNSET_ADDR) { - uint32_t low = static_cast(hex2int(m_watchpoints->item(row, WATCH_LOW_COL)->text())); - uint32_t high = static_cast(hex2int(m_prevWatchHigh)); + uint32_t low = uint32_t(hex2int(m_watchpoints->item(row, WATCH_LOW_COL)->text())); + uint32_t high = uint32_t(hex2int(m_prevWatchHigh)); for (uint32_t watch_addr = low; watch_addr <= high; watch_addr++) { - debug_watch(watch_addr, DBG_MASK_READ | DBG_MASK_WRITE, false); + changeDebugPoint(watch_addr, DBG_MASK_READ | DBG_MASK_WRITE, false); } } @@ -1752,7 +1795,7 @@ void MainWindow::batterySetCharging(bool checked) { } void MainWindow::batterySet(int value) const { - control.setBatteryStatus = static_cast(value); + control.setBatteryStatus = uint8_t(value); ui->sliderBattery->setValue(value); ui->labelBattery->setText(QString::number(value * 20) + "%"); } @@ -1797,9 +1840,9 @@ void MainWindow::updateLabels() { (m_watchpoints->item(row, WATCH_WRITE_COL)->checkState() == Qt::Checked ? DBG_MASK_WRITE : DBG_MASK_NONE); // remove old watchpoint and add new one m_watchpoints->blockSignals(true); - debug_watch(static_cast(hex2int(old)), mask, false); + changeDebugPoint(uint32_t(hex2int(old)), mask, false); m_watchpoints->item(row, WATCH_LOW_COL)->setText(next); - debug_watch(static_cast(hex2int(next)), mask, true); + changeDebugPoint(uint32_t(hex2int(next)), mask, true); m_watchpoints->blockSignals(true); } } @@ -1809,9 +1852,9 @@ void MainWindow::updateLabels() { if (!next.isEmpty() && next != old) { int mask = breakGetMask(row); m_breakpoints->blockSignals(true); - debug_watch(static_cast(hex2int(old)), mask, false); + changeDebugPoint(uint32_t(hex2int(old)), mask, false); m_breakpoints->item(row, BREAK_ADDR_COL)->setText(next); - debug_watch(static_cast(hex2int(next)), mask, true); + changeDebugPoint(uint32_t(hex2int(next)), mask, true); m_breakpoints->blockSignals(false); } } @@ -2268,16 +2311,16 @@ bool MainWindow::eventFilter(QObject *obj, QEvent *e) { QString val2 = QString::number(num2); if (num > 0x7FFFFF) { - val += QStringLiteral("\n\t") + QString::number(static_cast(num | 0xFF000000u)); + val += QStringLiteral("\n\t") + QString::number(int32_t(num | 0xFF000000u)); } if (num0 > 0x7F) { - val0 += QStringLiteral("\t") + QString::number(static_cast(num0)); + val0 += QStringLiteral("\t") + QString::number(int8_t(num0)); } if (num1 > 0x7F) { - val1 += QStringLiteral("\t") + QString::number(static_cast(num1)); + val1 += QStringLiteral("\t") + QString::number(int8_t(num1)); } if (num2 > 0x7F) { - val2 += QStringLiteral("\t") + QString::number(static_cast(num2)); + val2 += QStringLiteral("\t") + QString::number(int8_t(num2)); } if (name == QStringLiteral("afregView")) @@ -2403,7 +2446,7 @@ void MainWindow::osUpdate() { for (uint32_t j = i; j < i+11; j++) { uint8_t ch = mem_peek_byte(j); - array.append(static_cast(ch)); + array.append(char(ch)); if ((ch < 0x20) || (ch > 0x7e)) { ch = '.'; } @@ -2581,7 +2624,7 @@ void MainWindow::opModified(QTableWidgetItem *item) const { QString txt = item->text(); QString data; QByteArray array; - uint32_t addr = static_cast(hex2int(ui->opView->item(row, OP_ADDR_COL)->text())); + uint32_t addr = uint32_t(hex2int(ui->opView->item(row, OP_ADDR_COL)->text())); array.resize(11); sender()->blockSignals(true); @@ -2600,7 +2643,7 @@ void MainWindow::opModified(QTableWidgetItem *item) const { array.fill(0); try { data_t value = tivars::TypeHandlers::TH_GenericReal::makeDataFromString(txt.toStdString()); - for (int i = 0; i < 11 && i < static_cast(value.size()); i++) { + for (int i = 0; i < 11 && i < int(value.size()); i++) { array[i] = value[i]; } } catch(...) {} @@ -2615,7 +2658,7 @@ void MainWindow::opModified(QTableWidgetItem *item) const { for (uint32_t j = addr; j < addr + 11; j++) { uint8_t ch = mem_peek_byte(j); - array.append(static_cast(ch)); + array.append(char(ch)); if ((ch < 0x20) || (ch > 0x7e)) { ch = '.'; } @@ -2646,7 +2689,7 @@ void MainWindow::fpModified(QTableWidgetItem *item) const { QString txt = item->text(); QString data; QByteArray array; - uint32_t addr = static_cast(hex2int(ui->fpStack->item(row, FP_ADDR_COL)->text())); + uint32_t addr = uint32_t(hex2int(ui->fpStack->item(row, FP_ADDR_COL)->text())); array.resize(11); sender()->blockSignals(true); @@ -2665,7 +2708,7 @@ void MainWindow::fpModified(QTableWidgetItem *item) const { array.fill(0); try { data_t value = tivars::TypeHandlers::TH_GenericReal::makeDataFromString(txt.toStdString()); - for (int i = 0; i < 9 && i < static_cast(value.size()); i++) { + for (int i = 0; i < 9 && i < int(value.size()); i++) { array[i] = value[i]; } } catch(...) {} @@ -2680,7 +2723,7 @@ void MainWindow::fpModified(QTableWidgetItem *item) const { for (uint32_t j = addr; j < addr + 9; j++) { uint8_t ch = mem_peek_byte(j); - array.append(static_cast(ch)); + array.append(char(ch)); if ((ch < 0x20) || (ch > 0x7e)) { ch = '.'; } @@ -2719,7 +2762,7 @@ void MainWindow::contextOp(const QPoint &posa) { QAction *item = menu.exec(globalPos); if (item == gotoMem) { - gotoMemAddr(static_cast(hex2int(addr))); + gotoMemAddr(uint32_t(hex2int(addr))); } else if (item == copyAddr) { qApp->clipboard()->setText(addr); } else if (item == copyData) { @@ -2786,7 +2829,7 @@ void MainWindow::stepIn() { disconnect(m_shortcutStepIn, &QShortcut::activated, this, &MainWindow::stepIn); debugSync(); - debugStep(DBG_STEP_IN); + debugStep(ui->buttonToggleCStepping->isChecked() ? DBG_RUN_IN : DBG_STEP_IN); } void MainWindow::stepOver() { @@ -2797,7 +2840,7 @@ void MainWindow::stepOver() { disconnect(m_shortcutStepOver, &QShortcut::activated, this, &MainWindow::stepOver); debugSync(); - debugStep(DBG_STEP_OVER); + debugStep(ui->buttonToggleCStepping->isChecked() ? DBG_RUN_OVER : DBG_STEP_OVER); } void MainWindow::stepNext() { @@ -2822,6 +2865,12 @@ void MainWindow::stepOut() { debugStep(DBG_STEP_OUT); } +void MainWindow::runUntil(uint32_t addr) { + m_runUntilAddr = addr; + debugToggle(); + debugStep(DBG_RUN_UNTIL); +} + //------------------------------------------------ // Other Functions //------------------------------------------------ diff --git a/gui/qt/debugger/cdebughighlighter.cpp b/gui/qt/debugger/cdebughighlighter.cpp new file mode 100644 index 000000000..39346cf6c --- /dev/null +++ b/gui/qt/debugger/cdebughighlighter.cpp @@ -0,0 +1,293 @@ +#include "cdebughighlighter.h" +#include "sourceswidget.h" + +#include + +#include + +const QSet CDebugHighlighter::s_keywords{ + QStringLiteral("_Align"), + QStringLiteral("_At"), + QStringLiteral("auto"), + QStringLiteral("break"), + QStringLiteral("case"), + QStringLiteral("char"), + QStringLiteral("const"), + QStringLiteral("continue"), + QStringLiteral("default"), + QStringLiteral("do"), + QStringLiteral("double"), + QStringLiteral("else"), + QStringLiteral("enum"), + QStringLiteral("extern"), + QStringLiteral("float"), + QStringLiteral("for"), + QStringLiteral("goto"), + QStringLiteral("if"), + QStringLiteral("int"), + QStringLiteral("interrupt"), + QStringLiteral("long"), + QStringLiteral("nested_interrupt"), + QStringLiteral("register"), + QStringLiteral("return"), + QStringLiteral("short"), + QStringLiteral("signed"), + QStringLiteral("sizeof"), + QStringLiteral("static"), + QStringLiteral("struct"), + QStringLiteral("switch"), + QStringLiteral("typedef"), + QStringLiteral("union"), + QStringLiteral("unsigned"), + QStringLiteral("void"), + QStringLiteral("volatile"), + QStringLiteral("while") +}; + +CDebugHighlighter::CDebugHighlighter(SourcesWidget *sources, QPlainTextEdit *source) + : QSyntaxHighlighter(source), m_sources(sources) { + setDocument(source->document()); +} + +void CDebugHighlighter::highlightBlock(const QString &text) { + enum class ParseState { + Default = -1, + StringLiteral, + PreprocessorString, + PreprocessorDirective, + PreprocessorInstruction, + PreprocessorInclude, + PreprocessorIf, + PreprocessorOther, + CharacterLiteral, + MultilineComment, + Comment, + NumberLiteral, + PreprocessorFilename, + Identifier, + Escape, + EscapeOctal, + EscapeHexadecimal + } state = static_cast(previousBlockState()), baseState = state; + assert(state == ParseState::Default || state == ParseState::MultilineComment); + int start = 0, literalStart = 0, literalInfo = 0; + for (int i = 0; i <= text.length(); i++) { + QChar c = text.data()[i]; + switch (state) { + case ParseState::Default: + if (c == '\"') { + state = ParseState::StringLiteral; + literalStart = i; + } else if (c == '#' && !i) { + state = ParseState::PreprocessorDirective; + } else if (c == '\'') { + state = ParseState::CharacterLiteral; + literalStart = i; + literalInfo = 0; + } else if (c == '.' && i < text.length() - 1) { + c = text[i + 1]; + if (c >= '0' && c <= '9') { + state = ParseState::NumberLiteral; + literalInfo = true; + } + } else if (c == '/' && i < text.length() - 1) { + c = text[i + 1]; + if (c == '*') { + state = ParseState::MultilineComment; + } else if (c == '/') { + state = ParseState::Comment; + } + } else if (c >= '0' && c <= '9') { + state = ParseState::NumberLiteral; + literalInfo = false; + } else if ((c >='A' && c <= 'Z') || c == '_' || (c >= 'a' && c <= 'z')) { + state = ParseState::Identifier; + } + if (i == text.length() || state != ParseState::Default) { + setFormat(start, i - start, m_sources->m_defaultFormat); + start = i; + } + break; + case ParseState::StringLiteral: + case ParseState::PreprocessorString: + case ParseState::CharacterLiteral: + case ParseState::PreprocessorFilename: + if (i == text.length()) { + setFormat(literalStart, i - literalStart, m_sources->m_errorFormat); + } else if ((c == '\"' && (state == ParseState::StringLiteral || + state == ParseState::PreprocessorString)) || + (c == '\'' && state == ParseState::CharacterLiteral) || + (c == '>' && state == ParseState::PreprocessorFilename)) { + setFormat(start, i + 1 - start, m_sources->m_literalFormat); + if (state == ParseState::CharacterLiteral && literalInfo != 1) { + setFormat(literalStart, i + 1 - literalStart, + m_sources->m_errorFormat); + } + start = i + 1; + if (state == ParseState::StringLiteral || state == ParseState::CharacterLiteral) { + state = ParseState::Default; + } else { + state = ParseState::PreprocessorOther; + } + } else { + literalInfo++; + if (c == '\\' && + state != ParseState::PreprocessorString && + state != ParseState::PreprocessorFilename) { + setFormat(start, i - start, m_sources->m_literalFormat); + start = i; + baseState = state; + state = ParseState::Escape; + } + } + break; + case ParseState::PreprocessorDirective: + if (i == text.length() || (c != ' ' && c != '\t')) { + state = ParseState::PreprocessorInstruction; + start = i; + } else { + break; + } + [[gnu::fallthrough]]; + case ParseState::PreprocessorInstruction: + if (c == ' ' || c == '\t') { + QStringView instruction = text.mid(start, i - start); + if (instruction == QStringLiteral("include")) { + state = ParseState::PreprocessorInclude; + } else if (instruction == QStringLiteral("if")) { + state = ParseState::PreprocessorIf; + } else { + state = ParseState::PreprocessorOther; + } + start = 0; + } + if (i != text.length()) { + break; + } + start = 0; + [[gnu::fallthrough]]; + case ParseState::PreprocessorInclude: + case ParseState::PreprocessorIf: + case ParseState::PreprocessorOther: + if (i == text.length() || c == '\"' || + (c == '<' && state == ParseState::PreprocessorInclude)) { + setFormat(start, i - start, m_sources->m_preprocessorFormat); + state = c == '\"' ? ParseState::PreprocessorString + : ParseState::PreprocessorFilename; + start = literalStart = i; + } else if (c != ' ' && c != '\t') { + state = ParseState::PreprocessorOther; + } + break; + case ParseState::MultilineComment: + if (i == text.length()) { + setFormat(start, i - start, m_sources->m_commentFormat); + } else if (i > start + (baseState != ParseState::MultilineComment) && + c == '/' && text[i - 1] == '*') { + setFormat(start, i + 1 - start, m_sources->m_commentFormat); + start = i + 1; + state = baseState = ParseState::Default; + } + break; + case ParseState::Comment: + i = text.length(); + setFormat(start, i - start, m_sources->m_commentFormat); + break; + case ParseState::NumberLiteral: + case ParseState::Identifier: + if (i == text.length() || + ((c < '0' || c > '9') && (c < 'A' || c > 'Z') && + c != '_' && (c < 'a' || c > 'z'))) { + QStringView token = text.mid(start, i - start); + if (state == ParseState::NumberLiteral) { + if (c == '.' || ((c == '+' || c == '-') && i > start && + (text[i - 1] == 'E' || text[i - 1] == 'e'))) { + literalInfo = true; + break; + } + bool ok; + if (literalInfo) { + if (token.endsWith('f', Qt::CaseInsensitive) || + token.endsWith('l', Qt::CaseInsensitive)) { + token.chop(1); + } + token.toFloat(&ok); + } else { + bool unsignedSuffix = false, longSuffix = false; + forever { + if (!unsignedSuffix && + token.endsWith('u', Qt::CaseInsensitive)) { + token.chop(1); + unsignedSuffix = true; + } else if (!longSuffix && + token.endsWith('l', Qt::CaseInsensitive)) { + token.chop(1); + longSuffix = true; + } else { + break; + } + } + if (unsignedSuffix) { + uint value = token.toUInt(&ok, 0); + ok &= longSuffix || value < 1u << 24; + } else { + int value = token.toInt(&ok, 0); + ok &= longSuffix || (value >= -(1 << 23) && + value < 1 << 23); + } + } + setFormat(start, i - start, ok ? m_sources->m_literalFormat + : m_sources->m_errorFormat); + } else { + setFormat(start, i - start, + s_keywords.contains(token) ? + m_sources->m_keywordFormat : + m_sources->m_identifierFormat); + } + start = i--; + state = ParseState::Default; + } + break; + case ParseState::Escape: + if (i == text.length()) { + setFormat(literalStart, i - literalStart, m_sources->m_errorFormat); + } else if (c >= QChar('0') && c <= QChar('7')) { + state = ParseState::EscapeOctal; + } else if (c == QChar('x') || c == QChar('X')) { + state = ParseState::EscapeHexadecimal; + } else { + bool error = c != QChar('\'') && c != QChar('\"') && c != QChar('\?') && c != QChar('\\') && + c != QChar('a') && c != QChar('b') && c != QChar('e') && c != QChar('f') && + c != QChar('n') && c != QChar('r') && c != QChar('t') && c != QChar('v'); + setFormat(start, i + 1 - start, error ? m_sources->m_errorFormat : m_sources->m_escapeFormat); + start = i + 1; + state = baseState; + baseState = ParseState::Default; + } + break; + case ParseState::EscapeOctal: + if (i == text.length()) { + setFormat(literalStart, i - literalStart, m_sources->m_errorFormat); + } else if (i == start + 4 || c < QChar('0') || c > QChar('9')) { + setFormat(start, i - start, m_sources->m_escapeFormat); + start = i--; + state = baseState; + baseState = ParseState::Default; + } + break; + case ParseState::EscapeHexadecimal: + if (i == text.length()) { + setFormat(literalStart, i - literalStart, m_sources->m_errorFormat); + } else if ((c < QChar('0') || c > QChar('9')) && + (c < QChar('a') || c > QChar('f')) && + (c < QChar('A') || c > QChar('F'))) { + setFormat(start, i - start, i == start + 2 ? m_sources->m_errorFormat : m_sources->m_escapeFormat); + start = i--; + state = baseState; + baseState = ParseState::Default; + } + break; + } + } + setCurrentBlockState(static_cast(state == ParseState::MultilineComment ? state : ParseState::Default)); +} diff --git a/gui/qt/debugger/cdebughighlighter.h b/gui/qt/debugger/cdebughighlighter.h new file mode 100644 index 000000000..cc449e8e9 --- /dev/null +++ b/gui/qt/debugger/cdebughighlighter.h @@ -0,0 +1,26 @@ +#ifndef CHIGHLIGHTER_H +#define CHIGHLIGHTER_H + +class SourcesWidget; + +#include +#include +#include +#include +class QPlainTextEdit; + +class CDebugHighlighter : public QSyntaxHighlighter { + Q_OBJECT + +public: + CDebugHighlighter(SourcesWidget *sources, QPlainTextEdit *source = Q_NULLPTR); + +protected: + void highlightBlock(const QString &text) override; + +private: + SourcesWidget *m_sources; + const static QSet s_keywords; +}; + +#endif diff --git a/gui/qt/debugger/debuginfo.cpp b/gui/qt/debugger/debuginfo.cpp new file mode 100644 index 000000000..8efb16c01 --- /dev/null +++ b/gui/qt/debugger/debuginfo.cpp @@ -0,0 +1,1927 @@ +#include "debuginfo.h" + +#define MY_DEBUG + +#ifdef MY_DEBUG +#include +#include +#endif + +namespace debuginfo { +namespace { +template constexpr Type consume(Data &data) noexcept { + auto res = data.complete(); + data = data.subview(sizeof(Type)); + return res; +} +_Str consume_strz(Data &data) noexcept { + _Str str = data.bitcast<_Str::element_type>(); + auto end = std::find(str.begin(), str.end(), '\0'); + if (end == str.end()) { + return ""; + } + return data.take_first(end - str.begin() + 1).bitcast<_Str::element_type>(); +} +} // end anonymous namespace + +namespace elf { +enum { + ELFCLASS32 = 1, + ELFDATA2LSB = 1, + ELFOSABI_STANDALONE = 255, + ET_EXEC = 2, + EM_Z80 = 220, + EF_Z80_MACH_Z80 = 1, + EF_Z80_MACH_Z180 = 2, + EF_Z80_MACH_R800 = 3, + EF_Z80_MACH_EZ80_Z80 = 4, + EF_Z80_MACH_EZ80_ADL = 4 | 1 << 7, + EF_Z80_MACH_GBZ80 = 5, + EF_Z80_MACH_Z80N = 6, + EF_Z80_MACH_MSK = (1 << 8) - 1, + EV_CURRENT = 1, + SHT_NULL = 0, + SHT_PROGBITS = 1, + SHT_SYMTAB = 2, + SHT_STRTAB = 3, + SHT_NOBITS = 8, + SHF_WRITE = 1 << 0, + SHF_ALLOC = 1 << 1, + SHF_EXECINSTR = 1 << 2, + SHF_MERGE = 1 << 4, + SHF_STRINGS = 1 << 5, +}; + +const File::Header File::Header::ez80 { + .ei_mag = { '\x7F', 'E','L','F' }, + .ei_class = ELFCLASS32, + .ei_data = ELFDATA2LSB, + .ei_version = EV_CURRENT, + .ei_osabi = ELFOSABI_STANDALONE, + .ei_pad = {}, + .e_type = ET_EXEC, + .e_machine = EM_Z80, + .e_version = EV_CURRENT, + .e_entry = {}, + .e_phoff = {}, + .e_shoff = {}, + .e_flags = EF_Z80_MACH_EZ80_ADL, + .e_ehsize = {}, + .e_phentsize = {}, + .e_phnum = {}, + .e_shentsize = {}, + .e_shnum = {}, + .e_shstrndx = {}, +}; + + +Data ProgramHeader::data(const File &file) const noexcept { + return file._M_data.subview(p_offset, p_filesz); +} + +_Str SectionHeader::name(const File &file) const noexcept { + auto name = file.section_headers()[file.header().e_shstrndx].data(file).subview(sh_name); + return consume_strz(name); +} +Data SectionHeader::data(const File &file) const noexcept { + if (sh_type == SHT_NOBITS) + return {}; + return file._M_data.subview(sh_offset, sh_size); +} +} // end namespace elf + +namespace dwarf { +void File::validate(const Header &expected) { + if (!elf::File::validate(expected)) + return error("Invalid ELF header"); + for (const auto &sh : section_headers()) { + auto name = sh.name(*this); + if (name == ".debug_abbrev") + _M_abbrev.data(sh.data(*this)); + else if (name == ".debug_addr") + _M_addr.data(sh.data(*this)); + else if (name == ".debug_frame") + _M_frame.data(sh.data(*this)); + else if (name == ".debug_info") + _M_info.data(sh.data(*this)); + else if (name == ".debug_line") + _M_line.data(sh.data(*this)); + else if (name == ".debug_line_str") + _M_line_str.data(sh.data(*this)); + else if (name == ".debug_line_str_offsets") + _M_line_str_offsets.data(sh.data(*this)); + else if (name == ".debug_loclists") + _M_loclists.data(sh.data(*this)); + else if (name == ".debug_rnglists") + _M_rnglists.data(sh.data(*this)); + else if (name == ".debug_str") + _M_str.data(sh.data(*this)); + else if (name == ".debug_str_offsets") + _M_str_offsets.data(sh.data(*this)); + } + _M_frame.__parse(); + _M_info.__parse(_M_line, _M_loclists, _M_rnglists); +} + +namespace section { +namespace { +template constexpr +std::enable_if_t::value, std::underlying_type_t> underlying(Enum e) noexcept { + return std::underlying_type_t(e); +} +constexpr detail::SizedInteger consume_uleb128(Data &data) noexcept { + std::int8_t cur{}; + std::uintmax_t res{}; + std::size_t shift{}; + do { + cur = consume<_Sbyte>(data); + res |= (cur & 0x7F) << shift; + shift += std::numeric_limits::digits; + } while (cur < 0); + return { res, shift }; +} +constexpr detail::SizedInteger consume_sleb128(Data &data) noexcept { + std::int8_t cur{}; + std::intmax_t res{}; + std::size_t shift{}; + do { + cur = consume<_Sbyte>(data); + res |= (cur & 0x7F) << shift; + shift += std::numeric_limits::digits; + } while (cur < 0); + if (shift <= std::numeric_limits::digits) { + res = res << (std::numeric_limits::digits - shift + 1) + >> (std::numeric_limits::digits - shift + 1); + } + return { res, shift }; +} +_Val consume_form(const Abbrev::_Attr &attr, Data &data, _Form &fixup) noexcept { + switch (attr._M_at) { + default: + break; + case _At::DW_AT_high_pc: + fixup = _Form::_HighPC; + break; + case _At::DW_AT_decl_file: + fixup = _Form::_DeclFile; + break; + } + + switch (auto cur = attr._M_form) { + case _Form::DW_FORM_strx3: + case _Form::DW_FORM_addrx3: + fixup = cur; + [[gnu::fallthrough]]; + case _Form::DW_FORM_addr: + return consume<_Addr>(data); + case _Form::DW_FORM_block2: + return data.take_first(consume<_Half>(data)); + case _Form::DW_FORM_block4: + return data.take_first(consume<_Word>(data)); + case _Form::DW_FORM_ref2: + case _Form::DW_FORM_strx2: + case _Form::DW_FORM_addrx2: + fixup = cur; + [[gnu::fallthrough]]; + case _Form::DW_FORM_data2: + return consume<_Half>(data); + case _Form::DW_FORM_ref4: + case _Form::DW_FORM_strx4: + case _Form::DW_FORM_addrx4: + fixup = cur; + [[gnu::fallthrough]]; + case _Form::DW_FORM_data4: + return consume<_Word>(data); + case _Form::DW_FORM_ref8: + fixup = cur; + [[gnu::fallthrough]]; + case _Form::DW_FORM_data8: + return consume<_Long>(data); + case _Form::DW_FORM_string: + return consume_strz(data); + case _Form::DW_FORM_block: + case _Form::DW_FORM_exprloc: + return data.take_first(consume_uleb128(data)); + case _Form::DW_FORM_block1: + return data.take_first(consume<_Byte>(data)); + case _Form::DW_FORM_ref1: + case _Form::DW_FORM_strx1: + case _Form::DW_FORM_addrx1: + fixup = cur; + [[gnu::fallthrough]]; + case _Form::DW_FORM_data1: + return consume<_Byte>(data); + case _Form::DW_FORM_flag: + return bool(consume<_Byte>(data)); + case _Form::DW_FORM_sdata: + return consume_sleb128(data); + case _Form::DW_FORM_strp: + case _Form::DW_FORM_ref_addr: + case _Form::DW_FORM_line_strp: + fixup = cur; + [[gnu::fallthrough]]; + case _Form::DW_FORM_sec_offset: + return consume<_Off>(data); + case _Form::DW_FORM_ref_udata: + case _Form::DW_FORM_strx: + case _Form::DW_FORM_addrx: + case _Form::DW_FORM_loclistx: + case _Form::DW_FORM_rnglistx: + fixup = cur; + [[gnu::fallthrough]]; + case _Form::DW_FORM_udata: + return consume_uleb128(data); + case _Form::DW_FORM_indirect: + if (fixup == _Form::_None) { + auto form = consume_uleb128(data); + if (form <= underlying(_Form::_None) || form >= underlying(_Form::_HighPC)) + return {}; + auto val = consume_form({ _At::_None, _Form(form.get()), {} }, data, cur); + if (cur != _Form::DW_FORM_indirect) + fixup = cur; + return val; + } + [[gnu::fallthrough]]; + default: + return {}; + case _Form::DW_FORM_flag_present: + return true; + case _Form::DW_FORM_data16: + return data.take_first(16); + case _Form::DW_FORM_implicit_const: + if (attr._M_at == _At::_None) + return {}; + return attr._M_implicit_const; + } +} +_Str strx(_Word index, const _Die &unit_entry, const File &file) noexcept { + auto str_offsets = file.str_offsets().get(unit_entry); + if (index >= str_offsets.size()) { + file.error("Out of range index into str offsets"); + return {}; + } + return file.str().get(str_offsets[index]); +} +_Addr addrx(_Word index, const _Die &unit_entry, const File &file) noexcept { + auto addr = file.addr().get(unit_entry); + if (index >= addr.size()) { + file.error("Out of range index into addr"); + return {}; + } + return addr[index]; +} + +#ifdef MY_DEBUG +template constexpr std::size_t size(const Type (&)[Extent]) noexcept { + return Extent; +} + +template +QDebug operator<<[[gnu::unused]](QDebug debug, detail::UnalignedLittleEndianIntegerImpl< + Signed, Bits, Indices...> i) { + return debug << Qt::hex << Qt::showbase << i; +} + +QDebug operator<<[[gnu::unused]](QDebug debug, _Tag tag) { + static const char *names[] = { + "_None", + "DW_TAG_array_type", + "DW_TAG_class_type", + "DW_TAG_entry_point", + "DW_TAG_enumeration_type", + "DW_TAG_formal_parameter", + "_Reserved", + "_Reserved", + "DW_TAG_imported_declaration", + "_Reserved", + "DW_TAG_label", + "DW_TAG_lexical_block", + "_Reserved", + "DW_TAG_member", + "_Reserved", + "DW_TAG_pointer_type", + "DW_TAG_reference_type", + "DW_TAG_compile_unit", + "DW_TAG_string_type", + "DW_TAG_structure_type", + "_Reserved", + "DW_TAG_subroutine_type", + "DW_TAG_typedef", + "DW_TAG_union_type", + "DW_TAG_unspecified_parameters", + "DW_TAG_variant", + "DW_TAG_common_block", + "DW_TAG_common_inclusion", + "DW_TAG_inheritance", + "DW_TAG_inlined_subroutine", + "DW_TAG_module", + "DW_TAG_ptr_to_member_type", + "DW_TAG_set_type", + "DW_TAG_subrange_type", + "DW_TAG_with_stmt", + "DW_TAG_access_declaration", + "DW_TAG_base_type", + "DW_TAG_catch_block", + "DW_TAG_const_type", + "DW_TAG_constant", + "DW_TAG_enumerator", + "DW_TAG_file_type", + "DW_TAG_friend", + "DW_TAG_namelist", + "DW_TAG_namelist_item", + "DW_TAG_packed_type", + "DW_TAG_subprogram", + "DW_TAG_template_type_parameter", + "DW_TAG_template_value_parameter", + "DW_TAG_thrown_type", + "DW_TAG_try_block", + "DW_TAG_variant_part", + "DW_TAG_variable", + "DW_TAG_volatile_type", + "DW_TAG_dwarf_procedure", + "DW_TAG_restrict_type", + "DW_TAG_interface_type", + "DW_TAG_namespace", + "DW_TAG_imported_module", + "DW_TAG_unspecified_type", + "DW_TAG_partial_unit", + "DW_TAG_imported_unit", + "_Reserved", + "DW_TAG_condition", + "DW_TAG_shared_type", + "DW_TAG_type_unit", + "DW_TAG_rvalue_reference_type", + "DW_TAG_template_alias", + "DW_TAG_coarray_type", + "DW_TAG_generic_subrange", + "DW_TAG_dynamic_type", + "DW_TAG_atomic_type", + "DW_TAG_call_site", + "DW_TAG_call_site_parameter", + "DW_TAG_skeleton_unit", + "DW_TAG_immutable_type", + "_Unknown", + }; + static_assert(size(names) == std::size_t(_Tag::_Unused), "Missing names"); + QDebugStateSaver saver(debug); + return debug.nospace().noquote() << Qt::left << qSetFieldWidth(31) + << (std::size_t(tag) < size(names) + ? names[std::size_t(tag)] : "_Reserved"); +} +QDebug operator<<[[gnu::unused]](QDebug debug, _At at) { + const char *names[] = { + "_None", + "DW_AT_sibling", + "DW_AT_location", + "DW_AT_name", + "_Reserved", + "_Reserved", + "_Reserved", + "_Reserved", + "_Reserved", + "DW_AT_ordering", + "_Reserved", + "DW_AT_byte_size", + "_Reserved", + "DW_AT_bit_size", + "_Reserved", + "_Reserved", + "DW_AT_stmt_list", + "DW_AT_low_pc", + "DW_AT_high_pc", + "DW_AT_language", + "_Reserved", + "DW_AT_discr", + "DW_AT_discr_value", + "DW_AT_visibility", + "DW_AT_import", + "DW_AT_string_length", + "DW_AT_common_reference", + "DW_AT_comp_dir", + "DW_AT_const_value", + "DW_AT_containing_type", + "DW_AT_default_value", + "_Reserved", + "DW_AT_inline", + "DW_AT_is_optional", + "DW_AT_lower_bound", + "_Reserved", + "_Reserved", + "DW_AT_producer", + "_Reserved", + "DW_AT_prototyped", + "_Reserved", + "_Reserved", + "DW_AT_return_addr", + "_Reserved", + "DW_AT_start_scope", + "_Reserved", + "DW_AT_bit_stride", + "DW_AT_upper_bound", + "_Reserved", + "DW_AT_abstract_origin", + "DW_AT_accessibility", + "DW_AT_address_class", + "DW_AT_artificial", + "DW_AT_base_types", + "DW_AT_calling_convention", + "DW_AT_count", + "DW_AT_data_member_location", + "DW_AT_decl_column", + "DW_AT_decl_file", + "DW_AT_decl_line", + "DW_AT_declaration", + "DW_AT_discr_list", + "DW_AT_encoding", + "DW_AT_external", + "DW_AT_frame_base", + "DW_AT_friend", + "DW_AT_identifier_case", + "_Reserved", + "DW_AT_namelist_item", + "DW_AT_priority", + "DW_AT_segment", + "DW_AT_specification", + "DW_AT_static_link", + "DW_AT_type", + "DW_AT_use_location", + "DW_AT_variable_parameter", + "DW_AT_virtuality", + "DW_AT_vtable_elem_location", + "DW_AT_allocated", + "DW_AT_associated", + "DW_AT_data_location", + "DW_AT_byte_stride", + "DW_AT_entry_pc", + "DW_AT_use_UTF8", + "DW_AT_extension", + "DW_AT_ranges", + "DW_AT_trampoline", + "DW_AT_call_column", + "DW_AT_call_file", + "DW_AT_call_line", + "DW_AT_description", + "DW_AT_binary_scale", + "DW_AT_decimal_scale", + "DW_AT_small", + "DW_AT_decimal_sign", + "DW_AT_digit_count", + "DW_AT_picture_string", + "DW_AT_mutable", + "DW_AT_threads_scaled", + "DW_AT_explicit", + "DW_AT_object_pointer", + "DW_AT_endianity", + "DW_AT_elemental", + "DW_AT_pure", + "DW_AT_recursive", + "DW_AT_signature", + "DW_AT_main_subprogram", + "DW_AT_data_bit_offset", + "DW_AT_const_expr", + "DW_AT_enum_class", + "DW_AT_linkage_name", + "DW_AT_string_length_bit_size", + "DW_AT_string_length_byte_size", + "DW_AT_rank", + "DW_AT_str_offsets_base", + "DW_AT_addr_base", + "DW_AT_rnglists_base", + "_Reserved", + "DW_AT_dwo_name", + "DW_AT_reference", + "DW_AT_rvalue_reference", + "DW_AT_macros", + "DW_AT_call_all_calls", + "DW_AT_call_all_source_calls", + "DW_AT_call_all_tail_calls", + "DW_AT_call_return_pc", + "DW_AT_call_value", + "DW_AT_call_origin", + "DW_AT_call_parameter", + "DW_AT_call_pc", + "DW_AT_call_tail_call", + "DW_AT_call_target", + "DW_AT_call_target_clobbered", + "DW_AT_call_data_location", + "DW_AT_call_data_value", + "DW_AT_noreturn", + "DW_AT_alignment", + "DW_AT_export_symbols", + "DW_AT_deleted", + "DW_AT_defaulted", + "DW_AT_loclists_base", + "_Unknown", + }; + static_assert(size(names) == std::size_t(_At::_Unused), "Missing names"); + QDebugStateSaver saver(debug); + return debug.nospace().noquote() << Qt::left << qSetFieldWidth(30) + << (std::size_t(at) < size(names) + ? names[std::size_t(at)] : "_Reserved"); +} +QDebug operator<<[[gnu::used]](QDebug debug, _Form form) { + const char *names[] = { + "_None", + "DW_FORM_addr", + "_Reserved", + "DW_FORM_block2", + "DW_FORM_block4", + "DW_FORM_data2", + "DW_FORM_data4", + "DW_FORM_data8", + "DW_FORM_string", + "DW_FORM_block", + "DW_FORM_block1", + "DW_FORM_data1", + "DW_FORM_flag", + "DW_FORM_sdata", + "DW_FORM_strp", + "DW_FORM_udata", + "DW_FORM_ref_addr", + "DW_FORM_ref1", + "DW_FORM_ref2", + "DW_FORM_ref4", + "DW_FORM_ref8", + "DW_FORM_ref_udata", + "DW_FORM_indirect", + "DW_FORM_sec_offset", + "DW_FORM_exprloc", + "DW_FORM_flag_present", + "DW_FORM_strx", + "DW_FORM_addrx", + "DW_FORM_ref_sup4", + "DW_FORM_strp_sup", + "DW_FORM_data16", + "DW_FORM_line_strp", + "DW_FORM_ref_sig8", + "DW_FORM_implicit_const", + "DW_FORM_loclistx", + "DW_FORM_rnglistx", + "DW_FORM_ref_sup8", + "DW_FORM_strx1", + "DW_FORM_strx2", + "DW_FORM_strx3", + "DW_FORM_strx4", + "DW_FORM_addrx1", + "DW_FORM_addrx2", + "DW_FORM_addrx3", + "DW_FORM_addrx4", + "_HighPC", + "_File", + }; + static_assert(size(names) == std::size_t(_Form::_Unused), "Missing names"); + QDebugStateSaver saver(debug); + return debug.nospace().noquote() << Qt::left << qSetFieldWidth(29) + << (std::size_t(form) < size(names) + ? names[std::size_t(form)] : "_Reserved"); +} +QDebug operator<<[[gnu::unused]](QDebug debug, _Str str) { + return debug << QString::fromUtf8(str.data(), str.rtrim('\0').size()); +} +template QDebug operator<<[[gnu::unused]](QDebug debug, + const _Path &path) { + QString str; + for (auto c : path.canon()) + str += c; + return debug << str; +} +QDebug operator<<[[gnu::unused]](QDebug debug, const _Die &entry); +QDebug operator<<[[gnu::unused]](QDebug debug, const _Val &val) { + QDebugStateSaver saver(debug); + if (val.is_none()) + return debug.noquote() << ""; + if (val.is_addr()) + return debug << val.addr(); + if (val.is_cst()) + return debug << Qt::hex << Qt::showbase << val.cst(); + if (val.is_data()) + return debug.noquote() << QByteArray::fromRawData( + val.data().bitcast<_Str::element_type>().data(), val.data().size()).toHex(' '); + if (val.is_file()) + return debug.quote() << val.file().path(); + if (val.is_flag()) + return debug << val.flag(); + if (val.is_ref()) + return debug.quote() << val.ref()._M_tag << val.ref(); + if (val.is_str()) + return debug.quote() << val.str(); + debug.nospace(); + if (val.is_loclist()) { + bool first = true; + for (auto loc : val.loclist()) { + if (first) + first = false; + else + debug << ", "; + debug << '[' << loc._M_range.first << ',' << loc._M_range.second << ") " << loc._M_expr; + } + return debug; + } + if (val.is_rnglist()) { + bool first = true; + for (auto rng : val.rnglist()) { + if (first) + first = false; + else + debug << ", "; + debug << '[' << rng.first << ',' << rng.second << ')'; + } + return debug; + } + return debug << ""; +} +QDebug operator<<[[gnu::unused]](QDebug debug, const _Die &entry) { + auto name = std::cref(entry.attr(_At::DW_AT_name)); + if (name.get().is_none()) + name = entry.attr(_At::DW_AT_abstract_origin).ref().attr(_At::DW_AT_name); + return debug << name; +} +#endif +} // end anonymous namespace + +const SrcFile SrcFile::_S_null; + +const _Val _Val::_S_none; + +const _Val &_Die::attr(_At at) const noexcept { + auto attr = _M_attrs.find(at); + if (attr != _M_attrs.end()) + return attr->second; + attr = _M_attrs.find(_At::DW_AT_abstract_origin); + if (attr != _M_attrs.end()) + return attr->second.ref().attr(at); + return _Val::_S_none; +} + +const _Die _Die::_S_null{}; + +auto Abbrev::get(std::uint32_t base) const noexcept -> std::unordered_map { + std::unordered_map abbrevs; + auto unit = _M_data.subview(base); + while (!error()) { + auto code = consume_uleb128(unit); + if (!code) { + return abbrevs; + } + if (std::int64_t(code) != std::int32_t(code)) { + error("Unsupported code value size"); + return {}; + } + _Entry entry; + auto tag = consume_uleb128(unit); + switch (tag) { + case underlying(_Tag::_None): + error("Invalid tag"); + return {}; + default: +#ifdef MY_DEBUG + if (tag >= underlying(_Tag::_Unknown)) { + qWarning() << "Unknown TAG" << Qt::hex << Qt::showbase << tag.get(); + } +#endif + entry._M_tag = _Tag(std::min(tag, decltype(tag)(_Tag::_Unknown)).get()); + break; + // Vendor Extensions + case DW_TAG_GNU_call_site: + entry._M_tag = _Tag::DW_TAG_call_site; + break; + case DW_TAG_GNU_call_site_parameter: + entry._M_tag = _Tag::DW_TAG_call_site_parameter; + break; + } + entry._M_children = consume<_Byte>(unit); +#ifdef MY_DEBUG_DISABLED + qDebug().nospace() << '[' << code.get() << "] " << entry._M_tag + << (entry._M_children ? " DW_CHILDREN_yes" : " DW_CHILDREN_no"); +#endif + while (!error()) { + auto type = consume_uleb128(unit), form = consume_uleb128(unit); + if (!type && !form) { + break; + } + auto at = _At(type.get()); + switch (type) { + case underlying(_At::_None): + error("Invalid at"); + return {}; + case underlying(_At::DW_AT_low_pc): + switch (tag) { + default: + at = _At::DW_AT_low_pc; + break; + case DW_TAG_GNU_call_site: + at = _At::DW_AT_call_return_pc; + break; + } + break; + case underlying(_At::DW_AT_abstract_origin): + switch (tag) { + default: + at = _At::DW_AT_abstract_origin; + break; + case DW_TAG_GNU_call_site: + at = _At::DW_AT_call_origin; + break; + } + break; + default: +#ifdef MY_DEBUG + if (type >= underlying(_At::_Unknown)) { + qWarning() << "Unknown AT" << Qt::hex << Qt::showbase << at; + } +#endif + at = _At(std::min(type, decltype(type)(_At::_Unknown)).get()); + break; + // Vendor Extensions + case DW_AT_GNU_call_site_value: + at = _At::DW_AT_call_value; + break; + case DW_AT_GNU_call_site_target: + at = _At::DW_AT_call_target; + break; + case DW_AT_GNU_tail_call: + at = _At::DW_AT_call_tail_call; + break; + case DW_AT_GNU_all_call_sites: + at = _At::DW_AT_call_all_calls; + break; + } + if (form <= underlying(_Form::_None) || form >= underlying(_Form::_HighPC)) { + error("Out of range abbrev attr form"); + return {}; + } + std::int64_t implicit_const{}; + if (_Form(form.get()) == _Form::DW_FORM_implicit_const) { + implicit_const = consume_sleb128(unit); + } + entry._M_attrs.push_back({ std::min(_At(at), _At::_Unknown), + _Form(form.get()), implicit_const }); +#ifdef MY_DEBUG_DISABLED + qDebug() << '\t' << entry._M_attrs.back()._M_at << entry._M_attrs.back()._M_form; +#endif + } +#ifdef MY_DEBUG_DISABLED + qDebug() << ""; +#endif + entry._M_attrs.shrink_to_fit(); + abbrevs.emplace(code, std::move(entry)); + } + return {}; +} + +_AddrView Addr::get(const _Die &unit_entry) const noexcept { + auto base = unit_entry.attr(_At::DW_AT_addr_base); + if (base.is_none()) { + return {}; + } + auto unit = _M_data.subview(base.cst() - sizeof(_Word) + - sizeof(_Half) - sizeof(_Byte) - sizeof(_Byte)); + auto unit_length = consume<_Word>(unit); + unit = unit.first(unit_length); + auto version = consume<_Half>(unit); + if (version < 5 || version > 5) { + error("Unsupported addr version"); + return {}; + } + auto address_size = consume<_Byte>(unit); + if (address_size != sizeof(_Addr)) { + error("Unsupported address size"); + return {}; + } + auto segment_selector_size = consume<_Byte>(unit); + if (segment_selector_size) { + error("Unsupported segment selector size"); + return {}; + } + return unit.bitcast(); +} + +const Frame::RuleSet Frame::RuleSet::_S_undef; + +void Frame::__parse() { + // Table 7.29: Call frame instruction encodings + enum { + DW_CFA_advance_loc = 0x1, + DW_CFA_offset = 0x2, + DW_CFA_restore = 0x3, + DW_CFA_nop = 0, + DW_CFA_set_loc = 0x01, + DW_CFA_advance_loc1 = 0x02, + DW_CFA_advance_loc2 = 0x03, + DW_CFA_advance_loc4 = 0x04, + DW_CFA_offset_extended = 0x05, + DW_CFA_restore_extended = 0x06, + DW_CFA_undefined = 0x07, + DW_CFA_same_value = 0x08, + DW_CFA_register = 0x09, + DW_CFA_remember_state = 0x0a, + DW_CFA_restore_state = 0x0b, + DW_CFA_def_cfa = 0x0c, + DW_CFA_def_cfa_register = 0x0d, + DW_CFA_def_cfa_offset = 0x0e, + DW_CFA_def_cfa_expression = 0x0f, + DW_CFA_expression = 0x10, + DW_CFA_offset_extended_sf = 0x11, + DW_CFA_def_cfa_sf = 0x12, + DW_CFA_def_cfa_offset_sf = 0x13, + DW_CFA_val_offset = 0x14, + DW_CFA_val_offset_sf = 0x15, + DW_CFA_val_expression = 0x16, + // Vendor Extensions + DW_CFA_lo_user = 0x1c, + DW_CFA_hi_user = 0x3f, + }; + struct CIE { + std::uint32_t code_alignment_factor; + std::int32_t data_alignment_factor; + RuleSet initial_rules; + }; + std::unordered_map cies; + + const auto evaluate_instructions = [&](const CIE &cie, RuleSet &rules, Data instructions, + _Word location = _InvalidWord) { + const auto set_loc = [&](_Addr new_location) { + if (location == _InvalidWord) + return error("Invalid location in initial instructions"); + _M_rules[std::exchange(location, new_location)] = rules; + }; + const auto advance_loc = [&](std::uint32_t advance) { + set_loc(location + advance * cie.code_alignment_factor); + }; + const auto parse_reg = [&](std::uint64_t reg) -> Reg { + if (reg >= decltype(reg)(Reg::None)) { + error("Unknown register"); + return Reg::None; + } + return Reg(reg); + }; + std::vector stack; + while (!instructions.empty() && !error()) { + Reg reg; + switch (auto opcode = consume<_Byte>(instructions)) { + case DW_CFA_nop: + break; + case DW_CFA_set_loc: + set_loc(consume<_Addr>(instructions)); + break; + case DW_CFA_advance_loc1: + advance_loc(consume<_Byte>(instructions)); + break; + case DW_CFA_advance_loc2: + advance_loc(consume<_Half>(instructions)); + break; + case DW_CFA_advance_loc4: + advance_loc(consume<_Word>(instructions)); + break; + case DW_CFA_offset_extended: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = Rule::off(consume_uleb128(instructions) + * cie.data_alignment_factor); + break; + case DW_CFA_restore_extended: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = cie.initial_rules[reg]; + break; + case DW_CFA_undefined: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = Rule::undef(); + break; + case DW_CFA_same_value: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = Rule::same_val(); + break; + case DW_CFA_register: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = Rule::reg(parse_reg(consume_uleb128(instructions))); + break; + case DW_CFA_remember_state: + stack.push_back(rules); + break; + case DW_CFA_restore_state: + if (stack.empty()) + return error("Underflow state stack"); + rules = stack.back(); + stack.pop_back(); + break; + case DW_CFA_def_cfa: + reg = parse_reg(consume_uleb128(instructions)); + rules.cfa() = Rule::reg_off(reg, consume_uleb128(instructions)); + break; + case DW_CFA_def_cfa_register: + rules.cfa() = Rule::reg_off(parse_reg(consume_uleb128(instructions)), + rules.cfa().off()); + break; + case DW_CFA_def_cfa_offset: + rules.cfa() = Rule::reg_off(rules.cfa().reg(), consume_uleb128(instructions)); + break; + case DW_CFA_def_cfa_expression: + rules.cfa() = Rule::val_expr(*this, instructions.take_first( + consume_uleb128(instructions))); + break; + case DW_CFA_expression: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = Rule::expr(*this, instructions.take_first( + consume_uleb128(instructions))); + break; + case DW_CFA_offset_extended_sf: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = Rule::off(consume_sleb128(instructions) + * cie.data_alignment_factor); + break; + case DW_CFA_def_cfa_sf: + reg = parse_reg(consume_uleb128(instructions)); + rules.cfa() = Rule::reg_off(reg, consume_sleb128(instructions) + * cie.data_alignment_factor); + break; + case DW_CFA_def_cfa_offset_sf: + rules.cfa() = Rule::reg_off(rules.cfa().reg(), consume_sleb128(instructions) + * cie.data_alignment_factor); + break; + case DW_CFA_val_offset: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = Rule::val_off(consume_uleb128(instructions) + * cie.data_alignment_factor); + break; + case DW_CFA_val_offset_sf: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = Rule::val_off(consume_sleb128(instructions) + * cie.data_alignment_factor); + break; + case DW_CFA_val_expression: + reg = parse_reg(consume_uleb128(instructions)); + rules[reg] = Rule::val_expr(*this, instructions.take_first( + consume_uleb128(instructions))); + break; + default: { + decltype(opcode) info = opcode & ((1 << 6) - 1); + switch (opcode >> 6) { + case DW_CFA_advance_loc: + advance_loc(info); + break; + case DW_CFA_offset: + reg = parse_reg(info); + rules[reg] = Rule::off(consume_uleb128(instructions) + * cie.data_alignment_factor); + break; + case DW_CFA_restore: + reg = parse_reg(info); + rules[reg] = cie.initial_rules[reg]; + break; + default: +#ifdef MY_DEBUG + qWarning() << "Unknown opcode" << Qt::hex << Qt::showbase << opcode; +#endif + return error("Unknown opcode"); + } + break; + } + } + } + if (location != _InvalidWord) { + _M_rules[location] = rules; + } + }; + + Data data = _M_data; + while (!data.empty() && !error()) { + _Off entry_offset = std::distance(_M_data.begin(), data.begin()); + auto length = consume<_Word>(data); + auto entry = data.take_first(length); + auto cie_offset = consume<_Off>(entry); + if (cie_offset == _InvalidOff) { + auto &cie = cies[entry_offset]; + auto version = consume<_Byte>(entry); + if (version < 1 || version == 2 || version > 4) + return error("Unsupported frame version"); + auto augmentation = consume_strz(entry); + if (augmentation != "") + return error("Unsupported augmentation"); + auto address_size = consume<_Byte>(entry); + if (address_size != sizeof(_Addr)) + return error("Unsupported address size"); + auto segment_selector_size = consume<_Byte>(entry); + if (segment_selector_size) + return error("Unsupported segment selector size"); + auto code_alignment_factor = consume_uleb128(entry); + cie.code_alignment_factor = code_alignment_factor; + if (cie.code_alignment_factor != code_alignment_factor) + return error("Unsupported code alignment factor"); + auto data_alignment_factor = consume_sleb128(entry); + cie.data_alignment_factor = data_alignment_factor; + if (cie.data_alignment_factor != data_alignment_factor) + return error("Unsupported data alignment factor"); + auto return_address_register = consume_uleb128(entry); + if (return_address_register >= decltype(return_address_register)(Reg::None)) + return error("Out of range register"); + auto initial_instructions = entry; + evaluate_instructions(cie, cie.initial_rules, initial_instructions); + } else { + auto it = cies.find(cie_offset); + if (it == cies.end()) + return error("Invalid CIE_pointer"); + const auto &cie = it->second; + auto initial_location = consume<_Addr>(entry); + auto address_range = consume<_Addr>(entry); + auto instructions = entry; + RuleSet rules = cie.initial_rules; + evaluate_instructions(cie, rules, instructions, initial_location); + _M_rules.emplace(initial_location + address_range, RuleSet::undef()); + } + } +} + +auto Frame::get(_Addr addr) const noexcept -> const RuleSet & { + auto it = _M_rules.upper_bound(addr); + if (it == _M_rules.begin()) + return RuleSet::undef(); + return (--it)->second; +} + +auto Info::_Scope::get(_Addr addr) const noexcept -> const _Scope & { + auto it = _M_children.upper_bound(addr); + if (it == _M_children.begin() || addr >= (--it)->second.first) + return _Scope::_S_null; + return it->second.second; +} + +void Info::_Scope::dump(int indent) const { +#ifdef MY_DEBUG + qDebug().noquote() << QString(indent, ' ') << _M_entry; + for (const auto &child : _M_children) { + qDebug().noquote().nospace() << QString(indent, ' ') + << '[' << child.first << ',' << child.second.first << ')'; + child.second.second.dump(indent + 1); + } +#endif +} + +const Info::_Scope Info::_Scope::_S_null; + +void Info::__parse(Line &line, LocLists &loclists, RngLists &rnglists) { + struct Fixup { + std::uint32_t entry; + _At at; + _Form form; + }; + struct Unit { + std::uint32_t offset; + std::vector fixups; + std::vector file_map; + std::vector> loclists, rnglists; + }; + std::vector units; + std::unordered_map entry_offsets; + + { + std::vector<_Word> stack{ _InvalidWord }; + Data data = _M_data; + while (!data.empty()) { + _Off unit_offset = std::distance(_M_data.begin(), data.begin()); + auto unit_length = consume<_Word>(data); + auto unit = data.take_first(unit_length); + auto version = consume<_Half>(unit); + if (version < 2 || version > 5) + return error("Unsupported info version"); + auto unit_type = version < 5 ? _Byte(DW_UT_compile) : consume<_Byte>(unit); + if (unit_type != DW_UT_compile) + return error("Unsupported unit type"); + _Byte address_size; + if (version >= 5) { + address_size = consume<_Byte>(unit); + } + auto abbrevs = _M_file.abbrev().get(consume<_Off>(unit)); + if (version < 5) { + address_size = consume<_Byte>(unit); + } + if (address_size != sizeof(_Addr)) + return error("Unsupported address size"); + + units.emplace_back(); + units.back().offset = unit_offset; + auto first = true; + Abbrev::_Attr early_fixups[] = { + { _At::DW_AT_name, _Form::_None, {} }, + { _At::DW_AT_comp_dir, _Form::_None, {} }, + { _At::DW_AT_low_pc, _Form::_None, {} }, + }; + while (!error() && !unit.empty()) { + _Off entry_offset = std::distance(_M_data.begin(), unit.begin()); + auto code = consume_uleb128(unit); + if (code) { + auto abbrev = abbrevs.find(code); + if (abbrev == abbrevs.end()) + return error("Missing abbrev code"); + if (first != (abbrev->second._M_tag == _Tag::DW_TAG_compile_unit)) + return error("Expected only the first entry to be a compile unit"); + + std::uint32_t entry_index = _M_entries.size(); + entry_offsets.emplace(entry_offset, entry_index); + if (stack.back() != _InvalidWord) { + _M_entries[stack.back()]._M_sibling = entry_index; + } + stack.back() = entry_index; + if (abbrev->second._M_children) { + stack.push_back(_InvalidWord); + } + + _M_entries.emplace_back(); + auto &entry = _M_entries.back(); + entry._M_tag = abbrev->second._M_tag; + entry._M_children = abbrev->second._M_children; + for (const auto &attr : abbrev->second._M_attrs) { + auto fixup = _Form::_None; + auto val = consume_form(attr, unit, fixup); + if (attr._M_at == _At::_Unknown) { + continue; + } + auto inserted = entry._M_attrs.emplace(attr._M_at, val); + if (!inserted.second) + return error("Duplicate attr"); + else if (val.is_none()) { +#ifdef MY_DEBUG + qWarning() << "Unsupported form" << attr._M_form; +#endif + return error("Unsupported form"); + } + if (fixup != _Form::_None) { + if (first) { + for (auto &early_fixup : early_fixups) { + if (attr._M_at == early_fixup._M_at) { + early_fixup._M_form = std::exchange(fixup, _Form::_None); + break; + } + } + } + if (fixup != _Form::_None) { + units.back().fixups.push_back({ entry_index, attr._M_at, fixup }); + } + } + } + + if (first) { + for (auto &early_fixup : early_fixups) { + if (early_fixup._M_form == _Form::_None) { + continue; + } + auto &val = entry._M_attrs[early_fixup._M_at]; + switch (early_fixup._M_form) { + case _Form::DW_FORM_strp: + val = _M_file.str().get(val.cst()); + break; + case _Form::DW_FORM_strx: + case _Form::DW_FORM_strx1: + case _Form::DW_FORM_strx2: + case _Form::DW_FORM_strx3: + case _Form::DW_FORM_strx4: + val = strx(val.cst(), entry, _M_file); + break; + case _Form::DW_FORM_addrx: + case _Form::DW_FORM_addrx1: + case _Form::DW_FORM_addrx2: + case _Form::DW_FORM_addrx3: + case _Form::DW_FORM_addrx4: + val = addrx(val.cst(), entry, _M_file); + break; + default: +#ifdef MY_DEBUG + qWarning() << "Unsupported early form" << early_fixup._M_form; +#endif + return error("Unsupported early form"); + } + } + units.back().file_map = line.__parse(address_size, entry); + units.back().loclists = loclists.__parse(entry); + units.back().rnglists = rnglists.__parse(entry); + } + first = false; + } else if (stack.size() > 1u) { + stack.pop_back(); + } + } + if (stack.size() != 1u) + return error("Unexpected end of info unit"); + + if (error()) return; + } + _M_entries.shrink_to_fit(); + line.shrink_to_fit(); + loclists.shrink_to_fit(); + rnglists.shrink_to_fit(); + } + + { + _Word unit_index{}; + for (const auto &unit : units) { + const auto &unit_entry = _M_entries[unit_index]; + auto addr = _M_file.addr().get(unit_entry); + auto str_offsets = _M_file.str_offsets().get(unit_entry); + + for (const auto &fixup : unit.fixups) { + if (error()) return; + + auto &val = _M_entries[fixup.entry]._M_attrs.at(fixup.at); + switch (fixup.form) { + case _Form::DW_FORM_strp: + val = _M_file.str().get(val.cst()); + break; + case _Form::DW_FORM_ref_addr: + case _Form::DW_FORM_ref1: + case _Form::DW_FORM_ref2: + case _Form::DW_FORM_ref4: + case _Form::DW_FORM_ref8: + case _Form::DW_FORM_ref_udata: { + auto base = fixup.form == _Form::DW_FORM_ref_addr ? 0u : unit.offset; + auto offset = entry_offsets.find(base + val.cst()); + if (offset == entry_offsets.end()) + return error("Unknown entry offset"); + val = _M_entries[offset->second]; + break; + } + case _Form::DW_FORM_strx: + case _Form::DW_FORM_strx1: + case _Form::DW_FORM_strx2: + case _Form::DW_FORM_strx3: + case _Form::DW_FORM_strx4: + if (val.cst() >= str_offsets.size()) + return error("Out of range index into str offsets"); + val = _M_file.str().get(str_offsets[val.cst()]); + break; + case _Form::DW_FORM_addrx: + case _Form::DW_FORM_addrx1: + case _Form::DW_FORM_addrx2: + case _Form::DW_FORM_addrx3: + case _Form::DW_FORM_addrx4: + if (val.cst() >= addr.size()) + return error("Out of range index into addr"); + val = addr[val.cst()]; + break; + case _Form::DW_FORM_line_strp: + val = _M_file.line_str().get(val.cst()); + break; + case _Form::DW_FORM_loclistx: + if (val.cst() >= unit.loclists.size()) + return error("Out of range index into location lists"); + val = _M_file.loclists().get(unit.loclists[val.cst()]); + break; + case _Form::DW_FORM_rnglistx: + if (val.cst() >= unit.rnglists.size()) + return error("Out of range index into range lists is"); + val = _M_file.rnglists().get(unit.rnglists[val.cst()]); + break; + case _Form::_HighPC: + if (val.is_cst()) { + val = _M_entries[fixup.entry].attr(_At::DW_AT_low_pc).addr() + _Addr(val.cst()); + } + break; + case _Form::_DeclFile: + if (val.cst() >= unit.file_map.size()) + return error("Out of range index into files"); + val = _M_file.line().get(unit.file_map[val.cst()]); + break; + default: + return error("Unknown fixup"); + } + } + unit_index = _M_entries[unit_index]._M_sibling; + } + } + + { + std::vector, + std::reference_wrapper<_Scope>>> stack{ + { _Die::_S_null, _M_scopes.front() } + }; + bool children = true; + for (const _Die &entry : _M_entries) { + if (!children) { + bool sibling = false; + while (!sibling) { + assert(stack.size() > 1 && "Invalid tree structure"); + sibling = stack.back().first.get()._M_sibling; + stack.pop_back(); + } + } + children = entry._M_children; + switch (entry._M_tag) { + case _Tag::DW_TAG_compile_unit: + break; + case _Tag::DW_TAG_subprogram: + if (!(entry.attr(_At::DW_AT_declaration).flag() || + entry.attr(_At::DW_AT_inline).cst() & 1)) + break; + stack.emplace_back(entry, _M_scopes.front()); + continue; + case _Tag::DW_TAG_lexical_block: + case _Tag::DW_TAG_inlined_subroutine: + if (stack.back().second.get().entry()._M_tag != _Tag::_None) + break; + [[gnu::fallthrough]]; + default: + switch (entry._M_tag) { + case _Tag::DW_TAG_label: + case _Tag::DW_TAG_variable: + stack.back().second.get().add(entry); + break; + default: + break; + } + stack.emplace_back(entry, stack.back().second); + continue; + } + _M_scopes.emplace_back(_M_scopes.back(), entry); + const auto &low_pc = entry.attr(_At::DW_AT_low_pc); + const auto &high_pc = entry.attr(_At::DW_AT_high_pc); + const auto &ranges = entry.attr(_At::DW_AT_ranges); + if (low_pc.is_addr() && high_pc.is_addr()) + stack.back().second.get().set(low_pc.addr(), high_pc.addr(), _M_scopes.back()); + else if (ranges.is_rnglist()) + for (const auto &rng : ranges.rnglist()) + stack.back().second.get().set(rng.first, rng.second, _M_scopes.back()); + stack.emplace_back(entry, _M_scopes.back()); + } + } + +#ifdef MY_DEBUG_DISABLED + root().dump(); +#endif + +#ifdef MY_DEBUG_DISABLED + if (!error()) { + for (std::size_t i{}; i != _M_file.line().size(); ++i) { + const auto &file = _M_file.line().get(i); + auto debug = qDebug() << file.path() << ':'; + for (auto part : file.path()) + debug << part; + } + qDebug() << ""; + } +#endif + +#ifdef MY_DEBUG_DISABLED + if (!error()) { + for (const auto &entry : _M_entries) { + switch (entry._M_tag) { + default: break; // whatever + case _Tag::DW_TAG_compile_unit: + qDebug() << entry._M_tag << ':' << entry._M_attrs.at(_At::DW_AT_low_pc) + << '-' << entry._M_attrs.at(_At::DW_AT_high_pc); + break; + } + } + } +#endif + +#ifdef MY_DEBUG_DISABLED + if (!error()) { + std::vector stack; + QString indent; + for (const auto &entry : _M_entries) { + qDebug().noquote().nospace() << indent << entry._M_tag; + for (const auto &attr : entry._M_attrs) + qDebug().noquote().nospace() << indent << " " << attr.first + << '(' << attr.second << ')'; + if (entry._M_children) { + indent += " "; + stack.push_back(entry._M_sibling); + } else if (!entry._M_sibling && !stack.empty()) { + bool cur; + do { + indent.chop(2); + cur = stack.back(); + stack.pop_back(); + } while (!cur && !stack.empty()); + } + } + } +#endif +} + +std::vector Line::__parse(std::uint8_t address_size, const _Die &unit_entry) { + std::vector file_map; + auto base = unit_entry.attr(_At::DW_AT_stmt_list); + if (base.is_none()) { + return file_map; + } + auto unit = _M_data.subview(base.cst()); + auto unit_length = consume<_Word>(unit); + unit = unit.first(unit_length); + auto version = consume<_Half>(unit); + if (version < 2 || version > 5) { + error("Unsupported line version"); + return {}; + } + std::uint8_t segment_selector_size{}; + if (version >= 5) { + address_size = consume<_Byte>(unit); + segment_selector_size = consume<_Byte>(unit); + } + if (address_size != sizeof(_Addr)) { + error("Unsupported address size"); + return {}; + } + if (segment_selector_size) { + error("Unsupported segment selector size"); + return {}; + } + auto header_length = consume<_Word>(unit); + auto header = unit.take_first(header_length); + auto minimum_instruction_length = consume<_Byte>(header); + if (minimum_instruction_length != 1) { + error("Unsupported minimum instruction length"); + return {}; + } + auto maximum_operations_per_instruction = version < 4 ? _Byte(1) : consume<_Byte>(header); + if (maximum_operations_per_instruction != 1) { + error("Unsupported maximum operations per instruction"); + return {}; + } + auto default_is_stmt = consume<_Byte>(header); + auto line_base = consume<_Sbyte>(header); + auto line_range = consume<_Byte>(header); + auto opcode_base = consume<_Byte>(header); + if (!opcode_base) { + error("Invalid opcode base"); + return {}; + } + auto standard_opcode_lengths = header.take_first(opcode_base - 1).bitcast(); + auto parse_header_entries = [&](std::initializer_list<_EntryFormat> default_format, + _At default_path_attr) -> std::vector<_Entry> { + auto entry_format_count = version < 5 ? _Byte(default_format.size()) : consume<_Byte>(header); + std::array<_EntryFormat, 5> entry_format; + if (entry_format_count > entry_format.size()) { + error("Unsupported entry format count"); + return {}; + } + if (version < 5) { + std::copy_n(default_format.begin(), entry_format_count, entry_format.begin()); + } else { + for (decltype(entry_format_count) i{}; i != entry_format_count; ++i) { + entry_format[i].first = _Lnct(consume_uleb128(header).get()); + entry_format[i].second = _Form(consume_uleb128(header).get()); + } + } + auto entries_count = version < 5 ? 0u : consume_uleb128(header).get(); + std::vector<_Entry> entries; + entries.reserve(entries_count); + if (version < 5) { + entries.emplace_back(); + entries.back()._M_path = unit_entry.attr(default_path_attr).str(); + entries.back()._M_directory_index = 0; + } + while (version < 5 || entries.size() < entries_count) { + if (header.empty()) { + error("Unexpected end of header"); + return {}; + } + _Entry entry; + for (decltype(entry_format_count) i{}; i != entry_format_count; ++i) { + auto fixup = _Form::_Unused; + auto val = consume_form({ _At::_None, entry_format[i].second, 0 }, header, fixup); + if (fixup != _Form::_Unused) { + error("Unexpected fixup in line header"); + return {}; + } + switch (entry_format[i].first) { + case _Lnct::DW_LNCT_path: + if (!val.is_str()) { + error("Unexpected form for path"); + return {}; + } + if (version < 5 && val.str() == "") { + return entries; + } + entry._M_path = val.str(); + break; + case _Lnct::DW_LNCT_directory_index: + if (!val.is_cst()) { + error("Unexpected form for directory index"); + return {}; + } + entry._M_directory_index = val.cst(); + break; + case _Lnct::DW_LNCT_timestamp: + if (!val.is_cst() && !val.is_data()) { + error("Unexpected form for timestamp"); + return {}; + } + entry._M_timestamp = val.cst(); + break; + case _Lnct::DW_LNCT_size: + if (!val.is_cst()) { + error("Unexpected form for size"); + return {}; + } + entry._M_size = val.cst(); + break; + case _Lnct::DW_LNCT_MD5: + if (!val.is_data()) { + error("Unexpected form for MD5"); + return {}; + } + if (val.data().size_bytes() != 16) { + error("Unexpected size for MD5"); + return {}; + } + entry._M_MD5 = val.data(); + break; + default: + error("Unsupported line number content description"); + return {}; + } + } + entries.emplace_back(std::move(entry)); + } + return entries; + }; + auto directories = parse_header_entries({ + { _Lnct::DW_LNCT_path, _Form::DW_FORM_string }, + }, _At::DW_AT_comp_dir); + auto file_names = parse_header_entries({ + { _Lnct::DW_LNCT_path, _Form::DW_FORM_string }, + { _Lnct::DW_LNCT_directory_index, _Form::DW_FORM_udata }, + { _Lnct::DW_LNCT_timestamp, _Form::DW_FORM_udata }, + { _Lnct::DW_LNCT_size, _Form::DW_FORM_udata }, + }, _At::DW_AT_name); + file_map.reserve(file_names.size()); + for (const auto &file : file_names) { + _Str dir; + if (file._M_directory_index < directories.size()) { + dir = directories[file._M_directory_index]._M_path; + } + auto inserted = _M_paths.insert({ { unit_entry.attr(_At::DW_AT_comp_dir).str(), + dir, file._M_path }, static_cast(size()) }); + if (inserted.second) { + _M_files.emplace_back(inserted.first->first, file._M_MD5.data()); + } + file_map.push_back(inserted.first->second); + } + + _Loc state; + auto reset_state = [&]() { + state._M_file = 1; + state._M_line = 1; + state._M_column = 0; + state._M_address = 0; + state._M_is_stmt = default_is_stmt; + state._M_basic_block = false; + state._M_end_sequence = false; + state._M_prologue_end = false; + state._M_epilogue_begin = false; + state._M_isa = 0; + state._M_discriminator = 0; + }; + reset_state(); + auto emit_loc = [&]() { + if (state._M_file >= file_map.size()) + return error("Out of bounds file index"); + auto file = file_map[state._M_file]; +#ifdef MY_DEBUG_DISABLED + qDebug() << _M_files[file].path() << state._M_line << state._M_column + << Qt::showbase << Qt::hex << state._M_address; +#endif + auto inserted_loc = _M_locs.emplace(std::uint32_t(state._M_address), state); + if (!inserted_loc.second && + inserted_loc.first->second._M_end_sequence) { + inserted_loc.first->second = state; + inserted_loc.second = true; + } + if (inserted_loc.second) { + inserted_loc.first->second._M_file = file; + if (state._M_is_stmt) { + auto inserted_line = _M_files[file].lines().insert( + { { state._M_line, state._M_column }, + std::uint32_t(state._M_address) }); + if (!inserted_line.second && + inserted_line.first->second > state._M_address) { + inserted_line.first->second = state._M_address; + } + } + } + state._M_basic_block = false; + state._M_prologue_end = false; + state._M_epilogue_begin = false; + state._M_discriminator = 0; + }; + + while (!error() && !unit.empty()) { + auto opc = consume<_Byte>(unit); + switch (opc) { + default: + if (opc >= opcode_base) { + // 1. special opcodes + opc -= opcode_base; + state._M_address += opc / line_range + / maximum_operations_per_instruction + * minimum_instruction_length; + state._M_line += line_base + opc % line_range; + emit_loc(); + } else { + // 2. standard opcodes + for (auto i = standard_opcode_lengths[opc - 1]; i; --i) { + (void)consume_uleb128(unit); + } + } + break; + case DW_LNS_copy: + emit_loc(); + break; + case DW_LNS_advance_pc: + state._M_address += consume_uleb128(unit).get(); + break; + case DW_LNS_advance_line: + state._M_line += consume_sleb128(unit); + break; + case DW_LNS_set_file: + state._M_file = consume_uleb128(unit); + break; + case DW_LNS_set_column: + state._M_column = consume_uleb128(unit); + break; + case DW_LNS_negate_stmt: + state._M_is_stmt = !state._M_is_stmt; + break; + case DW_LNS_set_basic_block: + state._M_basic_block = true; + break; + case DW_LNS_const_add_pc: + state._M_address += (0xFFu - opcode_base) / line_range + / maximum_operations_per_instruction * minimum_instruction_length; + break; + case DW_LNS_fixed_advance_pc: + state._M_address += consume<_Half>(unit); + break; + case DW_LNS_set_prologue_end: + state._M_prologue_end = true; + break; + case DW_LNS_set_epilogue_begin: + state._M_epilogue_begin = true; + break; + case DW_LNS_set_isa: + state._M_isa = consume_uleb128(unit); + break; + case DW_LNS_extended: { + auto len = consume_uleb128(unit); + auto instruction = unit.take_first(len); + switch (opc = consume<_Byte>(instruction)) { + // 3. extended opcodes + case DW_LNE_end_sequence: + state._M_end_sequence = true; + emit_loc(); + reset_state(); + break; + case DW_LNE_set_address: { + state._M_address = consume<_Addr>(instruction); + break; + } + case DW_LNE_set_discriminator: + state._M_discriminator = consume_uleb128(instruction); + break; + } + break; + } + } + } + return file_map; +} +const SrcFile &Line::get(_Files::size_type pos) const noexcept { + if (pos >= size()) { + error("Index into files out of bounds"); + return SrcFile::_S_null; + } + return _M_files[pos]; +} + +std::vector> LocLists::__parse(const _Die &unit_entry) { + std::vector> indices; + auto base = unit_entry.attr(_At::DW_AT_loclists_base); + if (base.is_none()) { + return indices; + } + auto unit = _M_data.subview(base.cst() - sizeof(_Word) + - sizeof(_Half) - sizeof(_Byte) - sizeof(_Byte) - sizeof(_Word)); + auto unit_length = consume<_Word>(unit); + unit = unit.first(unit_length); + auto version = consume<_Half>(unit); + if (version < 5 || version > 5) { + error("Unsupported loclists version"); + return {}; + } + auto address_size = consume<_Byte>(unit); + if (address_size != sizeof(_Addr)) { + error("Unsupported address size"); + return {}; + } + auto segment_selector_size = consume<_Byte>(unit); + if (segment_selector_size) { + error("Unsupported segment selector size"); + return {}; + } + auto offset_entry_count = consume<_Word>(unit); + auto addr = _M_file.addr().get(unit_entry); + auto base_addr = unit_entry.attr(_At::DW_AT_low_pc).addr(); + for (auto offset : unit.bitcast().first(offset_entry_count)) { + if (error()) { + return {}; + } + + indices.emplace_back(_M_locs.size(), _InvalidWord); + auto loclist = unit.subview(offset); + while (!error() && indices.back().second == _InvalidWord) { + std::uint64_t base_index, start_index, end_index; + std::uint32_t start_addr, end_addr; + switch (consume<_Byte>(loclist)) { + case DW_LLE_end_of_list: + indices.back().second = _M_locs.size(); + continue; + case DW_LLE_base_addressx: + base_index = consume_uleb128(loclist); + if (base_index >= addr.size()) { + error("Out of range index into addr"); + return {}; + } + base_addr = addr[base_index]; + continue; + case DW_LLE_startx_endx: + start_index = consume_uleb128(loclist); + end_index = consume_uleb128(loclist); + if (start_index >= addr.size() || + end_index >= addr.size()) { + error("Out of range index into addr"); + return {}; + } + start_addr = addr[start_index]; + end_addr = addr[end_index]; + break; + case DW_LLE_startx_length: + start_index = consume_uleb128(loclist); + if (start_index >= addr.size()) { + error("Out of range index into addr"); + return {}; + } + start_addr = addr[start_index]; + end_addr = start_addr + _Addr(consume_uleb128(loclist)); + break; + case DW_LLE_offset_pair: + start_addr = base_addr + _Addr(consume_uleb128(loclist)); + end_addr = base_addr + _Addr(consume_uleb128(loclist)); + break; + case DW_LLE_default_location: + _M_locs.push_back({ { std::numeric_limits<_Addr>::min(), + std::numeric_limits<_Addr>::max() }, {} }); + continue; + case DW_LLE_base_address: + base_addr = consume<_Addr>(loclist); + continue; + case DW_LLE_start_end: + start_addr = consume<_Addr>(loclist); + end_addr = consume<_Addr>(loclist); + break; + case DW_LLE_start_length: + start_addr = consume<_Addr>(loclist); + end_addr = start_addr + consume<_Addr>(loclist); + break; + default: + error("Unknown range list entry kind"); + return {}; + } + _M_locs.push_back({ { start_addr, end_addr }, + loclist.take_first(consume_uleb128(loclist)) }); + } + } + return indices; +} +_LocView LocLists::get(std::pair indices) const noexcept { + if (indices.first > indices.second || indices.first > _M_locs.size() + || indices.second > _M_locs.size()) { + error("Out of range index into location offsets"); + return {}; + } + return { _M_locs.data() + indices.first, _M_locs.data() + indices.second }; +} + +std::vector> RngLists::__parse(const _Die &unit_entry) { + std::vector> indices; + auto base = unit_entry.attr(_At::DW_AT_rnglists_base); + if (base.is_none()) { + return indices; + } + auto unit = _M_data.subview(base.cst() - sizeof(_Word) + - sizeof(_Half) - sizeof(_Byte) - sizeof(_Byte) - sizeof(_Word)); + auto unit_length = consume<_Word>(unit); + unit = unit.first(unit_length); + auto version = consume<_Half>(unit); + if (version < 5 || version > 5) { + error("Unsupported rnglists version"); + return {}; + } + auto address_size = consume<_Byte>(unit); + if (address_size != sizeof(_Addr)) { + error("Unsupported address size"); + return {}; + } + auto segment_selector_size = consume<_Byte>(unit); + if (segment_selector_size) { + error("Unsupported segment selector size"); + return {}; + } + auto offset_entry_count = consume<_Word>(unit); + auto addr = _M_file.addr().get(unit_entry); + auto base_addr = unit_entry.attr(_At::DW_AT_low_pc).addr(); + for (auto offset : unit.bitcast().first(offset_entry_count)) { + if (error()) { + return {}; + } + + indices.emplace_back(_M_rngs.size(), _InvalidWord); + auto rnglist = unit.subview(offset); + while (!error() && indices.back().second == _InvalidWord) { + std::uint64_t base_index, start_index, end_index; + std::uint32_t start_addr, end_addr; + switch (consume<_Byte>(rnglist)) { + case DW_RLE_end_of_list: + indices.back().second = _M_rngs.size(); + continue; + case DW_RLE_base_addressx: + base_index = consume_uleb128(rnglist); + if (base_index >= addr.size()) { + error("Out of range index into addr"); + return {}; + } + base_addr = addr[base_index]; + continue; + case DW_RLE_startx_endx: + start_index = consume_uleb128(rnglist); + end_index = consume_uleb128(rnglist); + if (start_index >= addr.size() || + end_index >= addr.size()) { + error("Out of range index into addr"); + return {}; + } + start_addr = addr[start_index]; + end_addr = addr[end_index]; + break; + case DW_RLE_startx_length: + start_index = consume_uleb128(rnglist); + if (start_index >= addr.size()) { + error("Out of range index into addr"); + return {}; + } + start_addr = addr[start_index]; + end_addr = start_addr + _Addr(consume_uleb128(rnglist)); + break; + case DW_RLE_offset_pair: + start_addr = base_addr + _Addr(consume_uleb128(rnglist)); + end_addr = base_addr + _Addr(consume_uleb128(rnglist)); + break; + case DW_RLE_base_address: + base_addr = consume<_Addr>(rnglist); + continue; + case DW_RLE_start_end: + start_addr = consume<_Addr>(rnglist); + end_addr = consume<_Addr>(rnglist); + break; + case DW_RLE_start_length: + start_addr = consume<_Addr>(rnglist); + end_addr = start_addr + consume<_Addr>(rnglist); + break; + default: + error("Unknown range list entry kind"); + return {}; + } + _M_rngs.emplace_back(start_addr, end_addr); + } + } + return indices; +} +_RngView RngLists::get(std::pair indices) const noexcept { + if (indices.first > indices.second || indices.first > _M_rngs.size() + || indices.second > _M_rngs.size()) { + error("Out of range index into range offsets"); + return {}; + } + return { _M_rngs.data() + indices.first, _M_rngs.data() + indices.second }; +} + +_Str Str::get(std::uint32_t offset) const noexcept { + Data temp(_M_data.subview(offset)); + return consume_strz(temp); +} + +_OffView StrOffsets::get(const _Die &unit_entry) const noexcept { + auto base = unit_entry.attr(_At::DW_AT_str_offsets_base); + if (base.is_none()) { + return {}; + } + auto unit = _M_data.subview(base.cst() - sizeof(_Word) - sizeof(_Half) - sizeof(_Half)); + unit = unit.first(consume<_Word>(unit)); + auto version = consume<_Half>(unit); + if (version < 5 || version > 5) { + error("Unsupported str offsets version"); + return {}; + } + consume<_Half>(unit); + return unit.bitcast<_OffView::element_type>(); +} +} // end namespace section +} // end namespace dwarf +} // end namespace debuginfo diff --git a/gui/qt/debugger/debuginfo.h b/gui/qt/debugger/debuginfo.h new file mode 100644 index 000000000..ebc6c5beb --- /dev/null +++ b/gui/qt/debugger/debuginfo.h @@ -0,0 +1,1716 @@ +#ifndef DEBUGINFO_H +#define DEBUGINFO_H + +/* Enable math constants on MSVC */ +#ifdef _MSC_VER +#define _USE_MATH_DEFINES +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace debuginfo { +namespace detail { + +constexpr auto BitsPerByte = std::numeric_limits::digits; + +template +class [[gnu::packed]] UnalignedLittleEndianIntegerImpl { + using Byte = unsigned char; + using IntMax = std::conditional_t; + +public: + std::array m_value; + constexpr UnalignedLittleEndianIntegerImpl() = default; + constexpr UnalignedLittleEndianIntegerImpl(IntMax value) + : m_value{ Byte(value >> Indices * BitsPerByte)... } {} + template + constexpr UnalignedLittleEndianIntegerImpl( + UnalignedLittleEndianIntegerImpl value) + : UnalignedLittleEndianIntegerImpl{IntMax(value)} {} + + constexpr UnalignedLittleEndianIntegerImpl &operator++() noexcept { + return *this = *this + 1; + } + constexpr UnalignedLittleEndianIntegerImpl &operator++(int) noexcept { + auto result = *this; + ++*this; + return result; + } + constexpr UnalignedLittleEndianIntegerImpl &operator+=(IntMax value) noexcept { + return *this = *this + value; + } + constexpr UnalignedLittleEndianIntegerImpl &operator--() noexcept { + return *this = *this - 1; + } + constexpr UnalignedLittleEndianIntegerImpl &operator--(int) noexcept { + auto result = *this; + --*this; + return result; + } + constexpr UnalignedLittleEndianIntegerImpl &operator-=(IntMax value) noexcept { + return *this = *this - value; + } + + constexpr operator IntMax() const noexcept { + constexpr IntMax extend = std::numeric_limits::digits + + std::numeric_limits::is_signed - Bits; + return ((IntMax(m_value[Indices]) << Indices * BitsPerByte) | ...) << extend >> extend; + } +}; + +template +UnalignedLittleEndianIntegerImpl +UnalignedLittleEndianIntegerHelper(std::index_sequence); + +template +using UnalignedLittleEndianInteger = + decltype(UnalignedLittleEndianIntegerHelper( + std::make_index_sequence<(Bits + CHAR_BIT - 1) / CHAR_BIT>{})); + +template class SizedInteger { +public: + template constexpr SizedInteger( + Int value, std::enable_if_t::is_integer + && std::is_signed::value == std::is_signed::value, std::size_t> width + = std::numeric_limits::digits + std::numeric_limits::is_signed) noexcept + : m_value(value), m_width(width) {} + template explicit constexpr SizedInteger( + Enum value, std::enable_if_t::value + && std::is_signed>::value == + std::is_signed::value, std::size_t> width + = std::numeric_limits>::digits + + std::numeric_limits>::is_signed) noexcept + : m_value(Integer(value)), m_width(width) {} + constexpr std::size_t width() const noexcept { return m_width; } + constexpr Integer get() const noexcept { return m_value; } + constexpr operator Integer() const noexcept { return m_value; } + +private: + Integer m_value; + std::size_t m_width; +}; + +} // end namespace detail + +enum class _Void : unsigned char {}; +using _Sbyte = detail::UnalignedLittleEndianInteger; +using _Byte = detail::UnalignedLittleEndianInteger; +using _Shalf = detail::UnalignedLittleEndianInteger; +using _Half = detail::UnalignedLittleEndianInteger; +using _Addr = detail::UnalignedLittleEndianInteger; +using _Off = detail::UnalignedLittleEndianInteger; +using _Sword = detail::UnalignedLittleEndianInteger; +using _Word = detail::UnalignedLittleEndianInteger; +using _Slong = detail::UnalignedLittleEndianInteger; +using _Long = detail::UnalignedLittleEndianInteger; + +static_assert(std::is_trivial_v<_Addr> && alignof(_Addr) == 1 && sizeof(_Addr) == 3, + "_Addr should be trivial, unaligned, and 3 bytes"); + +template class _View { +public: + using element_type = _Type; + using value_type = std::decay_t>; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = element_type *; + using const_pointer = const element_type *; + using reference = element_type &; + using const_reference = const element_type &; + using iterator = pointer; + using reverse_iterator = std::reverse_iterator; + using const_iterator = const_pointer; + using const_reverse_iterator = std::reverse_iterator; + + static const _View _S_empty; + + constexpr _View() noexcept : _M_size(0), _M_ptr(nullptr) {} + constexpr _View(iterator __first, size_type __count) noexcept : _M_size(__count), _M_ptr(__first) {} + constexpr _View(iterator __first, iterator __last) noexcept : _M_size(std::distance(__first, __last)), _M_ptr(__first) {} + template _View(element_type (&__arr)[_Size]) noexcept : _M_size(_Size), _M_ptr(__arr) {} + ~_View() noexcept = default; + constexpr _View(const _View &) noexcept = default; + _View &operator=(const _View &) noexcept = default; + + constexpr iterator begin() const noexcept { return _M_ptr; } + constexpr iterator end() const noexcept { return _M_ptr + _M_size; } + constexpr const_iterator cbegin() const noexcept { return _M_ptr; } + constexpr const_iterator cend() const noexcept { return _M_ptr + _M_size; } + constexpr reverse_iterator rbegin() const noexcept { return end(); } + constexpr reverse_iterator rend() const noexcept { return begin(); } + constexpr const_reverse_iterator crbegin() const noexcept { return cend(); } + constexpr const_reverse_iterator crend() const noexcept { return cbegin(); } + + constexpr reference front() const noexcept { assert(!empty()); return _M_ptr[0]; } + constexpr value_type front_value() const noexcept { if (empty()) return {}; return front(); } + constexpr reference back() const noexcept { assert(!empty()); return _M_ptr[_M_size - 1]; } + constexpr value_type back_value() const noexcept { if (empty()) return {}; return back(); } + constexpr reference operator[](size_type __pos) const noexcept { assert(__pos < _M_size); return _M_ptr[__pos]; } + constexpr value_type value(size_type __pos) const noexcept { if (__pos < _M_size) return (*this)[__pos]; return {}; } + constexpr pointer data() const noexcept { return _M_ptr; } + + constexpr size_type size() const noexcept { return _M_size; } + constexpr size_type size_bytes() const noexcept { return _M_size * size_type(sizeof(element_type)); } + constexpr bool empty() const noexcept { return !_M_size; } + + constexpr _View first(size_type __count) const noexcept { return { _M_ptr, std::min(_M_size, __count) }; } + constexpr _View last(size_type __count) const noexcept { return { _M_ptr + (_M_size - std::min(_M_size, __count)), std::min(_M_size, __count) }; } + constexpr _View subview(size_type __offset, size_type __count = std::numeric_limits::max()) const noexcept { return { _M_ptr + __offset, std::min(_M_size - std::min(_M_size, __offset), __count) }; } + + template constexpr _View<_Tp> bitcast() const noexcept { return { reinterpret_cast::pointer>(_M_ptr), size_bytes() / size_type(sizeof(_Tp)) }; } + + constexpr bool operator==(const _View &__view) const noexcept { + return std::equal(begin(), end(), __view.begin(), __view.end()); + } + constexpr bool operator!=(const _View &__view) const noexcept { return !(*this == __view); } + constexpr bool operator< (const _View &__view) const noexcept { + return std::lexicographical_compare(begin(), end(), __view.begin(), __view.end()); + } + constexpr bool operator> (const _View &__view) const noexcept { return __view < *this; } + constexpr bool operator<=(const _View &__view) const noexcept { return !(*this < __view); } + constexpr bool operator>=(const _View &__view) const noexcept { return !(__view < *this); } + + constexpr void drop_first(size_type __count) noexcept { + __count = std::min(_M_size, __count); + _M_size -= __count; + _M_ptr += __count; + } + constexpr _View take_first(size_type __count) noexcept { + _View __res = first(__count); + drop_first(__count); + return __res; + } + constexpr void drop_last(size_type __count) noexcept { + _M_size -= std::min(_M_size, __count); + } + constexpr _View take_last(size_type __count) noexcept { + _View __res = last(__count); + drop_last(__count); + return __res; + } + + constexpr _View ltrim(value_type __trim) const noexcept { + _View __res(*this); + while (!__res.empty() && __res.front() == __trim) { + __res.drop_first(1); + } + return __res; + } + constexpr _View rtrim(value_type __trim) const noexcept { + _View __res(*this); + while (!__res.empty() && __res.back() == __trim) { + __res.drop_last(1); + } + return __res; + } + + template constexpr _Long partial() const noexcept { + _Tp __res{}; + std::memcpy(&__res, _M_ptr, std::min(_M_size, size_type(sizeof(_Tp)))); + return __res; + } + template constexpr _Long complete() const noexcept { + _Tp __res{}; + if (size_bytes() >= sizeof(_Tp)) { + std::memcpy(&__res, _M_ptr, std::min(_M_size, size_type(sizeof(_Tp)))); + } + return __res; + } + +private: + size_type _M_size; + pointer _M_ptr; +}; +template const _View<_Type> _View<_Type>::_S_empty; + +using Data = _View; +using _Str = _View; +using _OffView = _View; +using _AddrView = _View; + +template class _Path { + using _Parts = std::array; + +public: + static const _Path _S_null; + static constexpr _Str::value_type sep = +#ifdef _WIN32 + '\\'; +#else + '/'; +#endif + static constexpr bool is_sep(_Str::value_type __c) noexcept { return __c == sep || __c == '/'; } + static constexpr bool is_abs(_Str __part) noexcept { + return is_sep(__part.value(0)) +#ifdef _WIN32 + || (__part.value(1) == ':' && is_sep(__part.value(2))) +#endif + ; + } + + using value_type = typename _Parts::value_type; + using size_type = typename _Parts::size_type; + using difference_type = typename _Parts::difference_type; + using reference = typename _Parts::reference; + using const_reference = typename _Parts::const_reference; + using pointer = typename _Parts::pointer; + using const_pointer = typename _Parts::const_pointer; + using iterator = typename _Parts::iterator; + using const_iterator = typename _Parts::const_iterator; + using reverse_iterator = typename _Parts::reverse_iterator; + using const_reverse_iterator = typename _Parts::const_reverse_iterator; + + constexpr _Path() noexcept = default; + constexpr _Path(std::initializer_list<_Str> __parts) noexcept { + auto __it = _M_parts.begin(); + for (auto __part : __parts) { + if (__part.rtrim('\0').empty()) continue; + if (is_abs(__part)) { + __it = _M_parts.begin(); + } + if (__it == _M_parts.end()) { + __it = _M_parts.begin(); + break; + } + *__it++ = __part; + } + while (__it != _M_parts.end()) { + *__it++ = {}; + } + } + + constexpr const_reference at(size_type __pos) const { return _M_parts[__pos]; } + constexpr const_reference operator[](size_type __pos) const noexcept { + if (__pos < _M_parts.size()) + return _M_parts[__pos]; + return _Str::_S_empty; + } + constexpr const_reference front() const noexcept { return (*this)[0]; } + constexpr const_reference back() const noexcept { return (*this)[size()]; } + pointer data() const noexcept { return _M_parts.data(); } + + auto begin() const noexcept { return _M_parts.begin(); } + auto end() const noexcept { + return std::find_if(_M_parts.begin(), _M_parts.end(), + [](const_reference __part) { return __part.empty(); }); + } + auto cbegin() const noexcept { return const_iterator(begin()); } + auto cend() const noexcept { return const_iterator(end()); } + auto rbegin() const noexcept { return reverse_iterator(end()); } + auto rend() const noexcept { return reverse_iterator(begin()); } + auto crbegin() const noexcept { return const_reverse_iterator(rbegin()); } + auto crend() const noexcept { return const_reverse_iterator(rend()); } + + constexpr bool empty() const noexcept { return _M_parts.front().empty(); } + constexpr size_type size() const noexcept { return std::distance(begin(), end()); } + constexpr size_type max_size() const noexcept { return _MaxParts; } + bool is_abs() const noexcept { return is_abs(front()); } + + constexpr bool operator==(const _Path &__path) const noexcept { + return std::equal(canon().begin(), canon().end(), + __path.canon().begin(), __path.canon().end()); + } + constexpr bool operator!=(const _Path &__path) const noexcept { return !(*this == __path); } + constexpr bool operator< (const _Path &__path) const noexcept { + return std::lexicographical_compare(canon().begin(), canon().end(), + __path.canon().begin(), __path.canon().end()); + } + constexpr bool operator> (const _Path &__path) const noexcept { return __path < *this; } + constexpr bool operator<=(const _Path &__path) const noexcept { return !(*this < __path); } + constexpr bool operator>=(const _Path &__path) const noexcept { return !(__path < *this); } + +private: + class _Canon { + friend _Path; + + enum class _State : bool { _AtSep, _Default }; + + constexpr _Canon(const _Path &__path) noexcept : _M_path(__path) {} + + public: + using value_type = _Str::value_type; + using size_type = _Str::size_type; + using difference_type = _Str::difference_type; + using reference = _Str::reference; + using const_reference = _Str::const_reference; + using pointer = _Str::pointer; + using const_pointer = _Str::const_pointer; + + class iterator { + friend _Canon; + + constexpr auto tie() const noexcept { return std::tie(_M_pos, _M_it, _M_state); } + constexpr const _Str &part() const noexcept { return _M_path[_M_pos]; } + constexpr void next() noexcept { + while (_M_it != part().end() && is_sep(*_M_it)) { + ++_M_it; + _M_state = _State::_AtSep; + } + while (_M_it == part().end() || *_M_it == '\0') { + ++_M_pos; + _M_it = part().begin(); + if (part().empty()) { + _M_pos = _MaxParts; + return; + } + _M_state = _State::_AtSep; + } + } + + constexpr iterator(const _Path &__path, _Path::size_type __pos) noexcept + : _M_path(__path), _M_pos(__pos), _M_it(part().begin()) { next(); } + + public: + using value_type = std::iterator_traits<_Str::iterator>::value_type; + using difference_type = std::iterator_traits<_Str::iterator>::difference_type; + using pointer = std::iterator_traits<_Str::iterator>::pointer; + using reference = std::iterator_traits<_Str::iterator>::reference; + using iterator_category = std::forward_iterator_tag; + + constexpr iterator() : _M_path(_S_null), _M_pos(), _M_it() {} + + constexpr reference operator*() const noexcept { + switch (_M_state) { + case _State::_AtSep: + return sep; + case _State::_Default: + return *_M_it; + } + } + constexpr pointer operator->() const noexcept { return &*this; } + constexpr iterator &operator++() noexcept { + if (std::exchange(_M_state, _State::_Default) == _State::_Default) { + ++_M_it; + next(); + } + return *this; + } + constexpr iterator operator++(int) noexcept { auto __res(*this); ++*this; return __res; } + + constexpr bool operator==(const iterator &__it) const noexcept { return tie() == __it.tie(); } + constexpr bool operator!=(const iterator &__it) const noexcept { return tie() != __it.tie(); } + constexpr bool operator< (const iterator &__it) const noexcept { return tie() < __it.tie(); } + constexpr bool operator> (const iterator &__it) const noexcept { return tie() > __it.tie(); } + constexpr bool operator<=(const iterator &__it) const noexcept { return tie() <= __it.tie(); } + constexpr bool operator>=(const iterator &__it) const noexcept { return tie() >= __it.tie(); } + + private: + const _Path &_M_path; + _Path::size_type _M_pos; + _Str::iterator _M_it; + _State _M_state = _State::_Default; + }; + using const_iterator = iterator; + + constexpr iterator begin() const noexcept { return { _M_path, 0 }; } + constexpr iterator end() const noexcept { return { _M_path, _MaxParts }; } + constexpr const_iterator cbegin() const noexcept { return begin(); } + constexpr const_iterator cend() const noexcept { return end(); } + + private: + const _Path &_M_path; + }; + +public: + constexpr _Canon canon() const noexcept { return *this; } + +private: + std::array<_Str, _MaxParts> _M_parts; +}; +template const _Path<_MaxParts> _Path<_MaxParts>::_S_null; +template const _Str::value_type _Path<_MaxParts>::sep; + +} // end namespace debuginfo + +namespace std { +template +class numeric_limits< + debuginfo::detail::UnalignedLittleEndianIntegerImpl> { + using Integer = debuginfo::detail::UnalignedLittleEndianIntegerImpl; + +public: + static constexpr bool is_specialized = true; + static constexpr bool is_signed = Signed; + static constexpr bool is_integer = true; + static constexpr bool is_exact = true; + static constexpr bool has_infinity = false; + static constexpr bool has_quiet_NaN = false; + static constexpr bool has_signaling_NaN = false; + static constexpr float_denorm_style has_denorm = denorm_absent; + static constexpr bool has_denorm_loss = false; + static constexpr float_round_style round_style = round_toward_zero; + static constexpr bool is_iec559 = false; + static constexpr bool is_modulo = !Signed; + static constexpr int digits = Bits - Signed; + static constexpr int digits10 = M_LN2 / M_LN10 * digits; + static constexpr int max_digits10 = 0; + static constexpr int radix = 2; + static constexpr int min_exponent = 0; + static constexpr int min_exponent10 = 0; + static constexpr int max_exponent = 0; + static constexpr int max_exponent10 = 0; + static constexpr bool traps = true; + static constexpr bool tinyness_before = false; + + static constexpr Integer min() noexcept { + return Signed ? ~INTMAX_C(0) << (Bits - 1) : UINTMAX_C(0); + } + static constexpr Integer lowest() noexcept { + return Signed ? ~INTMAX_C(0) << (Bits - 1) : UINTMAX_C(0); + } + static constexpr Integer max() noexcept { return ~min(); } + static constexpr Integer epsilon() noexcept { return 0; } + static constexpr Integer round_error() noexcept { return 0; } + static constexpr Integer infinity() noexcept { return 0; } + static constexpr Integer quiet_NaN() noexcept { return 0; } + static constexpr Integer signaling_NaN() noexcept { return 0; } + static constexpr Integer denorm_min() noexcept { return 0; } +}; +template +struct numeric_limits> + : numeric_limits> {}; +template +struct numeric_limits< volatile debuginfo::detail::UnalignedLittleEndianIntegerImpl< + Signed, Bits, Indices...>> + : numeric_limits> {}; +template +struct numeric_limits> + : numeric_limits> {}; + +template<> struct hash { + constexpr std::size_t operator()(debuginfo::_Str __str) const noexcept { + debuginfo::_Word __hash = 5381; + for (auto c : __str.rtrim('\0')) { + __hash = __hash * 33 + debuginfo::_Byte(c); + } + return __hash; + } +}; + +template struct hash> { + constexpr std::size_t operator()(const debuginfo::_Path<_MaxParts> &__path) const noexcept { + debuginfo::_Word __hash = 5381; + for (auto c : __path.canon()) { + __hash = __hash * 33 + debuginfo::_Byte(c); + } + return __hash; + } +}; +} // end namespace std + +namespace debuginfo { +constexpr _Off _InvalidOff = std::numeric_limits<_Off>::max(); +constexpr _Word _InvalidWord = std::numeric_limits<_Word>::max(); + +namespace elf { +class File; + +struct [[gnu::packed]] ProgramHeader { + _Word p_type; + _Off p_offset; + _Word p_vaddr; + _Word p_paddr; + _Word p_filesz; + _Word p_memsz; + _Word p_flags; + _Word p_align; + + Data data(const File &___file) const noexcept; +}; + +struct [[gnu::packed]] SectionHeader { + _Word sh_name; + _Word sh_type; + _Word sh_flags; + _Word sh_addr; + _Off sh_offset; + _Word sh_size; + _Word sh_link; + _Word sh_info; + _Word sh_addralign; + _Word sh_entsize; + + _Str name(const File &__file) const noexcept; + Data data(const File &___file) const noexcept; +}; + +class File { + friend SectionHeader; + friend ProgramHeader; + + constexpr bool has_header() const noexcept { return _M_data.size_bytes() >= sizeof(Header); } + + template constexpr _View<_Header> get( + std::uint32_t __offset, std::uint16_t __size, std::uint16_t __count) const noexcept { + if (__size != sizeof(_Header)) { + return {}; + } + return _M_data.subview(__offset).bitcast<_Header>().first(__count); + } + +public: + struct Header { + _Byte ei_mag[4]; + _Byte ei_class; + _Byte ei_data; + _Byte ei_version; + _Byte ei_osabi; + _Byte ei_pad[8]; + _Half e_type; + _Half e_machine; + _Word e_version; + _Word e_entry; + _Off e_phoff; + _Off e_shoff; + _Word e_flags; + _Half e_ehsize; + _Half e_phentsize; + _Half e_phnum; + _Half e_shentsize; + _Half e_shnum; + _Half e_shstrndx; + + const static Header ez80; + }; + + explicit File(Data __data) noexcept : _M_data(__data) {} + + constexpr auto &header() const noexcept { + assert(has_header()); + return _M_data.bitcast().front(); + } + + constexpr bool validate(const Header &__expected) const noexcept { + return has_header() && !std::memcmp(&header(), &__expected, offsetof(Header, e_entry)) && + (header().e_flags & __expected.e_flags) == __expected.e_flags && + header().e_ehsize == sizeof(Header) && header().e_shstrndx < header().e_shnum; + } + + constexpr auto program_headers() const noexcept { + return get(header().e_phoff, header().e_phentsize, header().e_phnum); + } + + constexpr auto section_headers() const noexcept { + return get(header().e_shoff, header().e_shentsize, header().e_shnum); + } + +protected: + const Data _M_data; +}; +} // end namespace elf + +namespace dwarf { +enum class Reg : std::uint8_t { BC, DE, HL, AF, IX, IY, SPS, SPL, PC, None }; + +// Table 7.3: Tag encodings +enum class _Tag : std::uint8_t { + _None, + DW_TAG_array_type = 0x01, + DW_TAG_class_type = 0x02, + DW_TAG_entry_point = 0x03, + DW_TAG_enumeration_type = 0x04, + DW_TAG_formal_parameter = 0x05, + DW_TAG_imported_declaration = 0x08, + DW_TAG_label = 0x0a, + DW_TAG_lexical_block = 0x0b, + DW_TAG_member = 0x0d, + DW_TAG_pointer_type = 0x0f, + DW_TAG_reference_type = 0x10, + DW_TAG_compile_unit = 0x11, + DW_TAG_string_type = 0x12, + DW_TAG_structure_type = 0x13, + DW_TAG_subroutine_type = 0x15, + DW_TAG_typedef = 0x16, + DW_TAG_union_type = 0x17, + DW_TAG_unspecified_parameters = 0x18, + DW_TAG_variant = 0x19, + DW_TAG_common_block = 0x1a, + DW_TAG_common_inclusion = 0x1b, + DW_TAG_inheritance = 0x1c, + DW_TAG_inlined_subroutine = 0x1d, + DW_TAG_module = 0x1e, + DW_TAG_ptr_to_member_type = 0x1f, + DW_TAG_set_type = 0x20, + DW_TAG_subrange_type = 0x21, + DW_TAG_with_stmt = 0x22, + DW_TAG_access_declaration = 0x23, + DW_TAG_base_type = 0x24, + DW_TAG_catch_block = 0x25, + DW_TAG_const_type = 0x26, + DW_TAG_constant = 0x27, + DW_TAG_enumerator = 0x28, + DW_TAG_file_type = 0x29, + DW_TAG_friend = 0x2a, + DW_TAG_namelist = 0x2b, + DW_TAG_namelist_item = 0x2c, + DW_TAG_packed_type = 0x2d, + DW_TAG_subprogram = 0x2e, + DW_TAG_template_type_parameter = 0x2f, + DW_TAG_template_value_parameter = 0x30, + DW_TAG_thrown_type = 0x31, + DW_TAG_try_block = 0x32, + DW_TAG_variant_part = 0x33, + DW_TAG_variable = 0x34, + DW_TAG_volatile_type = 0x35, + DW_TAG_dwarf_procedure = 0x36, + DW_TAG_restrict_type = 0x37, + DW_TAG_interface_type = 0x38, + DW_TAG_namespace = 0x39, + DW_TAG_imported_module = 0x3a, + DW_TAG_unspecified_type = 0x3b, + DW_TAG_partial_unit = 0x3c, + DW_TAG_imported_unit = 0x3d, + DW_TAG_condition = 0x3f, + DW_TAG_shared_type = 0x40, + DW_TAG_type_unit = 0x41, + DW_TAG_rvalue_reference_type = 0x42, + DW_TAG_template_alias = 0x43, + DW_TAG_coarray_type = 0x44, + DW_TAG_generic_subrange = 0x45, + DW_TAG_dynamic_type = 0x46, + DW_TAG_atomic_type = 0x47, + DW_TAG_call_site = 0x48, + DW_TAG_call_site_parameter = 0x49, + DW_TAG_skeleton_unit = 0x4a, + DW_TAG_immutable_type = 0x4b, + // For Internal Use + _Unknown, + _Unused +}; +enum { + // Vendor Extensions + DW_TAG_lo_user = 0x4080, + DW_TAG_GNU_call_site = 0x4109, + DW_TAG_GNU_call_site_parameter = 0x410a, + DW_TAG_hi_user = 0xffff, +}; +// Table 7.5: Attribute encodings +enum class _At : std::uint8_t { + _None, + DW_AT_sibling = 0x01, // reference + DW_AT_location = 0x02, // exprloc, loclist + DW_AT_name = 0x03, // string + DW_AT_ordering = 0x09, // constant + DW_AT_byte_size = 0x0b, // constant, exprloc, reference + DW_AT_bit_size = 0x0d, // constant, exprloc, reference + DW_AT_stmt_list = 0x10, // lineptr + DW_AT_low_pc = 0x11, // address + DW_AT_high_pc = 0x12, // address, constant + DW_AT_language = 0x13, // constant + DW_AT_discr = 0x15, // reference + DW_AT_discr_value = 0x16, // constant + DW_AT_visibility = 0x17, // constant + DW_AT_import = 0x18, // reference + DW_AT_string_length = 0x19, // exprloc, loclist, reference + DW_AT_common_reference = 0x1a, // reference + DW_AT_comp_dir = 0x1b, // string + DW_AT_const_value = 0x1c, // block, constant, string + DW_AT_containing_type = 0x1d, // reference + DW_AT_default_value = 0x1e, // constant, reference, flag + DW_AT_inline = 0x20, // constant + DW_AT_is_optional = 0x21, // flag + DW_AT_lower_bound = 0x22, // constant, exprloc, reference + DW_AT_producer = 0x25, // string + DW_AT_prototyped = 0x27, // flag + DW_AT_return_addr = 0x2a, // exprloc, loclist + DW_AT_start_scope = 0x2c, // constant, rnglist + DW_AT_bit_stride = 0x2e, // constant, exprloc, reference + DW_AT_upper_bound = 0x2f, // constant, exprloc, reference + DW_AT_abstract_origin = 0x31, // reference + DW_AT_accessibility = 0x32, // constant + DW_AT_address_class = 0x33, // constant + DW_AT_artificial = 0x34, // flag + DW_AT_base_types = 0x35, // reference + DW_AT_calling_convention = 0x36, // constant + DW_AT_count = 0x37, // constant, exprloc, reference + DW_AT_data_member_location = 0x38, // constant, exprloc, loclist + DW_AT_decl_column = 0x39, // constant + DW_AT_decl_file = 0x3a, // constant + DW_AT_decl_line = 0x3b, // constant + DW_AT_declaration = 0x3c, // flag + DW_AT_discr_list = 0x3d, // block + DW_AT_encoding = 0x3e, // constant + DW_AT_external = 0x3f, // flag + DW_AT_frame_base = 0x40, // exprloc, loclist + DW_AT_friend = 0x41, // reference + DW_AT_identifier_case = 0x42, // constantmacptr + DW_AT_namelist_item = 0x44, // reference + DW_AT_priority = 0x45, // reference + DW_AT_segment = 0x46, // exprloc, loclist + DW_AT_specification = 0x47, // reference + DW_AT_static_link = 0x48, // exprloc, loclist + DW_AT_type = 0x49, // reference + DW_AT_use_location = 0x4a, // exprloc, loclist + DW_AT_variable_parameter = 0x4b, // flag + DW_AT_virtuality = 0x4c, // constant + DW_AT_vtable_elem_location = 0x4d, // exprloc, loclist + DW_AT_allocated = 0x4e, // constant, exprloc, reference + DW_AT_associated = 0x4f, // constant, exprloc, reference + DW_AT_data_location = 0x50, // exprloc + DW_AT_byte_stride = 0x51, // constant, exprloc, reference + DW_AT_entry_pc = 0x52, // address, constant + DW_AT_use_UTF8 = 0x53, // flag + DW_AT_extension = 0x54, // reference + DW_AT_ranges = 0x55, // rnglist + DW_AT_trampoline = 0x56, // address, flag, reference, string + DW_AT_call_column = 0x57, // constant + DW_AT_call_file = 0x58, // constant + DW_AT_call_line = 0x59, // constant + DW_AT_description = 0x5a, // string + DW_AT_binary_scale = 0x5b, // constant + DW_AT_decimal_scale = 0x5c, // constant + DW_AT_small = 0x5d, // reference + DW_AT_decimal_sign = 0x5e, // constant + DW_AT_digit_count = 0x5f, // constant + DW_AT_picture_string = 0x60, // string + DW_AT_mutable = 0x61, // flag + DW_AT_threads_scaled = 0x62, // flag + DW_AT_explicit = 0x63, // flag + DW_AT_object_pointer = 0x64, // reference + DW_AT_endianity = 0x65, // constant + DW_AT_elemental = 0x66, // flag + DW_AT_pure = 0x67, // flag + DW_AT_recursive = 0x68, // flag + DW_AT_signature = 0x69, // reference + DW_AT_main_subprogram = 0x6a, // flag + DW_AT_data_bit_offset = 0x6b, // constant + DW_AT_const_expr = 0x6c, // flag + DW_AT_enum_class = 0x6d, // flag + DW_AT_linkage_name = 0x6e, // string + DW_AT_string_length_bit_size = 0x6f, // constant + DW_AT_string_length_byte_size = 0x70, // constant + DW_AT_rank = 0x71, // constant, exprloc + DW_AT_str_offsets_base = 0x72, // stroffsetsptr + DW_AT_addr_base = 0x73, // addrptr + DW_AT_rnglists_base = 0x74, // rnglistsptr + DW_AT_dwo_name = 0x76, // string + DW_AT_reference = 0x77, // flag + DW_AT_rvalue_reference = 0x78, // flag + DW_AT_macros = 0x79, // macptr + DW_AT_call_all_calls = 0x7a, // flag + DW_AT_call_all_source_calls = 0x7b, // flag + DW_AT_call_all_tail_calls = 0x7c, // flag + DW_AT_call_return_pc = 0x7d, // address + DW_AT_call_value = 0x7e, // exprloc + DW_AT_call_origin = 0x7f, // exprloc + DW_AT_call_parameter = 0x80, // reference + DW_AT_call_pc = 0x81, // address + DW_AT_call_tail_call = 0x82, // flag + DW_AT_call_target = 0x83, // exprloc + DW_AT_call_target_clobbered = 0x84, // exprloc + DW_AT_call_data_location = 0x85, // exprloc + DW_AT_call_data_value = 0x86, // exprloc + DW_AT_noreturn = 0x87, // flag + DW_AT_alignment = 0x88, // constant + DW_AT_export_symbols = 0x89, // flag + DW_AT_deleted = 0x8a, // flag + DW_AT_defaulted = 0x8b, // constant + DW_AT_loclists_base = 0x8c, // loclistsptr + // For Internal Use + _Unknown, + _Unused +}; +enum { + // Vendor Extensions + DW_AT_lo_user = 0x2000, + DW_AT_GNU_call_site_value = 0x2111, + DW_AT_GNU_call_site_target = 0x2113, + DW_AT_GNU_tail_call = 0x2115, + DW_AT_GNU_all_call_sites = 0x2117, + DW_AT_hi_user = 0x3fff, +}; +// Table 7.6: Attribute form encodings +enum class _Form : std::uint8_t { + _None, + DW_FORM_addr = 0x01, + DW_FORM_block2 = 0x03, + DW_FORM_block4 = 0x04, + DW_FORM_data2 = 0x05, + DW_FORM_data4 = 0x06, + DW_FORM_data8 = 0x07, + DW_FORM_string = 0x08, + DW_FORM_block = 0x09, + DW_FORM_block1 = 0x0a, + DW_FORM_data1 = 0x0b, + DW_FORM_flag = 0x0c, + DW_FORM_sdata = 0x0d, + DW_FORM_strp = 0x0e, + DW_FORM_udata = 0x0f, + DW_FORM_ref_addr = 0x10, + DW_FORM_ref1 = 0x11, + DW_FORM_ref2 = 0x12, + DW_FORM_ref4 = 0x13, + DW_FORM_ref8 = 0x14, + DW_FORM_ref_udata = 0x15, + DW_FORM_indirect = 0x16, + DW_FORM_sec_offset = 0x17, + DW_FORM_exprloc = 0x18, + DW_FORM_flag_present = 0x19, + DW_FORM_strx = 0x1a, + DW_FORM_addrx = 0x1b, + DW_FORM_ref_sup4 = 0x1c, + DW_FORM_strp_sup = 0x1d, + DW_FORM_data16 = 0x1e, + DW_FORM_line_strp = 0x1f, + DW_FORM_ref_sig8 = 0x20, + DW_FORM_implicit_const = 0x21, + DW_FORM_loclistx = 0x22, + DW_FORM_rnglistx = 0x23, + DW_FORM_ref_sup8 = 0x24, + DW_FORM_strx1 = 0x25, + DW_FORM_strx2 = 0x26, + DW_FORM_strx3 = 0x27, + DW_FORM_strx4 = 0x28, + DW_FORM_addrx1 = 0x29, + DW_FORM_addrx2 = 0x2a, + DW_FORM_addrx3 = 0x2b, + DW_FORM_addrx4 = 0x2c, + // For Internal Use + _HighPC, + _DeclFile, + _Unused +}; +// Table 7.9: DWARF operation encodings +enum { + _None, + DW_OP_addr = 0x03, + DW_OP_deref = 0x06, + DW_OP_const1u = 0x08, + DW_OP_const1s = 0x09, + DW_OP_const2u = 0x0a, + DW_OP_const2s = 0x0b, + DW_OP_const4u = 0x0c, + DW_OP_const4s = 0x0d, + DW_OP_const8u = 0x0e, + DW_OP_const8s = 0x0f, + DW_OP_constu = 0x10, + DW_OP_consts = 0x11, + DW_OP_dup = 0x12, + DW_OP_drop = 0x13, + DW_OP_over = 0x14, + DW_OP_pick = 0x15, + DW_OP_swap = 0x16, + DW_OP_rot = 0x17, + DW_OP_xderef = 0x18, + DW_OP_abs = 0x19, + DW_OP_and = 0x1a, + DW_OP_div = 0x1b, + DW_OP_minus = 0x1c, + DW_OP_mod = 0x1d, + DW_OP_mul = 0x1e, + DW_OP_neg = 0x1f, + DW_OP_not = 0x20, + DW_OP_or = 0x21, + DW_OP_plus = 0x22, + DW_OP_plus_uconst = 0x23, + DW_OP_shl = 0x24, + DW_OP_shr = 0x25, + DW_OP_shra = 0x26, + DW_OP_xor = 0x27, + DW_OP_bra = 0x28, + DW_OP_eq = 0x29, + DW_OP_ge = 0x2a, + DW_OP_gt = 0x2b, + DW_OP_le = 0x2c, + DW_OP_lt = 0x2d, + DW_OP_ne = 0x2e, + DW_OP_skip = 0x2f, + DW_OP_lit0 = 0x30, + DW_OP_lit1 = 0x31, + DW_OP_lit2 = 0x32, + DW_OP_lit3 = 0x33, + DW_OP_lit4 = 0x34, + DW_OP_lit5 = 0x35, + DW_OP_lit6 = 0x36, + DW_OP_lit7 = 0x37, + DW_OP_lit8 = 0x38, + DW_OP_lit9 = 0x39, + DW_OP_lit10 = 0x3a, + DW_OP_lit11 = 0x3b, + DW_OP_lit12 = 0x3c, + DW_OP_lit13 = 0x3d, + DW_OP_lit14 = 0x3e, + DW_OP_lit15 = 0x3f, + DW_OP_lit16 = 0x40, + DW_OP_lit17 = 0x41, + DW_OP_lit18 = 0x42, + DW_OP_lit19 = 0x43, + DW_OP_lit20 = 0x44, + DW_OP_lit21 = 0x45, + DW_OP_lit22 = 0x46, + DW_OP_lit23 = 0x47, + DW_OP_lit24 = 0x48, + DW_OP_lit25 = 0x49, + DW_OP_lit26 = 0x4a, + DW_OP_lit27 = 0x4b, + DW_OP_lit28 = 0x4c, + DW_OP_lit29 = 0x4d, + DW_OP_lit30 = 0x4e, + DW_OP_lit31 = 0x4f, + DW_OP_reg0 = 0x50, + DW_OP_reg1 = 0x51, + DW_OP_reg2 = 0x52, + DW_OP_reg3 = 0x53, + DW_OP_reg4 = 0x54, + DW_OP_reg5 = 0x55, + DW_OP_reg6 = 0x56, + DW_OP_reg7 = 0x57, + DW_OP_reg8 = 0x58, + DW_OP_reg9 = 0x59, + DW_OP_reg10 = 0x5a, + DW_OP_reg11 = 0x5b, + DW_OP_reg12 = 0x5c, + DW_OP_reg13 = 0x5d, + DW_OP_reg14 = 0x5e, + DW_OP_reg15 = 0x5f, + DW_OP_reg16 = 0x60, + DW_OP_reg17 = 0x61, + DW_OP_reg18 = 0x62, + DW_OP_reg19 = 0x63, + DW_OP_reg20 = 0x64, + DW_OP_reg21 = 0x65, + DW_OP_reg22 = 0x66, + DW_OP_reg23 = 0x67, + DW_OP_reg24 = 0x68, + DW_OP_reg25 = 0x69, + DW_OP_reg26 = 0x6a, + DW_OP_reg27 = 0x6b, + DW_OP_reg28 = 0x6c, + DW_OP_reg29 = 0x6d, + DW_OP_reg30 = 0x6e, + DW_OP_reg31 = 0x6f, + DW_OP_breg0 = 0x70, + DW_OP_breg1 = 0x71, + DW_OP_breg2 = 0x72, + DW_OP_breg3 = 0x73, + DW_OP_breg4 = 0x74, + DW_OP_breg5 = 0x75, + DW_OP_breg6 = 0x76, + DW_OP_breg7 = 0x77, + DW_OP_breg8 = 0x78, + DW_OP_breg9 = 0x79, + DW_OP_breg10 = 0x7a, + DW_OP_breg11 = 0x7b, + DW_OP_breg12 = 0x7c, + DW_OP_breg13 = 0x7d, + DW_OP_breg14 = 0x7e, + DW_OP_breg15 = 0x7f, + DW_OP_breg16 = 0x80, + DW_OP_breg17 = 0x81, + DW_OP_breg18 = 0x82, + DW_OP_breg19 = 0x83, + DW_OP_breg20 = 0x84, + DW_OP_breg21 = 0x85, + DW_OP_breg22 = 0x86, + DW_OP_breg23 = 0x87, + DW_OP_breg24 = 0x88, + DW_OP_breg25 = 0x89, + DW_OP_breg26 = 0x8a, + DW_OP_breg27 = 0x8b, + DW_OP_breg28 = 0x8c, + DW_OP_breg29 = 0x8d, + DW_OP_breg30 = 0x8e, + DW_OP_breg31 = 0x8f, + DW_OP_regx = 0x90, + DW_OP_fbreg = 0x91, + DW_OP_bregx = 0x92, + DW_OP_piece = 0x93, + DW_OP_deref_size = 0x94, + DW_OP_nop = 0x96, + DW_OP_push_object_address = 0x97, + DW_OP_call2 = 0x98, + DW_OP_call4 = 0x99, + DW_OP_call_ref = 0x9a, + DW_OP_form_tls_address = 0x9b, + DW_OP_call_fram_cfa = 0x9c, + DW_OP_bit_piece = 0x9d, + DW_OP_implicit_value = 0x9e, + DW_OP_stack_value = 0x9f, + DW_OP_implicit_pointer = 0xa0, + DW_OP_addrx = 0xa1, + DW_OP_constx = 0xa2, + DW_OP_entry_value = 0xa3, + DW_OP_const_type = 0xa4, + DW_OP_regval_type = 0xa5, + DW_OP_deref_type = 0xa6, + DW_OP_xderef_type = 0xa7, + DW_OP_convert = 0xa8, + DW_OP_reinterpret = 0xa9, + // Vendor Extensions + DW_OP_lo_user = 0xe0, + DW_OP_GNU_entry_value = 0xf3, + DW_OP_hi_user = 0xff, +}; + +class File; + +namespace section { +struct _Die; +class Line; +class LocLists; +class RngLists; + +using _Rng = std::pair<_Addr, _Addr>; +using _RngView = _View; + +struct _Loc { + _Rng _M_range; + Data _M_expr; +}; +using _LocView = _View; + +class SrcFile { + using _Lines = std::map, _Addr>; + +public: + static const SrcFile _S_null; + + SrcFile() noexcept : _M_md5(nullptr) {} + explicit SrcFile(const _Path<3> &__path, Data::element_type *__md5) noexcept + : _M_path(__path), _M_md5(__md5) {} + constexpr const _Path<3> &path() const noexcept { return _M_path; } + constexpr _Lines &lines() noexcept { return _M_lines; } + constexpr const _Lines &lines() const noexcept { return _M_lines; } + constexpr Data::element_type *md5() const noexcept { return _M_md5; } + +private: + _Path<3> _M_path; + Data::element_type *_M_md5; + _Lines _M_lines; +}; + +class _Val { + enum class _Kind : std::uint8_t { + _None, + _Addr, + _Cst, + _Data, + _File, + _Flag, + _Ref, + _LocList, + _RngList, + _Str, + }; + + static constexpr bool is_large(std::size_t bits) noexcept { + return bits > std::numeric_limits::digits; + } + constexpr std::size_t size() const noexcept { return _M_addr; } + constexpr bool is_large() const noexcept { + return is_cst() && size() > std::numeric_limits::digits; + } + std::uintmax_t *take_large() const & { return new std::uintmax_t(*_M_large); } + std::uintmax_t *take_large() && { return std::exchange(_M_large, nullptr); } + void maybe_moved_from() const & {} + void maybe_moved_from() && { + _M_kind = _Kind::_None; + _U_addr = {}; + _U_ptr = {}; + } + template _Val ©(_Ty &&__val) { + if (this == &__val) return *this; + if (is_large() && __val.is_large()) { + _M_flag = __val._M_flag; + _M_addr = __val._M_addr; + *_M_large = *__val._M_large; + return *this; + } + this->~_Val(); + _M_kind = __val._M_kind; + _M_flag = __val._M_flag; + switch (_M_kind) { + case _Kind::_None: + case _Kind::_File: + case _Kind::_Flag: + case _Kind::_Ref: + _U_addr = __val._U_addr; + break; + case _Kind::_Addr: + case _Kind::_Cst: + case _Kind::_Data: + case _Kind::_LocList: + case _Kind::_RngList: + case _Kind::_Str: + _M_addr = __val._M_addr; + break; + } + switch (_M_kind) { + case _Kind::_None: + case _Kind::_Addr: + case _Kind::_Flag: + _U_ptr = __val._U_ptr; + break; + case _Kind::_Cst: + if (is_large()) + _M_large = std::forward<_Ty>(__val).take_large(); + else + _M_small = __val._M_small; + break; + case _Kind::_Data: + _M_data = __val._M_data; + break; + case _Kind::_File: + _M_file = __val._M_file; + break; + case _Kind::_Ref: + _M_ref = __val._M_ref; + break; + case _Kind::_LocList: + _M_loclist = __val._M_loclist; + break; + case _Kind::_RngList: + _M_rnglist = __val._M_rnglist; + break; + case _Kind::_Str: + _M_str = __val._M_str; + break; + } + std::forward<_Ty>(__val).maybe_moved_from(); + return *this; + } + +public: + static const _Val _S_none; + + _Val(const _Val &__val) : _Val() { copy(__val); } + _Val(_Val &&__val) : _Val() { copy(std::move(__val)); } + _Val &operator=(const _Val &__val) { return copy(__val); } + _Val &operator=(_Val &&__val) { return copy(std::move(__val)); } + ~_Val() noexcept { if (is_large()) delete _M_large; } + + constexpr _Val() noexcept : _M_kind(_Kind::_None), _M_flag(), _U_addr(), _U_ptr() {} + constexpr bool is_none() const noexcept { return _M_kind == _Kind::_None; } + + constexpr _Val(_Addr __addr) noexcept : _M_kind(_Kind::_Addr), _M_flag(), _M_addr(__addr), _U_ptr() {} + constexpr bool is_addr() const noexcept { return _M_kind == _Kind::_Addr; } + constexpr _Addr addr() const noexcept { if (is_addr()) return _M_addr; return {}; } + + template _Val(detail::SizedInteger<_Int> __cst) + : _M_kind(_Kind::_Cst), _M_flag(std::is_signed<_Int>::value), _M_addr(__cst.width()), _U_ptr() { + if (is_large()) { + _M_large = new std::uintmax_t(__cst.get()); + } else { + _M_small = __cst.get(); + } + } + template::is_integer, int> = 0> + _Val(_Int __cst) : _Val(detail::SizedInteger<_Int>(__cst)) {} + constexpr bool is_cst() const noexcept { return _M_kind == _Kind::_Cst; } + constexpr std::uintmax_t cst() const noexcept { + if (is_large()) return *_M_large; + if (is_cst()) return _M_small; + if (is_data()) return data().partial(); + return {}; + } + + constexpr _Val(Data __data) noexcept + : _M_kind(_Kind::_Data), _M_flag(), _M_addr(__data.size()), + _M_data(__data.data()) { assert(size() == __data.size() && "Overflow"); } + constexpr bool is_data() const noexcept { return _M_kind == _Kind::_Data; } + constexpr Data data() const noexcept { if (is_data()) return { _M_data, size() }; return {}; } + + constexpr _Val(const _Die &__ref) noexcept + : _M_kind(_Kind::_Ref), _M_flag(), _U_addr(), _M_ref(&__ref) {} + constexpr bool is_ref() const noexcept { return _M_kind == _Kind::_Ref; } + constexpr const _Die &ref() const noexcept; + + constexpr _Val(const SrcFile &__file) + : _M_kind(_Kind::_File), _M_flag(), _U_addr(), _M_file(&__file) {} + constexpr bool is_file() const noexcept { return _M_kind == _Kind::_File; } + constexpr const SrcFile &file() const noexcept { return is_file() ? *_M_file : SrcFile::_S_null; } + + constexpr _Val(bool __flag) noexcept + : _M_kind(_Kind::_Flag), _M_flag(__flag), _U_addr(), _U_ptr() {} + constexpr bool is_flag() const noexcept { return _M_kind == _Kind::_Flag; } + constexpr bool flag() const noexcept { if (is_flag()) return _M_flag; return {}; } + + constexpr _Val(_LocView __loclist) noexcept + : _M_kind(_Kind::_LocList), _M_flag(), _M_addr(__loclist.size()), + _M_loclist(__loclist.data()) { assert(size() == __loclist.size() && "Overflow"); } + constexpr bool is_loclist() const noexcept { return _M_kind == _Kind::_LocList; } + constexpr _LocView loclist() const noexcept { + if (is_loclist()) return { _M_loclist, size() }; + return {}; + } + + constexpr _Val(_RngView __rnglist) noexcept + : _M_kind(_Kind::_RngList), _M_flag(), _M_addr(__rnglist.size()), + _M_rnglist(__rnglist.data()) { assert(size() == __rnglist.size() && "Overflow"); } + constexpr bool is_rnglist() const noexcept { return _M_kind == _Kind::_RngList; } + constexpr _RngView rnglist() const noexcept { + if (is_rnglist()) return { _M_rnglist, size() }; + return {}; + } + + constexpr _Val(_Str __str) noexcept + : _M_kind(_Kind::_Str), _M_flag(), _M_addr(__str.size()), + _M_str(__str.data()) { assert(size() == __str.size() && "Overflow"); } + constexpr bool is_str() const noexcept { return _M_kind == _Kind::_Str; } + constexpr _Str str() const noexcept { if (is_str()) return { _M_str, size() }; return {}; } + +private: + _Kind _M_kind : 4; + bool _M_flag : 1; + union { + struct {} _U_addr; + _Addr _M_addr; + }; + union { + struct {} _U_ptr; + std::uintptr_t _M_small; + std::uintmax_t *_M_large; + Data::pointer _M_data; + const SrcFile *_M_file; + const _Die *_M_ref; + _LocView::pointer _M_loclist; + _RngView::pointer _M_rnglist; + _Str::pointer _M_str; + }; +}; + +struct _Die { + _Tag _M_tag : std::numeric_limits<_Sbyte>::digits; + bool _M_children : 1; + std::uint32_t _M_sibling : std::numeric_limits<_Word>::digits - + std::numeric_limits<_Byte>::digits; + std::unordered_map<_At, _Val> _M_attrs; + + const _Val &attr(_At __at) const noexcept; + + static const _Die _S_null; +}; + +constexpr const _Die &_Val::ref() const noexcept { return is_ref() ? *_M_ref : _Die::_S_null; } + +class Base { +public: + constexpr Base(File &__file) noexcept : _M_file(__file) {} + constexpr void error(const char *__error) const noexcept; + constexpr const char *error() const noexcept; + constexpr void data(Data __data) noexcept { _M_data = __data; } + + File &_M_file; + Data _M_data; +}; + +class Abbrev : public Base { +public: + struct _Attr { + _At _M_at; + _Form _M_form; + std::int64_t _M_implicit_const; + }; + struct _Entry { + _Tag _M_tag : std::numeric_limits::digits; + bool _M_children : 1; + std::vector<_Attr> _M_attrs; + }; + + Abbrev(File &__file) noexcept : Base(__file) {} + std::unordered_map get(std::uint32_t __base) const noexcept; +}; + +class Addr : public Base { +public: + Addr(File &__file) noexcept : Base(__file) {} + _AddrView get(const _Die &__unit_entry) const noexcept; +}; + +class Frame : public Base { +public: + class Rule { + enum class _Kind : std::uint8_t { + _Undef, + _SameVal, + _Off, + _ValOff, + _Reg, + _RegOff, + _Expr, + _ValExpr, + _Arch, + }; + + constexpr Rule(_Kind __kind, Reg __reg = Reg::None) noexcept : Rule(__kind, __reg, {}) {} + constexpr Rule(_Kind __kind, _Addr __off) noexcept + : _M_kind(__kind), _M_reg(Reg::None), _M_off(__off) {} + constexpr Rule(_Kind __kind, Reg __reg, _Addr __off) noexcept + : _M_kind(__kind), _M_reg(__reg), _M_off(__off) {} + + public: + constexpr Rule() noexcept : Rule(_Kind::_Undef) {} + static constexpr Rule undef() noexcept { return {}; } + constexpr bool is_undef() const noexcept { return _M_kind == _Kind::_Undef; } + + static constexpr Rule same_val() noexcept { return { _Kind::_SameVal }; } + constexpr bool is_same_val() const noexcept { return _M_kind == _Kind::_SameVal; } + + static constexpr Rule off(std::int32_t __off) noexcept { return { _Kind::_Off, std::uint32_t(__off) }; } + constexpr bool is_off() const noexcept { return _M_kind == _Kind::_Off; } + + static constexpr Rule val_off(std::int32_t __off) noexcept { return { _Kind::_ValOff, std::uint32_t(__off) }; } + constexpr bool is_val_off() const noexcept { return _M_kind == _Kind::_ValOff; } + + static constexpr Rule reg(Reg __reg) noexcept { return { _Kind::_ValOff, __reg }; } + constexpr bool is_reg() const noexcept { return _M_kind == _Kind::_Reg; } + + static constexpr Rule reg_off(Reg __reg, std::int32_t __off) noexcept { + return { _Kind::_RegOff, __reg, std::uint32_t(__off) }; + } + constexpr bool is_reg_off() const noexcept { return _M_kind == _Kind::_RegOff; } + + static Rule expr(Frame &__frame, Data __expr) noexcept { + return { _Kind::_Expr, __frame.expr(__expr) }; + } + constexpr bool is_expr() const noexcept { return _M_kind == _Kind::_Expr; } + + static Rule val_expr(Frame &__frame, Data __expr) noexcept { + return { _Kind::_ValExpr, __frame.expr(__expr) }; + } + constexpr bool is_val_expr() const noexcept { return _M_kind == _Kind::_ValExpr; } + + constexpr bool is_arch() const noexcept { return _M_kind == _Kind::_Arch; } + + constexpr bool is_val() const noexcept { + return is_same_val() || is_reg() || is_reg_off() || is_val_off() || is_val_expr(); + } + constexpr bool is_addr() const noexcept { return is_off() || is_expr(); } + + constexpr Reg reg() const noexcept { return _M_reg; } + constexpr std::int32_t off() const noexcept { return _M_off; } + constexpr Data expr(const Frame &__frame) const noexcept { + if (is_expr() || is_val_expr()) + return __frame.expr(_M_off); + return {}; + } + + private: + _Kind _M_kind : 4; + Reg _M_reg : 4; + _Addr _M_off; + }; + class RuleSet { + public: + constexpr Rule &cfa() noexcept { return _M_cfa; } + constexpr Rule cfa() const noexcept { return _M_cfa; } + constexpr Rule &operator[](Reg __reg) noexcept { + assert(__reg >= Reg() && __reg < Reg::None); + return _M_rules[std::size_t(__reg)]; + } + constexpr Rule operator[](Reg __reg) const noexcept { + if (__reg >= Reg() && __reg < Reg::None) + return _M_rules[std::size_t(__reg)]; + return {}; + } + + static constexpr const RuleSet &undef() noexcept { return _S_undef; } + + private: + Rule _M_cfa; + Rule _M_rules[std::size_t(Reg::None)]; + + static const RuleSet _S_undef; + }; + +private: + using _Exprs = std::vector; + using _Rules = std::map<_Addr, RuleSet>; + + friend Rule; + _Exprs::size_type expr(Data __expr) noexcept { + auto __pos = _M_exprs.size(); + _M_exprs.push_back(__expr); + return __pos; + } + Data expr(_Exprs::size_type __pos) const noexcept { + if (__pos < _M_exprs.size()) + return _M_exprs[__pos]; + return {}; + } + +public: + Frame(File &__file) noexcept : Base(__file) {} + void __parse(); + const RuleSet &get(_Addr __addr) const noexcept; + +private: + _Exprs _M_exprs; + _Rules _M_rules; +}; + +class Info : public Base { + // Table 7.2: Unit header unit type encodings + enum { + DW_UT_compile = 0x01, + DW_UT_type = 0x02, + DW_UT_partial = 0x03, + DW_UT_skeleton = 0x04, + DW_UT_split_compile = 0x05, + DW_UT_split_type = 0x06, + }; + +public: + class _Scope { + public: + _Scope(const _Scope &__parent = _S_null, const _Die &__entry = _Die::_S_null) noexcept + : _M_parent(__parent), _M_entry(__entry) {} + void set(_Addr __low, _Addr __high, _Scope &__child) { + _M_children.insert({__low, { __high, __child } }); + } + void add(const _Die &__entry) { _M_entities.push_back(__entry); } + + explicit constexpr operator bool() const noexcept { return this != &_S_null; } + static constexpr const _Scope &null() noexcept { return _S_null; } + + constexpr const _Scope &parent() const noexcept { return _M_parent; } + constexpr bool is_unit() const noexcept { + return _M_entry._M_tag == _Tag::DW_TAG_compile_unit; + } + constexpr bool is_function() const noexcept { + return _M_entry._M_tag == _Tag::DW_TAG_inlined_subroutine || + _M_entry._M_tag == _Tag::DW_TAG_subprogram; + } + constexpr const _Scope &function() const noexcept { + return is_function() ? *this : _M_parent ? _M_parent.function() : null(); + } + constexpr const _Die &entry() const noexcept { return _M_entry; } + auto begin() const { return _M_entities.begin(); } + auto end() const { return _M_entities.end(); } + const _Scope &get(_Addr __addr) const noexcept; + void dump[[gnu::used]](int indent = 0) const; + + private: + const _Scope &_M_parent; + const _Die &_M_entry; + std::vector> _M_entities; + std::map<_Addr, std::pair<_Addr, const _Scope &>> _M_children; + + static const _Scope _S_null; + }; + + Info(File &__file) noexcept : Base(__file), _M_scopes{{}} {} + void __parse(Line &line, LocLists &loclists, RngLists &rnglists); + const _Scope &root() const noexcept { return _M_scopes.front(); } + +private: + std::vector<_Die> _M_entries; + std::deque<_Scope> _M_scopes; +}; + +class Line : public Base { + // Table 7.25: Line number standard opcode encodings + enum { + DW_LNS_extended = 0x00, + DW_LNS_copy = 0x01, + DW_LNS_advance_pc = 0x02, + DW_LNS_advance_line = 0x03, + DW_LNS_set_file = 0x04, + DW_LNS_set_column = 0x05, + DW_LNS_negate_stmt = 0x06, + DW_LNS_set_basic_block = 0x07, + DW_LNS_const_add_pc = 0x08, + DW_LNS_fixed_advance_pc = 0x09, + DW_LNS_set_prologue_end = 0x0a, + DW_LNS_set_epilogue_begin = 0x0b, + DW_LNS_set_isa = 0x0c, + }; + // Table 7.26: Line number extended opcode encodings + enum { + DW_LNE_end_sequence = 0x01, + DW_LNE_set_address = 0x02, + DW_LNE_set_discriminator = 0x04, + // Vendor Extensions + DW_LNE_lo_user = 0x80, + DW_LNE_hi_user = 0xff, + }; + // Table 7.27: Line number header entry format encodings + enum class _Lnct : std::uint8_t { + DW_LNCT_path = 0x1, + DW_LNCT_directory_index = 0x2, + DW_LNCT_timestamp = 0x3, + DW_LNCT_size = 0x4, + DW_LNCT_MD5 = 0x5, + }; + enum { + // Vendor Extensions + DW_LNCT_lo_user = 0x2000, + DW_LNCT_hi_user = 0x3fff, + }; + + using _EntryFormat = std::pair<_Lnct, _Form>; + struct _Entry { + _Str _M_path; + _Word _M_directory_index = _InvalidWord; + _Long _M_timestamp{}; + _Long _M_size{}; + Data _M_MD5; + }; + +public: + struct _Loc { + std::uint32_t _M_file; + std::uint32_t _M_line; + std::uint32_t _M_column; + std::uint32_t _M_address : 24; + bool _M_is_stmt : 1; + bool _M_basic_block : 1; + bool _M_end_sequence : 1; + bool _M_prologue_end : 1; + bool _M_epilogue_begin : 1; + std::uint8_t _M_isa : 1; + std::uint8_t _M_discriminator : 2; + }; + +private: + using _Files = std::vector; + using _Paths = std::unordered_map<_Path<3>, std::uint32_t>; + using _Locs = std::map; + +public: + Line(File &__file) noexcept : Base(__file) {} + std::vector __parse(std::uint8_t __address_size, const _Die &__unit_entry); + void shrink_to_fit() noexcept { _M_files.shrink_to_fit(); } + const SrcFile &get(_Files::size_type __pos) const noexcept; + _Files::size_type size() const noexcept { return _M_files.size(); } + constexpr const _Locs &locs() const noexcept { return _M_locs; } + +private: + _Files _M_files; + _Paths _M_paths; + _Locs _M_locs; +}; + +class LocLists : public Base { + // Table 7.10: Location list entry encoding values + enum { + DW_LLE_end_of_list = 0x00, + DW_LLE_base_addressx = 0x01, + DW_LLE_startx_endx = 0x02, + DW_LLE_startx_length = 0x03, + DW_LLE_offset_pair = 0x04, + DW_LLE_default_location = 0x05, + DW_LLE_base_address = 0x06, + DW_LLE_start_end = 0x07, + DW_LLE_start_length = 0x08, + }; + +public: + LocLists(File &__file) noexcept : Base(__file) {} + std::vector> __parse(const _Die &__unit_entry); + void shrink_to_fit() noexcept { _M_locs.shrink_to_fit(); } + _LocView get(std::pair __indices) const noexcept; + +private: + std::vector<_Loc> _M_locs; +}; + +class RngLists : public Base { + // Table 7.30: Range list entry encoding values + enum { + DW_RLE_end_of_list = 0x00, + DW_RLE_base_addressx = 0x01, + DW_RLE_startx_endx = 0x02, + DW_RLE_startx_length = 0x03, + DW_RLE_offset_pair = 0x04, + DW_RLE_base_address = 0x05, + DW_RLE_start_end = 0x06, + DW_RLE_start_length = 0x07, + }; + +public: + RngLists(File &__file) noexcept : Base(__file) {} + std::vector> __parse(const _Die &__unit_entry); + void shrink_to_fit() noexcept { _M_rngs.shrink_to_fit(); } + _RngView get(std::pair __indices) const noexcept; + +private: + std::vector<_Rng> _M_rngs; +}; + +class Str : public Base { +public: + Str(File &__file) noexcept : Base(__file) {} + _Str get(std::uint32_t __offset) const noexcept; +}; + +class StrOffsets : public Base { +public: + StrOffsets(File &__file) noexcept : Base(__file) {} + _OffView get(const _Die &__unit_entry) const noexcept; +}; +} + +class File : public elf::File { +public: + explicit File(Data __data) + : elf::File(__data), _M_error(nullptr), _M_abbrev(*this), + _M_addr(*this), _M_frame(*this), _M_info(*this), _M_line(*this), + _M_line_str(*this), _M_line_str_offsets(*this), _M_loclists(*this), + _M_rnglists(*this), _M_str(*this), _M_str_offsets(*this) {} + + void validate(const Header &__expected); + + constexpr void error(const char *__error) const noexcept { + if (!_M_error) { + _M_error = __error; + } + } + constexpr const char *error() const noexcept { return _M_error; }; + + constexpr const section::Abbrev &abbrev() const noexcept { return _M_abbrev; } + constexpr const section::Addr &addr() const noexcept { return _M_addr; } + constexpr const section::Frame &frame() const noexcept { return _M_frame; } + constexpr const section::Info &info() const noexcept { return _M_info; } + constexpr const section::Line &line() const noexcept { return _M_line; } + constexpr const section::Str &line_str() const noexcept { return _M_line_str; } + constexpr const section::StrOffsets &line_str_offsets() const noexcept { return _M_line_str_offsets; } + constexpr const section::LocLists &loclists() const noexcept { return _M_loclists; } + constexpr const section::RngLists &rnglists() const noexcept { return _M_rnglists; } + constexpr const section::Str &str() const noexcept { return _M_str; } + constexpr const section::StrOffsets &str_offsets() const noexcept { return _M_str_offsets; } + +private: + mutable const char *_M_error; + section::Abbrev _M_abbrev; + section::Addr _M_addr; + section::Frame _M_frame; + section::Info _M_info; + section::Line _M_line; + section::Str _M_line_str; + section::StrOffsets _M_line_str_offsets; + section::LocLists _M_loclists; + section::RngLists _M_rnglists; + section::Str _M_str; + section::StrOffsets _M_str_offsets; +}; + +constexpr void section::Base::error(const char *__error) const noexcept { _M_file.error(__error); } +constexpr const char *section::Base::error() const noexcept { return _M_file.error(); } +} // end namespace elf +} // end namespace debuginfo + +#endif diff --git a/gui/qt/debugger/disasm.cpp b/gui/qt/debugger/disasm.cpp index e28dd2fac..4357ee7af 100644 --- a/gui/qt/debugger/disasm.cpp +++ b/gui/qt/debugger/disasm.cpp @@ -146,7 +146,7 @@ static bool disasmPut([[maybe_unused]] struct zdis_ctx *ctx, enum zdis_put kind, *disasm.cur += strA(static_cast(val)); break; case ZDIS_PUT_CHAR: - *disasm.cur += static_cast(val); + *disasm.cur += char(val); break; case ZDIS_PUT_MNE_SEP: disasm.instr.operands = disasm.tab ? '\t' : ' '; @@ -170,7 +170,7 @@ void disasmInit() { void disasmGet(bool useCpuMode) { disasm.ctx.zdis_lowercase = !disasm.uppercase; disasm.ctx.zdis_implicit = !disasm.implicit; - disasm.ctx.zdis_end_addr = static_cast(disasm.base); + disasm.ctx.zdis_end_addr = uint32_t(disasm.base); disasm.ctx.zdis_adl = useCpuMode ? cpu.ADL : disasm.adl; disasm.cur = &disasm.instr.opcode; @@ -186,7 +186,7 @@ void disasmGet(bool useCpuMode) { zdis_put_inst(&disasm.ctx); - if (disasm.highlight.pc && cpu.registers.PC != static_cast(disasm.base)) { + if (disasm.highlight.pc && cpu.registers.PC != uint32_t(disasm.base)) { static char tmpbuf[20]; size_t size = cpu.registers.PC - static_cast(disasm.base); disasm.instr.data = disasm.instr.data.substr(0, size * 2); @@ -211,12 +211,12 @@ void disasmGet(bool useCpuMode) { disasm.instr.operands += ','; } } while (size); - disasm.base = static_cast(disasm.ctx.zdis_start_addr); - disasm.next = static_cast(cpu.registers.PC); + disasm.base = int32_t(disasm.ctx.zdis_start_addr); + disasm.next = int32_t(cpu.registers.PC); disasm.highlight.pc = false; } else { - disasm.base = static_cast(disasm.ctx.zdis_start_addr); - disasm.next = static_cast(disasm.ctx.zdis_end_addr); + disasm.base = int32_t(disasm.ctx.zdis_start_addr); + disasm.next = int32_t(disasm.ctx.zdis_end_addr); } disasm.instr.size = static_cast(disasm.next) - static_cast(disasm.base); diff --git a/gui/qt/debugger/sourceswidget.cpp b/gui/qt/debugger/sourceswidget.cpp new file mode 100644 index 000000000..c7975eaf2 --- /dev/null +++ b/gui/qt/debugger/sourceswidget.cpp @@ -0,0 +1,1518 @@ +#include "sourceswidget.h" + +#include "cdebughighlighter.h" +#include "debuginfo.h" +#include "../mainwindow.h" +#include "../utils.h" +#include "../../core/mem.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +struct SourcesWidget::Context { + const Function *local; + const Source *global; + + typedef Scope value_type; + typedef const Scope &reference; + typedef const Scope *pointer; + typedef qint8 difference_type; + typedef quint8 size_type; + struct const_iterator { + typedef Scope value_type; + typedef const Scope &reference; + typedef const Scope *pointer; + typedef qint8 difference_type; + typedef std::random_access_iterator_tag iterator_category; + + const_iterator(const Context *context = nullptr, difference_type i = 0) : context(context), i(i) {} + reference operator*() const { switch (i) { case 0: return *context->local; default: return *context->global; } } + pointer operator->() const { return &**this; } + reference operator[](difference_type n) { return *(*this + n); } + const_iterator &operator++() { ++i; return *this; } + const_iterator operator++(int) { const_iterator temp(*this); ++*this; return temp; } + const_iterator &operator--() { ++i; return *this; } + const_iterator operator--(int) { const_iterator temp(*this); ++*this; return temp; } + const_iterator &operator+=(difference_type n) { i += n; return *this; } + friend const_iterator operator+(const_iterator iter, difference_type n) { return iter += n; } + friend const_iterator operator+(difference_type n, const_iterator iter) { return iter += n; } + const_iterator &operator-=(difference_type n) { i -= n; return *this; } + friend const_iterator operator-(const_iterator iter, difference_type n) { return iter -= n; } + difference_type operator-(const_iterator iter) const { return i - iter.i; } + bool operator==(const_iterator iter) const { return context == iter.context && i == iter.i; } + bool operator!=(const_iterator iter) const { return !(*this == iter); } + bool operator<(const_iterator iter) const { return i < iter.i; } + bool operator>(const_iterator iter) const { return i > iter.i; } + bool operator<=(const_iterator iter) const { return i <= iter.i; } + bool operator>=(const_iterator iter) const { return i >= iter.i; } + + private: + const Context *context; + difference_type i; + }; + typedef const_iterator iterator; + + size_type size() const { return !!local + !!global; } + static size_type max_size() { return 2; } + const_iterator cbegin() const { return const_iterator(this, max_size() - size()); } + const_iterator cend() const { return const_iterator(this, max_size()); } + iterator begin() const { return cbegin(); } + iterator end() const { return cend(); } +}; + + +#ifdef QT_DEBUG +#include + +QDebug operator<<(QDebug debug, const SourcesWidget::Line &line) { + QDebugStateSaver saver(debug); + debug.nospace().noquote() << "Line(num=" << line.num << ", addr=" << QString::number(line.addr, 16) << ')'; + return debug.maybeSpace(); +} +QDebug operator<<(QDebug debug, const SourcesWidget::SymbolKind &kind) { + QDebugStateSaver saver(debug); + switch (kind) { + case SourcesWidget::SymbolKind::Function: + debug << "SymbolKind::Function"; + break; + case SourcesWidget::SymbolKind::StaticFunction: + debug << "SymbolKind::StaticFunction"; + break; + case SourcesWidget::SymbolKind::StructField: + debug << "SymbolKind::StructField"; + break; + case SourcesWidget::SymbolKind::UnionField: + debug << "SymbolKind::UnionField"; + break; + case SourcesWidget::SymbolKind::BitField: + debug << "SymbolKind::BitField"; + break; + case SourcesWidget::SymbolKind::StackSlot: + debug << "SymbolKind::StackSlot"; + break; + case SourcesWidget::SymbolKind::Constant: + debug << "SymbolKind::Constant"; + break; + case SourcesWidget::SymbolKind::Uninitialized: + debug << "SymbolKind::Uninitialized"; + break; + case SourcesWidget::SymbolKind::Variable: + debug << "SymbolKind::Variable"; + break; + default: + debug.nospace() << "SymbolKind(" << quint8(kind) << ')'; + break; + } + return debug.maybeSpace(); +} +QDebug operator<<(QDebug debug, const SourcesWidget::Context &context) { + QDebugStateSaver saver(debug); + debug.nospace() << "Context(local=" << context.local << ", global=" << context.global << ')'; + return debug.maybeSpace(); +} +QDebug operator<<(QDebug debug, const SourcesWidget::Symbol &symbol) { + QDebugStateSaver saver(debug); + debug.nospace() << "Symbol(name=" << symbol.name << ", alias=" << symbol.alias << ", value=" << QString::number(symbol.value, symbol.kind == SourcesWidget::SymbolKind::StackSlot ? 10 : 16) << ", type=" << symbol.type << ", tag=" << symbol.tag << ", kind=" << symbol.kind << ", length=" << symbol.length << ", dims=" << symbol.dims << ')'; + return debug.maybeSpace(); +} +QDebug operator<<(QDebug debug, const SourcesWidget::Record &record) { + QDebugStateSaver saver(debug); + debug << "Record(symbolList=" << record.symbolList << ", symbolMap=" << record.symbolMap << ", recordList=" << record.recordList << ", recordMap=" << record.recordMap << ", size=" << record.size << ')'; + return debug.maybeSpace(); +} +QDebug operator<<(QDebug debug, const SourcesWidget::Function &function) { + QDebugStateSaver saver(debug); + debug << "Function(symbolList=" << function.symbolList << ", symbolMap=" << function.symbolMap << ", recordList=" << function.recordList << ", recordMap=" << function.recordMap << ", lines=" << function.lines << ')'; + return debug.maybeSpace(); +} +QDebug operator<<(QDebug debug, const SourcesWidget::Source &source) { + QDebugStateSaver saver(debug); + debug << "Source(symbolList=" << source.symbolList << ", symbolMap=" << source.symbolMap << ", recordList=" << source.recordList << ", recordMap=" << source.recordMap << ", functionList=" << source.functionList << ", functionMap=" << source.functionMap << ')'; + return debug.maybeSpace(); +} +#endif + +class SourcesWidget::VariableModel : public QAbstractItemModel { + void deleteVariable(int id); +protected: + explicit VariableModel(SourcesWidget *parent) : QAbstractItemModel(parent) {} +#define using(const, field) const decltype(m_##field) &field() const { \ + return static_cast(QObject::parent())->m_##field; \ + } + using(const, debugFile) + using(const, stringList) + using(const, stringMap) +#undef using + struct Variable { + enum class Flag : quint8 { + Deleted = 1 << 0, + Changed = 1 << 1, + Visited = 1 << 2, + }; + Q_DECLARE_FLAGS(Flags, Flag) + + int parent, parentIndex, childIndex; + QList children; + + Symbol symbol; + qint32 base; + Context context; + + std::string value; + + QString data[2]; + Flags flags; + }; + bool m_visited = false; + int m_freeVariable = -1; + QVector m_variables; + QStringList m_topLevelData[2]; + QList> m_topLevelChildren; + static quint32 sizeOfSymbol(const Symbol &symbol, Context context); + QString variableTypeToString(const Variable &variable) const; + QString variableValueToString(const Variable &variable) const; + int createVariable(int parent, int parentIndex, int childIndex, const debuginfo::dwarf::section::_Die &entry); + int createVariable(int parent, int parentIndex, int childIndex, const Symbol &symbol, Context context, qint32 base = 0, const QString &name = {}); + void createVariable(const QModelIndex &parent, const Symbol &symbol, qint32 base = 0, const QString &name = {}); + void removeTopLevels(int first, int last); + constexpr static quintptr s_topLevelId = ~quintptr(); + constexpr static int s_topLevelParent = int(s_topLevelId); +private: + const Variable &updateVariable(int id); +public: + virtual void update(); + QModelIndex parent(const QModelIndex &child) const override; + QModelIndex index(int row, int column, + const QModelIndex &parent = {}) const override; + int childCount(const QModelIndex &parent = {}) const; + bool hasChildren(const QModelIndex &parent = {}) const override; + int rowCount(const QModelIndex &parent = {}) const override; + int columnCount(const QModelIndex &parent = {}) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool canFetchMore(const QModelIndex &parent) const override; + void fetchMore(const QModelIndex &parent) override; +}; +class SourcesWidget::GlobalModel : public VariableModel { + static int commonPrefixLength(const QStringList &paths); +public: + explicit GlobalModel(SourcesWidget *parent) : VariableModel(parent) {} + void init(const QStringList &paths); +}; +class SourcesWidget::StackModel : public VariableModel { + static constexpr quint32 undefValue = ~0u; + + using RegsBase = std::array; + struct Regs : RegsBase { + quint32 &operator[](debuginfo::dwarf::Reg reg) { + return RegsBase::operator[](std::size_t(reg)); + } + const quint32 &operator[](debuginfo::dwarf::Reg reg) const { + return RegsBase::operator[](std::size_t(reg)); + } + }; + struct StackEntry { + quint32 cfa; + Regs regs; + const debuginfo::dwarf::section::Info::_Scope *scope; + + bool same(const StackEntry &other) const { + return cfa == other.cfa && + regs[debuginfo::dwarf::Reg::PC] == other.regs[debuginfo::dwarf::Reg::PC] && + scope == other.scope; + } + }; + QVector m_stack; + + static void computeRule(const StackEntry &state, quint32 &value, + const debuginfo::dwarf::section::Frame::Rule &rule); + +public: + explicit StackModel(SourcesWidget *parent) : VariableModel(parent) {} + void update() override; + void init(); +}; + +class SourcesWidget::DebugFile : protected QFile, public debuginfo::dwarf::File { +private: + debuginfo::Data mapData() { + if (open(ReadOnly)) { + if (auto ptr = reinterpret_cast(map(0, size()))) { + return { ptr, debuginfo::Data::size_type(size()) }; + } + } + return {}; + } + +public: + DebugFile(const QString &path) : QFile(path), debuginfo::dwarf::File(mapData()) {} + + using debuginfo::dwarf::File::error; +}; + +namespace { +enum class DebugTokenKind { + Eof, + Alias, + Base, + BegFunc, + BegRec, + Class, + Debug, + Define, + Dim, + End, + Endef, + EndFunc, + EndRec, + File, + Length, + Line, + Reg, + Size, + Space, + Strings, + Tag, + Type, + Value, +}; + +class DbgFile : public QFile { +public: + bool success; + DbgFile(QString name) : QFile(name), success(open(QIODevice::ReadOnly)) {} + void setErrorString(const QString &errorString) { + if (success) { + success = false; + QFile::setErrorString(errorString); + } + } + quint32 readULEB128() { + if (!success) { + return 0; + } + quint32 result = 0, shift = 0; + char next; + do { + if (!getChar(&next)) { + success = false; + return 0; + } + result |= (next & 0x7F) << shift; + if (shift >= 25 && next >> (32 - shift)) { + setErrorString(tr("Shift overflow")); + return 0; + } + shift += 7; + } while (next & 0x80); + return result; + } + qint32 readSLEB128() { + if (!success) { + return 0; + } + qint32 result = 0, shift = 0; + char next; + do { + if (!getChar(&next)) { + success = false; + return 0; + } + result |= (next & 0x7F) << shift; + if (shift >= 25 && (next ^ ((qint8)next << 1 >> 7 & 0x7F)) >> (32 - shift)) { + setErrorString(tr("Shift overflow")); + return 0; + } + shift += 7; + } while (next & 0x80); + if (shift >= 32) { + return result; + } + shift = 32 - shift; + return result << shift >> shift; + } + QString readString() { + if (!success) { + return QString(); + } + quint32 length = readULEB128(); + if (!length) { + return QString(""); + } + QByteArray result = read(length); + if (result.isEmpty()) { + setErrorString(tr("Invalid string")); + return QString(); + } + return QString::fromLocal8Bit(result); + } +}; +} // end anonymous namespace + +SourcesWidget::SourcesWidget(QWidget *parent) : QWidget(parent) { + QPushButton *button = new QPushButton(tr("Select Debug File")); + connect(button, &QPushButton::clicked, this, &SourcesWidget::selectDebugFile); + m_tabs = new QTabWidget; + m_tabs->setTabPosition(QTabWidget::South); + m_global = new QTreeView; + m_globalModel = new GlobalModel(this); + m_global->setModel(m_globalModel); + m_stack = new QTreeView; + m_stackModel = new StackModel(this); +#ifdef QT_DEBUG + new QAbstractItemModelTester(m_globalModel, + QAbstractItemModelTester::FailureReportingMode::Fatal, + this); + new QAbstractItemModelTester(m_stackModel, + QAbstractItemModelTester::FailureReportingMode::Fatal, + this); +#endif + m_stack->setModel(m_stackModel); + QSplitter *treeSplitter = new QSplitter(Qt::Vertical); + treeSplitter->addWidget(m_global); + treeSplitter->addWidget(m_stack); + treeSplitter->setSizes({INT_MAX, INT_MAX}); + QSplitter *splitter = new QSplitter(Qt::Horizontal); + splitter->addWidget(m_tabs); + splitter->addWidget(treeSplitter); + splitter->setSizes({INT_MAX, INT_MAX}); + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget(button); + layout->addWidget(splitter); + setLayout(layout); + m_literalFormat.setForeground(QColor::fromRgb(0x008000)); + m_escapeFormat.setForeground(QColor::fromRgb(0x008000)); + m_escapeFormat.setFontWeight(QFont::Bold); + m_preprocessorFormat.setForeground(QColor::fromRgb(0x003F3F)); + m_preprocessorFormat.setFontWeight(QFont::Bold); + m_commentFormat.setForeground(QColor::fromRgb(0x808080)); + m_keywordFormat.setForeground(QColor::fromRgb(0x3F3FFF)); + m_keywordFormat.setFontWeight(QFont::Bold); + m_operatorFormat.setFontWeight(QFont::Bold); + m_errorFormat.setBackground(QColor::fromRgb(0xFF3F3F)); +} + +SourcesWidget::~SourcesWidget() = default; + +void SourcesWidget::selectDebugFile() { + QString debugName = QFileDialog::getOpenFileName(this, tr("Open Debug File"), QString(), tr("Debug File (*.debug)")); + if (debugName.isNull()) { + return; + } + + auto debugFile = std::make_unique(debugName); + debugFile->validate(debuginfo::elf::File::Header::ez80); + + if (const char *error = debugFile->error()) { + QMessageBox dialog(QMessageBox::Critical, + findParent(this)->MSG_ERROR, + tr("Error loading debug file:"), QMessageBox::Ok, this); + dialog.setInformativeText(tr(error)); + dialog.exec(); + } else { + for (int i = 0; i != m_tabs->count(); ++i) { + m_tabs->widget(i)->deleteLater(); + } + m_tabs->clear(); + + QStringList changedSourcePaths; + for (std::size_t i = 0; i != debugFile->line().size(); ++i) { + auto source = debugFile->line().get(i); + QString sourcePath; + sourcePath.reserve(std::accumulate(source.path().begin(), source.path().end(), -1, + [](int length, const auto &str) { + return length + str.rtrim('\0').size() + 1; + })); + std::for_each(source.path().canon().begin(), source.path().canon().end(), + [&sourcePath](char c) { sourcePath += c; }); + QFile sourceFile(sourcePath); + if (sourceFile.open(QIODevice::ReadOnly)) { + QByteArray sourceContents = sourceFile.readAll(); + if (source.md5() && QCryptographicHash::hash(sourceContents, QCryptographicHash::Md5) != + QByteArray::fromRawData(reinterpret_cast(source.md5()), + QCryptographicHash::hashLength(QCryptographicHash::Md5))) { + changedSourcePaths << sourcePath; + } + QPlainTextEdit *sourceView = new QPlainTextEdit(sourceContents); + sourceView->setObjectName(QStringLiteral("sourceView%0").arg(i)); + sourceView->setReadOnly(true); + sourceView->setCenterOnScroll(true); + sourceView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(sourceView, &QWidget::customContextMenuRequested, + this, &SourcesWidget::sourceContextMenu); + m_tabs->addTab(sourceView, QFileInfo(sourceFile).fileName()); + new CDebugHighlighter(this, sourceView); + } + } + if (!changedSourcePaths.empty()) { + QMessageBox dialog(QMessageBox::Warning, findParent(this)->MSG_WARNING, + tr("Some source files have changed since compilation:"), QMessageBox::Ok, + this); + dialog.setInformativeText(changedSourcePaths.join('\n')); + dialog.exec(); + } + //m_stringList = std::move(stringList); + //m_stringMap = std::move(stringMap); + //m_globalModel->init(paths); + //m_stackModel->init(); + + m_debugFile = std::move(debugFile); + } +} + +auto SourcesWidget::getLocInfo(quint32 addr) const -> LocInfo { + if (!m_debugFile) { + return {}; + } + const auto &locs = m_debugFile->line().locs(); + auto after = locs.upper_bound(addr); + if (after == locs.begin() || after == locs.end()) { + return {}; + } + auto cur = std::prev(after); + if (cur->second._M_end_sequence) { + return {}; + } + return { after->second._M_address, cur->second._M_file, cur->second._M_line, cur->second._M_column }; +} + +void SourcesWidget::updateFormats() { + for (QSyntaxHighlighter *highlighter : + m_tabs->findChildren()) { + highlighter->rehighlight(); + } +} + +void SourcesWidget::updatePC() { + m_globalModel->update(); + m_stackModel->update(); + for (auto sourceView : m_tabs->findChildren()) { + sourceView->setExtraSelections({}); + } + if (!m_debugFile) { + return; + } + const auto &locs = m_debugFile->line().locs(); + auto it = locs.upper_bound(cpu.registers.PC); + if (it == locs.begin()) { + return; + } + if (!(--it)->second._M_line) { + return; + } + QPlainTextEdit *sourceView = m_tabs->findChild( + QStringLiteral("sourceView%0").arg(it->second._M_file)); + QTextBlock block = sourceView->document()->findBlockByNumber(it->second._M_line - 1); + if (!block.isValid()) { + return; + } + QTextEdit::ExtraSelection pcSelection; + pcSelection.cursor = QTextCursor(block); + if (it->second._M_column) { + pcSelection.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, + it->second._M_column - 1); + } else if (pcSelection.cursor.movePosition(QTextCursor::EndOfWord)) { + pcSelection.cursor.movePosition(QTextCursor::StartOfBlock); + } else { + pcSelection.cursor.movePosition(QTextCursor::NextWord); + } + sourceView->setTextCursor(pcSelection.cursor); + if (it->second._M_column) { + pcSelection.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + } else { + pcSelection.cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); + pcSelection.cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); + pcSelection.cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + } + pcSelection.format.setBackground(QColor::fromRgb(0xFFFF99)); + sourceView->setExtraSelections({pcSelection}); + sourceView->centerCursor(); + m_tabs->setCurrentWidget(sourceView); +} + +void SourcesWidget::updateAddr(quint32 addr, unsigned type, bool state) { + (void)type; // TODO: implement + if (!m_debugFile) { + return; + } + const auto &locs = m_debugFile->line().locs(); + auto it = locs.upper_bound(addr); + if (it == locs.begin()) { + return; + } + if (!(--it)->second._M_line) { + return; + } + auto sourceView = m_tabs->findChild( + QStringLiteral("sourceView%0").arg(it->second._M_file)); + QTextBlock block = sourceView->document()->findBlockByNumber(it->second._M_line - 1); + if (!block.isValid()) { + return; + } + // TODO: This is obviously incorrect, but who cares for now. + QTextBlockFormat format; + if (state) { + format.setBackground(QColor::fromRgb(0xFF6050)); + } + QTextCursor(block).setBlockFormat(format); +} + +void SourcesWidget::sourceContextMenu(const QPoint &pos) { + if (!m_debugFile) { + return; + } + QPlainTextEdit *sourceView = static_cast(m_tabs->currentWidget()); + + QMenu menu; + QAction runUntilAction(tr("Run Until")); + menu.addAction(&runUntilAction); + menu.addSeparator(); + QAction toggleBreakAction(tr("Toggle Breakpoint")); + menu.addAction(&toggleBreakAction); + + QAction *action = menu.exec(sourceView->mapToGlobal(pos)); + + const auto &source = m_debugFile->line() + .get(sourceView->objectName().mid(QStringLiteral("sourceView").count()).toInt()); + QTextCursor cursor = sourceView->cursorForPosition(pos); + auto it = source.lines().upper_bound({ cursor.blockNumber() + 1, cursor.columnNumber() }); + if (it == source.lines().begin()) { + return; + } + --it; + + if (action == &runUntilAction) { + emit runUntilTriggered(it->second); + } else if (action == &toggleBreakAction) { + emit breakToggled(it->second); + } +} + +void SourcesWidget::setSourceFont(const QFont &font) { + for (auto *format : { &m_defaultFormat, &m_operatorFormat, &m_literalFormat, &m_escapeFormat, + &m_preprocessorFormat, &m_commentFormat, &m_keywordFormat, &m_identifierFormat, &m_errorFormat }) { + QFont::Weight weight = QFont::Weight(format->fontWeight()); + format->setFont(font); + format->setFontWeight(weight); + } + updateFormats(); + for (auto *child : { m_global, m_stack }) { + child->setFont(font); + } +} + +quint32 SourcesWidget::VariableModel::sizeOfSymbol(const Symbol &symbol, Context context) { + quint32 size = 1, type = symbol.type; + if (type & 0xE0000000u) { + type |= 0xFFFFFFE0u; + } + if ((type & 0x1C) == 0xC) { + type -= 10; + } + for (int dim = 0; ; ++dim) { + switch (type >> 5 & 7) { + case 0: + switch (type) { + case 1: + case 2: + case 3: + case 4: + case 5: + return size * (type - 1); + case 6: + return size * 4; + case 8: + for (auto &scope : context) { + auto record = scope.recordMap.find(symbol.tag); + if (record != scope.recordMap.end()) { + return size * scope.recordList.at(*record).size; + } + } + [[gnu::fallthrough]]; + default: + return 0; + } + case 1: + case 6: + return size * 3; + case 2: + default: + return 0; + case 3: + size *= symbol.dims.value(dim); + break; + } + type = (type >> 3 & ~0x1F) | (type & 0x1F); + } + return 0; +} +QString SourcesWidget::VariableModel::variableTypeToString(const Variable &variable) const { + QString name = stringList().value(variable.symbol.name - 1); + quint32 type = variable.symbol.type; + QString prefix, tag = "struct ", base, suffix = name; + if (type & 0xE0000000u) { + type |= 0xFFFFFFE0u; + } + if ((type & 0x1C) == 0xC) { + base = "unsigned "; + type -= 10; + } + switch (type & 0x1F) { + case 1: base = "void"; break; + case 2: base += "char"; break; + case 3: base += "short"; break; + case 4: base += "int"; break; + case 5: base += "long"; break; + case 6: base = "float"; break; + case 8: + for (auto &scope : variable.context) { + auto record = scope.recordMap.find(variable.symbol.tag); + if (record == scope.recordMap.end()) { + continue; + } + bool first = true; + for (auto &symbol : scope.recordList.at(*record).symbolList) { + switch (symbol.kind) { + case SymbolKind::BitField: + if (!first && symbol.length) { + if (!symbol.value) { + [[gnu::fallthrough]]; + case SymbolKind::UnionField: + tag = "union "; + } + [[gnu::fallthrough]]; + case SymbolKind::StructField: + break; + } + if (symbol.length) { + first = false; + } + [[gnu::fallthrough]]; + default: + continue; + } + break; + } + break; + } + base = tag + stringList().value(variable.symbol.tag - 1); + break; + default: base = ""; break; + } + base += ' '; + int dim = 0; + for (type >>= 5; ; type >>= 3) { + switch (type & 7) { + default: + base += ""; + [[gnu::fallthrough]]; + case 0: + return prefix + base + suffix; + case 1: + suffix = '*' + prefix + suffix; + prefix = ""; + break; + case 2: + if (suffix.startsWith('*')) { + suffix = '(' + prefix + suffix + ')'; + prefix = ""; + } + suffix += "(...)"; + break; + case 3: + if (suffix.startsWith('*')) { + suffix = '(' + prefix + suffix + ')'; + prefix = ""; + } + suffix += '[' + QString::number(variable.symbol.dims.value(dim++)) + ']'; + break; + case 6: + suffix = '*' + prefix + suffix; + prefix = "const "; + break; + } + } +} +QString SourcesWidget::VariableModel::variableValueToString(const Variable &variable) const { + qint32 addr = variable.base + variable.symbol.value; + bool isFloat = false, isBitField = false, isSigned = true; + quint32 type = variable.symbol.type; + if (type & 0xE0000000u) { + return {}; + } + switch (type >> 5 & 7) { + case 0: + if (type == 6) { + isFloat = true; + type = 5; + } else if (type == 16) { + isBitField = true; + isSigned = false; // We have no way of knowing if it is signed :( + type = 4; + addr = variable.base + 3*(variable.symbol.value / 24); + } else if ((type & ~3) == 0xC) { + isSigned = false; + type -= 10; + } + if (type >= 2 && type <= 5) { + union { + quint32 u; + qint32 s; + float f; + } value = { 0 }; + for (unsigned offset = 0; offset != type - 1; ++offset) { + value.u |= mem_peek_byte(addr++) << 8*offset; + } + if (isFloat) { + return QString::number(value.f); + } + if (isBitField) { + int shift = variable.symbol.value % 24, extend = 32 - variable.symbol.length; + if (shift < 0) { + shift += 24; + } + value.u <<= 8 + shift; + if (isSigned) { + return QString::number(value.s >> extend); + } + return QString::number(value.u >> unsigned(extend)); + } + if (isSigned) { + if (type == 2) { + QString valueStr; + switch (value.u) { + case '\'': valueStr = "'\\''"; break; + case '\"': valueStr = "'\\\"'"; break; + case '\\': valueStr = "'\\\\'"; break; + case '\a': valueStr = "'\\a'"; break; + case '\b': valueStr = "'\\b'"; break; + case '\e': valueStr = "'\\e'"; break; + case '\f': valueStr = "'\\f'"; break; + case '\n': valueStr = "'\\n'"; break; + case '\r': valueStr = "'\\r'"; break; + case '\t': valueStr = "'\\t'"; break; + case '\v': valueStr = "'\\v'"; break; + default: + valueStr += '\''; + if (value.u >= ' ' && value.u <= '~') { + valueStr += char(value.u); + } else { + valueStr += '\\' + QString::number(value.u, 8) + .rightJustified(3, '0'); + } + valueStr += '\''; + break; + } + return valueStr; + } + int extend = 32 - 8*(type - 1); + return QString::number(value.s << extend >> extend); + } + return QString::number(value.u); + } + [[gnu::fallthrough]]; + default: + return {}; + case 1: + case 6: + if (quint32 value = mem_peek_long(addr)) { + //int sourceIndex, functionIndex; + //if (lookupAddr(value, &sourceIndex, &functionIndex)) { + // return '&' + stringList().value(sources().at(sourceIndex).functionList.at(functionIndex).name - 1); + //} else { + return "0x" + QString::number(value, 16).rightJustified(6, '0'); + //} + } + return "NULL"; + } +} + +int SourcesWidget::VariableModel::createVariable(int parent, int parentIndex, int childIndex, const debuginfo::dwarf::section::_Die &entry) { + int id = m_freeVariable; + if (id == -1) { + id = m_variables.count(); + m_variables.resize(id + 1); + } else { + m_freeVariable = m_variables.at(id).parent; + } + auto &variable = m_variables[id]; + variable.parent = parent; + variable.parentIndex = parentIndex; + variable.childIndex = childIndex; + variable.flags = {}; + variable.flags.setFlag(Variable::Flag::Visited, m_visited); + const auto name = entry.attr(debuginfo::dwarf::_At::DW_AT_name); + variable.data[0] = QString::fromUtf8(name.str().data(), name.str().rtrim('\0').size()); + variable.data[1] = ""; + return id; +} + +int SourcesWidget::VariableModel::createVariable(int parent, int parentIndex, int childIndex, + const Symbol &symbol, Context context, qint32 base, + const QString &name) { + int id = m_freeVariable; + if (id == -1) { + id = m_variables.count(); + m_variables.resize(id + 1); + } else { + m_freeVariable = m_variables.at(id).parent; + } + auto &variable = m_variables[id]; + variable.parent = parent; + variable.parentIndex = parentIndex; + variable.childIndex = childIndex; + variable.symbol = symbol; + variable.base = base; + variable.context = context; + variable.flags = {}; + variable.flags.setFlag(Variable::Flag::Visited, m_visited); + variable.data[0] = name.isNull() ? variableTypeToString(variable) : name; + variable.data[1] = variableValueToString(variable); + qDebug().noquote() << '\t' << stringList().value(m_variables.value(variable.parent).symbol.name - 1) << "→" << stringList().value(variable.symbol.name - 1) << variable.symbol << QString::number(variable.base, 16); + return id; +} +void SourcesWidget::VariableModel::createVariable(const QModelIndex &parent, const Symbol &symbol, + qint32 base, const QString &name) { + Q_ASSERT(parent.isValid() && parent.model() == this); + auto &variable = m_variables.at(parent.internalId()); + int child = createVariable(parent.internalId(), parent.row(), variable.children.count(), + symbol, variable.context, base, name); + m_variables[parent.internalId()].children << child; +} +void SourcesWidget::VariableModel::deleteVariable(int id) { + auto &variable = m_variables[id]; + for (int child : variable.children) { + deleteVariable(child); + } + variable.parent = m_freeVariable; + variable.children.clear(); + variable.flags.setFlag(Variable::Flag::Deleted); + m_freeVariable = id; +} +void SourcesWidget::VariableModel::removeTopLevels(int first, int last) { + if (first > last) { + return; + } + beginRemoveRows(QModelIndex(), first, last); + for (int topLevel = first; topLevel <= last; ++topLevel) { + for (int id : m_topLevelChildren.at(topLevel)) { + deleteVariable(id); + } + } + for (auto &topLevelData : m_topLevelData) + topLevelData.erase(topLevelData.begin() + first, topLevelData.begin() + last + 1); + m_topLevelChildren.erase(m_topLevelChildren.begin() + first, + m_topLevelChildren.begin() + last + 1); + endRemoveRows(); +} +auto SourcesWidget::VariableModel::updateVariable(int id) -> const Variable & { + auto &variable = m_variables[id]; + if (variable.flags.testFlag(Variable::Flag::Deleted) || + variable.flags.testFlag(Variable::Flag::Visited) == m_visited) { + return variable; + } + if (variable.parent != s_topLevelParent) { + auto &parent = updateVariable(variable.parent); + if (parent.symbol.kind > SymbolKind::StaticFunction) { + variable.base = parent.base + parent.symbol.value; + if ((parent.symbol.type >> 5 & 7) == 1) { + variable.base = mem_peek_long(variable.base); + } + } + } + QString valueStr = variableValueToString(variable); + bool valueChanged = variable.data[1] != valueStr; + int firstChangedColumn = 1; + QVector changedRoles; + changedRoles.reserve(2); + if (valueChanged) { + variable.data[1] = valueStr; + changedRoles << Qt::DisplayRole; + } + if (valueChanged != variable.flags.testFlag(Variable::Flag::Changed)) { + variable.flags.setFlag(Variable::Flag::Changed, valueChanged); + firstChangedColumn = 0; + changedRoles << Qt::BackgroundRole; + } + if (!changedRoles.isEmpty()) { + emit dataChanged(createIndex(variable.childIndex, firstChangedColumn, id), + createIndex(variable.childIndex, 1, id), changedRoles); + } + qDebug().noquote() << '\t' << stringList().value(m_variables.value(variable.parent).symbol.name - 1) << "→" << stringList().value(variable.symbol.name - 1) << variable.symbol << QString::number(variable.base, 16); + variable.flags.setFlag(Variable::Flag::Visited, m_visited); + return variable; +} +void SourcesWidget::VariableModel::update() { + m_visited = !m_visited; + for (int id = 0; id < m_variables.count(); ++id) { + updateVariable(id); + } +} +QModelIndex SourcesWidget::VariableModel::parent(const QModelIndex &child) const { + Q_ASSERT(!child.isValid() || child.model() == this); + if (!child.isValid() || child.internalId() == s_topLevelId) { + return QModelIndex(); + } + auto &variable = m_variables.at(child.internalId()); + Q_ASSERT(variable.parent >= s_topLevelParent && + variable.parent < m_variables.count()); + int index = variable.parentIndex; + if (index < 0) { + Q_ASSERT(variable.parent == s_topLevelParent); + index += m_topLevelChildren.count(); + } + return createIndex(index, 0, variable.parent); +} +QModelIndex SourcesWidget::VariableModel::index(int row, int column, + const QModelIndex &parent) const { + Q_ASSERT(!parent.isValid() || parent.model() == this); + if (row < 0 || row >= rowCount(parent) || + column < 0 || column >= columnCount(parent)) { + return {}; + } + if (!parent.isValid()) { + return createIndex(row, column, s_topLevelId); + } + if (parent.column()) { + return {}; + } + if (parent.internalId() == s_topLevelId) { + if (parent.row() < 0 || parent.row() >= m_topLevelChildren.count()) { + return {}; + } + return createIndex(row, column, m_topLevelChildren.at(parent.row()).at(row)); + } + if (parent.internalId() >= unsigned(m_variables.count())) { + return {}; + } + return createIndex(row, column, + m_variables.at(parent.internalId()).children.at(row)); +} +int SourcesWidget::VariableModel::childCount(const QModelIndex &parent) const { + Q_ASSERT(!parent.isValid() || parent.model() == this); + if (parent.column() || parent.internalId() == s_topLevelId) { + return rowCount(parent); + } + auto &variable = m_variables.at(parent.internalId()); + switch (variable.symbol.type >> 5 & 7) { + case 1: + case 6: + return (variable.symbol.type >> 5 >> 3 & 7) != 2 && variable.data[1] != "NULL"; + case 2: { + int count = 0; + auto &source = *variable.context.global; + auto function = source.functionMap.find(variable.symbol.name); + if (function != source.functionMap.end()) { + for (auto &symbol : source.functionList.at(*function).symbolList) { + if (symbol.kind == SymbolKind::StackSlot) { + continue; + } + ++count; + } + } + return count; + } + case 3: + return variable.symbol.dims.value(0); + case 0: + if (variable.symbol.type == 8) { + for (auto &scope : variable.context) { + auto record = scope.recordMap.find(variable.symbol.tag); + if (record != scope.recordMap.end()) { + return scope.recordList.at(*record).symbolList.count(); + } + } + } + [[gnu::fallthrough]]; + default: + return 0; + } +} +bool SourcesWidget::VariableModel::hasChildren(const QModelIndex &parent) const { + return childCount(parent); +} +int SourcesWidget::VariableModel::rowCount(const QModelIndex &parent) const { + Q_ASSERT(!parent.isValid() || parent.model() == this); + if (!parent.isValid()) { + return m_topLevelChildren.count(); + } + if (parent.column()) { + return {}; + } + if (parent.internalId() == s_topLevelId) { + return m_topLevelChildren.value(parent.row()).count(); + } + if (parent.internalId() >= unsigned(m_variables.count())) { + return {}; + } + return m_variables.at(parent.internalId()).children.count(); +} +int SourcesWidget::VariableModel::columnCount(const QModelIndex &parent) const { + Q_ASSERT(!parent.isValid() || parent.model() == this); + return 2; +} +QVariant SourcesWidget::VariableModel::data(const QModelIndex &index, int role) const { + Q_ASSERT(!index.isValid() || index.model() == this); + if (!index.isValid() || index.column() < 0 || index.column() >= columnCount(index)) { + return {}; + } + switch (role) { + case Qt::DisplayRole: + if (index.internalId() == s_topLevelId) + return m_topLevelData[index.column()].at(index.row()); + return m_variables.at(index.internalId()).data[index.column()]; + case Qt::TextAlignmentRole: + return int((index.column() ? Qt::AlignLeft : Qt::AlignRight) | + Qt::AlignVCenter); + case Qt::BackgroundRole: + if (index.internalId() != s_topLevelId && index.column() == 1 && + m_variables.at(index.internalId()).flags.testFlag(Variable::Flag::Changed)) { + return QColor::fromRgb(0xFFFF99); + } + break; + } + return {}; +} +Qt::ItemFlags SourcesWidget::VariableModel::flags(const QModelIndex &index) const { + Q_ASSERT(!index.isValid() || index.model() == this); + if (!index.isValid()) { + return Qt::NoItemFlags; + } + Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + if (index.column()) { + flags |= Qt::ItemNeverHasChildren; + } else if (index.isValid() && index.internalId() != s_topLevelId) { + qint32 type = m_variables.at(index.internalId()).symbol.type; + if (!(type & ~0x1F) && type != 8) { + flags |= Qt::ItemNeverHasChildren; + } + } + return flags; +} +bool SourcesWidget::VariableModel::canFetchMore(const QModelIndex &parent) const { + return rowCount(parent) < childCount(parent); +} +void SourcesWidget::VariableModel::fetchMore(const QModelIndex &parent) { + Q_ASSERT(!parent.isValid() || parent.model() == this); + if (!parent.isValid()) { + return; + } + auto &variable = m_variables.at(parent.internalId()); + auto symbol = variable.symbol; + qint32 addr = variable.base + symbol.value; + symbol.value = 0; + if (symbol.type & 0xE0000000u) { + symbol.type |= 0xFFFFFFE0u; + } + if (symbol.kind == SymbolKind::StackSlot) { + symbol.kind = SymbolKind::Uninitialized; + } + QString name = variable.data[0]; + if (variable.parent == s_topLevelParent || + m_variables.at(variable.parent).symbol.kind <= SymbolKind::StaticFunction) { + name = stringList().value(variable.symbol.name - 1); + } + if (symbol.type & ~0x1F) { + quint8 mod = symbol.type >> 5 & 7; + symbol.type = (symbol.type >> 3 & ~0x1F) | (symbol.type & 0x1F); + if (mod == 1 || mod == 6) { + if ((addr = mem_peek_long(addr))) { + createVariable(parent, symbol, addr, '*' + name); + } + } else if (mod == 2) { + auto &source = *variable.context.global; + auto function = source.functionMap.find(variable.symbol.name); + if (function != source.functionMap.end()) { + for (auto &symbol : source.functionList.at(*function).symbolList) { + if (symbol.kind == SymbolKind::StackSlot) { + continue; + } + createVariable(parent, symbol); + } + } + } else if (mod == 3 && !symbol.dims.isEmpty()) { + quint32 elements = symbol.dims.takeFirst(), + elementSize = sizeOfSymbol(symbol, variable.context); + if (name.startsWith('*')) { + name = '(' + name + ')'; + } + for (quint32 index = 0; index != elements; ++index) { + createVariable(parent, symbol, addr, + name + '[' + QString::number(index) + ']'); + symbol.value += elementSize; + } + } + } else if (symbol.type == 8) { + for (auto &scope : variable.context) { + auto record = scope.recordMap.find(variable.symbol.tag); + if (record == scope.recordMap.end()) { + continue; + } + if (name.startsWith('*')) { + name = name.mid(1) + "->"; + } else { + name = name + '.'; + } + for (auto &member : scope.recordList.at(*record).symbolList) { + createVariable(parent, member, addr, name + stringList().value(member.name - 1)); + } + break; + } + } +} + +int SourcesWidget::GlobalModel::commonPrefixLength(const QStringList &paths) { + int maxPrefixLength = 0; + if (!paths.count()) { + return maxPrefixLength; + } + forever { + int prefixLength = paths.first().indexOf('/', maxPrefixLength) + 1; + if (!prefixLength) { + return maxPrefixLength; + } + QStringView prefix = paths.first().left(prefixLength); + for (int i = 1; i < paths.count(); i++) { + if (!paths.at(i).startsWith(prefix)) { + return maxPrefixLength; + } + } + maxPrefixLength = prefixLength; + } +} +void SourcesWidget::GlobalModel::init(const QStringList &paths) { + static_cast(paths); + beginResetModel(); + m_freeVariable = -1; + m_variables.clear(); + for (auto &topLevelData : m_topLevelData) + topLevelData.clear(); + m_topLevelChildren.clear(); + //int prefixLength = commonPrefixLength(paths); + //for (int parent = 0; parent < sources().count(); ++parent) { + // QStringRef path = paths.at(parent).midRef(prefixLength); + // int lastSeparator = path.lastIndexOf('/') + 1; + // m_topLevelData[0] << path.left(lastSeparator).toString(); + // m_topLevelData[1] << path.mid(lastSeparator).toString(); + // m_topLevelChildren.append(QList()); + // + // auto &source = sources().at(parent); + // auto &symbols = source.symbolList; + // Context context = { nullptr, &source }; + // for (int child = 0; child < symbols.count(); ++child) { + // auto &symbol = symbols.at(child); + // QString name = stringList().value(symbol.name - 1); + // m_topLevelChildren.last() << + // createVariable(s_topLevelParent, parent, child, symbol, context); + // } + //} + endResetModel(); +} + +constexpr quint32 SourcesWidget::StackModel::undefValue; + +void SourcesWidget::StackModel::computeRule(const StackEntry &entry, quint32 &value, + const debuginfo::dwarf::section::Frame::Rule &rule) { + if (rule.is_same_val()) { + return; + } + value = undefValue; + if (rule.is_off()) { + if (entry.cfa != undefValue) { + value = mem_peek_long((entry.cfa + rule.off()) & 0xFFFFFFu); + } + } else if (rule.is_val_off()) { + if (entry.cfa != undefValue) { + value = (entry.cfa + rule.off()) & 0xFFFFFFu; + } + } else if (rule.is_reg()) { + if (rule.reg() != debuginfo::dwarf::Reg::None) { + value = entry.regs[rule.reg()]; + } + } else if (rule.is_reg_off()) { + if (rule.reg() != debuginfo::dwarf::Reg::None) { + quint32 reg = entry.regs[rule.reg()]; + if (reg != undefValue) { + value = (reg + rule.off()) & 0xFFFFFFu; + } + } + } +} + +void SourcesWidget::StackModel::update() { + if (!debugFile()) { + return; + } + + QVector stack; + StackEntry entry; + entry.cfa = undefValue; + entry.regs[debuginfo::dwarf::Reg::BC] = cpu.registers.BC; + entry.regs[debuginfo::dwarf::Reg::DE] = cpu.registers.DE; + entry.regs[debuginfo::dwarf::Reg::HL] = cpu.registers.HL; + entry.regs[debuginfo::dwarf::Reg::AF] = cpu.registers.AF; + entry.regs[debuginfo::dwarf::Reg::IX] = cpu.registers.IX; + entry.regs[debuginfo::dwarf::Reg::IY] = cpu.registers.IY; + entry.regs[debuginfo::dwarf::Reg::SPS] = cpu.registers.SPS; + entry.regs[debuginfo::dwarf::Reg::SPL] = cpu.registers.SPL; + entry.regs[debuginfo::dwarf::Reg::PC] = cpu.registers.PC; + QSet seen; + seen << undefValue; + int fudgePC = 0; + forever { + quint32 pc = (entry.regs[debuginfo::dwarf::Reg::PC] + fudgePC) & 0xFFFFFFu; + const auto &rules = debugFile()->frame().get(pc); + computeRule(entry, entry.cfa, rules.cfa()); + if (seen.contains(entry.cfa) || seen.size() > 1500) + break; + seen << entry.cfa; + { + int index = stack.count(); + const debuginfo::dwarf::section::Info::_Scope *prev = + &debuginfo::dwarf::section::Info::_Scope::null(); + const auto insert = [&]() { + if (*prev && !prev->is_unit()) { + entry.scope = prev; + stack.insert(index, entry); + } + }; + for (const auto *scope = &debugFile()->info().root(); + *scope; prev = scope, scope = &scope->get(pc)) + if (scope->is_function()) + insert(); + insert(); + } + for (debuginfo::dwarf::Reg reg{}; reg != debuginfo::dwarf::Reg::None; + reg = debuginfo::dwarf::Reg(std::size_t(reg) + 1)) + computeRule(entry, entry.regs[reg], rules[reg]); + fudgePC = -1; + } + int commonSuffix = 1; + while (commonSuffix <= stack.count() && commonSuffix <= m_stack.count() && + stack.at(stack.count() - commonSuffix).same(m_stack.at(m_stack.count() - commonSuffix))) + ++commonSuffix; + commonSuffix = 1; + removeTopLevels(0, m_stack.count() - commonSuffix); + VariableModel::update(); + if (commonSuffix <= stack.count()) { + beginInsertRows(QModelIndex(), 0, stack.count() - commonSuffix); + for (int parent = stack.count() - commonSuffix; parent >= 0; --parent) { + const auto &entry = stack.at(parent); + auto pc = entry.regs[debuginfo::dwarf::Reg::PC]; + const auto &name = entry.scope->function().entry().attr(debuginfo::dwarf::_At::DW_AT_name); + m_topLevelData[0].prepend(QString::fromUtf8(name.str().data(), + name.str().rtrim('\0').size())); + m_topLevelData[1].prepend(QStringLiteral("()")); + m_topLevelChildren.prepend({}); + int child = 0; + for (const auto *scope = entry.scope, + *prev = &debuginfo::dwarf::section::Info::_Scope::null(); + !prev->is_function(); prev = scope, scope = &scope->parent()) { + for (const auto &entity : *scope) { + switch (entity.get()._M_tag) { + case debuginfo::dwarf::_Tag::DW_TAG_variable: { + const auto &location = + entity.get().attr(debuginfo::dwarf::_At::DW_AT_location); + debuginfo::Data expr; + if (location.is_data()) + expr = location.data(); + else if (location.is_loclist()) + for (auto loc : location.loclist()) { + if (pc < decltype(pc)(loc._M_range.first)) + break; + if (pc < decltype(pc)(loc._M_range.second)) { + expr = expr = loc._M_expr; + break; + } + } + if (!expr.empty()) + m_topLevelChildren.first() << createVariable(s_topLevelParent, + parent - stack.count(), + child++, entity); + break; + } + default: + break; + } + } + } + } + endInsertRows(); + } + int firstParentChanged = -1; + const QVector changedRoles{ Qt::DisplayRole }; + for (int parent = 0; parent < stack.count(); ++parent) { + } + if (firstParentChanged != -1) + emit dataChanged(createIndex(firstParentChanged, 1, s_topLevelId), + createIndex(stack.count() - 1, 1, s_topLevelId), changedRoles); + m_stack = stack; + + //QVector stack; + //quint32 ix = cpu.registers.IX, pc = cpu.registers.PC; + //bool top = true; + //forever { + // int sourceIndex, functionIndex; + // if (!lookupAddr(pc, &sourceIndex, &functionIndex)) { + // break; + // } + // const Source &source = sources().at(sourceIndex); + // const Function &function = source.functionList.at(functionIndex); + // static const quint8 prolog[2 + 5 + 2] = {0xDD,0xE5, 0xDD,0x21,0,0,0, 0xDD,0x39}, + // epilog[2 + 2 + 1] = {0xDD,0xF9, 0xDD,0xE1, 0xC9}; + // static const quint32 stackTop = 0xD1A87E, stackBot = 0xD1987E; + // quint32 prologStartAddr = function.lines.first().addr, + // prologEndAddr = function.lines.at(1).addr, + // epilogStartAddr = function.lines.last().addr - sizeof(epilog); + // void *prologPtr = phys_mem_ptr(prologStartAddr, sizeof(prolog)), + // *epilogPtr = phys_mem_ptr(epilogStartAddr, sizeof(epilog)); + // if (!prologPtr || memcmp(prologPtr, prolog, sizeof(prolog)) || + // !epilogPtr || memcmp(epilogPtr, epilog, sizeof(epilog))) { + // break; + // } + // if (pc >= prologEndAddr && pc <= epilogStartAddr) { + // stack << StackEntry{ ix, pc, { mem_peek_long(ix), mem_peek_long(ix + 3) }, + // &source, &function }; + // } + // quint32 ixAddr, pcAddr; + // if (pc < prologStartAddr + sizeof(prolog)) { + // if (!top) { + // break; + // } + // if (pc < prologStartAddr + 2) { // PUSH IX + // ixAddr = ~0u; + // pcAddr = cpu.registers.SPL; + // } else { // LD IX,0 \ ADD IX,SP + // ixAddr = cpu.registers.SPL; + // pcAddr = cpu.registers.SPL + 3; + // } + // } else if (pc >= epilogStartAddr + 2 + 2) { // LD SP,IX \ POP IX + // if (!top) { + // break; + // } + // ixAddr = ~0u; + // pcAddr = cpu.registers.SPL; + // } else { + // ixAddr = ix; + // pcAddr = ix + 3; + // } + // if (ixAddr > stackBot + 3 && ixAddr <= stackTop - 3) { + // ix = mem_peek_long(ixAddr); + // } else if (ixAddr != ~0u) { + // break; + // } + // if (pcAddr > stackBot + 3 && pcAddr <= stackTop - 3) { + // pc = mem_peek_long(pcAddr); + // } else { + // break; + // } + // top = false; + //} + //if (stack.isEmpty()) { + // ix = cpu.registers.SPL; + //} else { + // ix = stack.last().ix; + //} + //int aliveIndex = m_stack.count(); + //while (aliveIndex--) { + // auto &entry = m_stack.at(aliveIndex); + // if (entry.ix <= ix || + // mem_peek_long(entry.ix) != entry.cookie[0] || + // mem_peek_long(entry.ix + 3) != entry.cookie[1]) { + // break; + // } + //} + //int commonSuffix = m_stack.count() - aliveIndex++; + //stack << m_stack.mid(aliveIndex); + //for (; commonSuffix <= stack.count() && commonSuffix <= m_stack.count() && + // stack.at(stack.count() - commonSuffix).function == + // m_stack.at(m_stack.count() - commonSuffix).function; ++commonSuffix); + //removeTopLevels(0, m_stack.count() - commonSuffix); + //VariableModel::update(); + //if (commonSuffix <= stack.count()) { + // beginInsertRows(QModelIndex(), 0, stack.count() - commonSuffix); + // for (int parent = stack.count() - commonSuffix; parent >= 0; --parent) { + // auto &entry = stack.at(parent); + // qDebug().nospace().noquote() << stringList().at(entry.function->name - 1) << ':'; + // m_topLevelData[0].prepend(stringList().value(entry.function->name - 1)); + // m_topLevelData[1].prepend("()"); + // m_topLevelChildren.prepend({}); + // Context context = { entry.function, entry.source }; + // auto &symbols = entry.function->symbolList; + // for (int child = 0; child < symbols.count(); ++child) { + // auto &symbol = symbols.at(child); + // QString name = stringList().value(symbol.name - 1); + // m_topLevelChildren.first() << + // createVariable(s_topLevelParent, parent - stack.count(), child, symbol, + // context, symbol.kind == SymbolKind::StackSlot ? entry.ix : 0); + // } + // } + // qDebug() << ""; + // endInsertRows(); + //} + //int firstParentChanged = -1; + //const QVector changedRoles{ Qt::DisplayRole }; + //for (int parent = 0; parent < stack.count(); ++parent) { + // auto &symbols = stack.at(parent).function->symbolList; + // QStringList argumentList; + // for (int child = 0; child < symbols.count(); ++child) { + // auto &symbol = symbols.at(child); + // if (symbol.kind != SymbolKind::StackSlot) { + // continue; + // } + // if (symbol.value < 0) { + // break; + // } + // argumentList << + // m_variables.at(m_topLevelChildren.at(parent).at(child)).data[1]; + // if (argumentList.last().isEmpty()) { + // argumentList.last() = '?'; + // } + // } + // QString arguments = '(' + argumentList.join(", ") + ')'; + // bool argumentsChanged = m_topLevelData[1].at(parent) != arguments; + // m_topLevelData[1][parent] = arguments; + // if (argumentsChanged ^ (firstParentChanged == -1)) { + // if (argumentsChanged) { + // emit dataChanged(createIndex(firstParentChanged, 1, s_topLevelId), + // createIndex(parent - 1, 1, s_topLevelId), changedRoles); + // firstParentChanged = -1; + // } else { + // firstParentChanged = parent; + // } + // } + //} + //if (firstParentChanged != -1) { + // emit dataChanged(createIndex(firstParentChanged, 1, s_topLevelId), + // createIndex(stack.count() - 1, 1, s_topLevelId), changedRoles); + //} + //m_stack = stack; +} + +void SourcesWidget::StackModel::init() { + beginResetModel(); + m_freeVariable = -1; + m_variables.clear(); + for (auto &topLevelData : m_topLevelData) + topLevelData.clear(); + m_topLevelChildren.clear(); + m_stack.clear(); + endResetModel(); +} diff --git a/gui/qt/debugger/sourceswidget.h b/gui/qt/debugger/sourceswidget.h new file mode 100644 index 000000000..c05c3b84d --- /dev/null +++ b/gui/qt/debugger/sourceswidget.h @@ -0,0 +1,145 @@ +#ifndef SOURCESWIDGET_H +#define SOURCESWIDGET_H + +#include "../../core/cpu.h" + +class CDebugHighlighter; + +#include + +#include +#include +#include +#include +#include +#include +QT_BEGIN_NAMESPACE +class QFont; +class QPushButton; +class QTabWidget; +class QTreeView; +class QTreeWidget; +class QTreeWidgetItem; +QT_END_NAMESPACE + +class SourcesWidget : public QWidget { + Q_OBJECT + +public: + SourcesWidget(QWidget * = Q_NULLPTR); + ~SourcesWidget() override; + + void setSourceFont(const QFont &font); + + class LocInfo { + quint32 m_end, m_file, m_line, m_col; + + public: + constexpr LocInfo() noexcept : m_end(~0u), m_file(), m_line(), m_col() {} + constexpr LocInfo(quint32 end, quint32 file, quint32 line, quint32 col) noexcept + : m_end(end), m_file(file), m_line(line), m_col(col) {} + + constexpr bool invalid() const noexcept { return m_end == ~0u; } + constexpr bool unknown() const noexcept { return !m_line; } + constexpr quint32 end() const noexcept { return m_end; } + constexpr void end(quint32 end) noexcept { m_end = end; } + constexpr bool sameSourceLoc(const LocInfo &other) const noexcept { + return m_file == other.m_file && m_line == other.m_line && m_col == other.m_col; + } + }; + LocInfo getLocInfo(quint32 addr) const; + +signals: + void runUntilTriggered(quint32 addr); + void breakToggled(quint32 addr, bool gui = true); + +public slots: + void selectDebugFile(); + void updatePC(); + void updateAddr(quint32 addr, unsigned type, bool state); + +private slots: + void sourceContextMenu(const QPoint &pos); + +private: + void updateFormats(); + + friend CDebugHighlighter; + class VariableModel; + class GlobalModel; + class StackModel; + class DebugFile; + + QTabWidget *m_tabs; + //QTextCursor m_pcCursor; + //QBlockFormat m_pcFormat; + QTreeView *m_global, *m_stack; + GlobalModel *m_globalModel; + StackModel *m_stackModel; + QTextCharFormat m_defaultFormat, m_operatorFormat, m_literalFormat, m_escapeFormat, + m_preprocessorFormat, m_commentFormat, m_keywordFormat, m_identifierFormat, m_errorFormat; + std::unique_ptr m_debugFile; + + constexpr static quint32 s_unnamed = 0; + struct Line { + quint32 num, addr; + }; + enum class SymbolKind : quint8 { + Unknown = 0, + Function = 2, + StaticFunction = 3, + StructField = 8, + UnionField = 11, + BitField = 18, + StackSlot = 65, // can be Constant, Uninitialized or Variable + Constant = 69, + Uninitialized = 83, // can be Constant or Variable + Variable = 84, + }; + struct Symbol { + quint32 name = s_unnamed, alias = s_unnamed; + qint32 value = 0, type = 0; + quint32 tag = 0; + SymbolKind kind = SymbolKind::Unknown; + quint8 length = 0; + QList dims; + }; + struct Record; + struct Scope { + QVector symbolList; + QVector recordList; + QMultiHash symbolMap; + QMultiHash recordMap; + }; + struct Context; + struct Record : Scope { + quint32 name, size; + }; + struct FunctionBase { + QVector lines; + }; + struct Function : FunctionBase, Scope { + quint32 name; + }; + struct SourceBase { + quint32 startAddr, endAddr; + }; + struct Source : SourceBase, Scope { + QVector functionList; + QMultiHash functionMap; + }; + //QVector m_sources; + QStringList m_stringList; + QHash m_stringMap; +#ifdef QT_DEBUG + friend QDebug operator<<(QDebug debug, const Line &line); + friend QDebug operator<<(QDebug debug, const SymbolKind &kind); + friend QDebug operator<<(QDebug debug, const Context &context); + friend QDebug operator<<(QDebug debug, const Symbol &symbol); + friend QDebug operator<<(QDebug debug, const Record &record); + friend QDebug operator<<(QDebug debug, const Function &function); + friend QDebug operator<<(QDebug debug, const Source &sourc); +#endif +}; + +#endif diff --git a/gui/qt/mainwindow.cpp b/gui/qt/mainwindow.cpp index 8e44ae8d8..31b4733cf 100644 --- a/gui/qt/mainwindow.cpp +++ b/gui/qt/mainwindow.cpp @@ -195,6 +195,9 @@ MainWindow::MainWindow(CEmuOpts &cliOpts, QWidget *p) : QMainWindow(p), ui(new U connect(ui->buttonCertID, &QPushButton::clicked, this, &MainWindow::setCalcId); connect(m_disasm, &DataWidget::gotoDisasmAddress, this, &MainWindow::gotoDisasmAddr); connect(m_disasm, &DataWidget::gotoMemoryAddress, this, &MainWindow::gotoMemAddr); + connect(ui->sources, &SourcesWidget::runUntilTriggered, this, &MainWindow::runUntil); + connect(ui->sources, &SourcesWidget::breakToggled, this, &MainWindow::breakToggle); + connect(this, &MainWindow::debugPointChanged, ui->sources, &SourcesWidget::updateAddr); #ifdef Q_OS_MACOS { @@ -2753,7 +2756,7 @@ void MainWindow::contextDisasm(const QPoint &posa) { backAct->setEnabled(m_disasmNavIndex > 0); fwdAct->setEnabled(m_disasmNavIndex >= 0 && m_disasmNavIndex + 1 < m_disasmNav.size()); menu.addSeparator(); - QAction *runUntil = menu.addAction(ACTION_RUN_UNTIL); + QAction *runUntilStr = menu.addAction(ACTION_RUN_UNTIL); menu.addSeparator(); QAction *toggleBreak = menu.addAction(ACTION_TOGGLE_BREAK); QAction *toggleRead = menu.addAction(ACTION_TOGGLE_READ); @@ -2784,10 +2787,8 @@ void MainWindow::contextDisasm(const QPoint &posa) { } else if (item == toggleRw) { watchAddGuiRW(); memUpdate(); - } else if (item == runUntil) { - m_runUntilAddr = addr; - debugToggle(); - debugStep(DBG_RUN_UNTIL); + } else if (item == runUntilStr) { + runUntil(addr); } else if (item == gotoMem) { gotoMemAddr(addr); } diff --git a/gui/qt/mainwindow.h b/gui/qt/mainwindow.h index 81623a71c..5f214dd50 100644 --- a/gui/qt/mainwindow.h +++ b/gui/qt/mainwindow.h @@ -11,8 +11,9 @@ #include "dockwidget.h" #include "datawidget.h" #include "vartablemodel.h" -#include "debugger/hexwidget.h" #include "debugger/disasm.h" +#include "debugger/hexwidget.h" +#include "debugger/sourceswidget.h" #include "capture/animated-png.h" #include "../../core/vat.h" #include "../../core/debug/debug.h" @@ -92,6 +93,8 @@ class MainWindow : public QMainWindow { void usbHotplug(libusb_device *device, bool attached); #endif void usbUnplug(); + // Debugging + void debugPointChanged(quint32 address, unsigned type, bool state); private slots: void usbUnplugged() const; @@ -353,6 +356,7 @@ private slots: void stepOver(); void stepNext(); void stepOut(); + void runUntil(uint32_t addr); // os view void osUpdate(); @@ -364,6 +368,7 @@ private slots: void gotoDisasmAddr(uint32_t addr); QAction *gotoDisasmAction(QMenu *menu) const; void gotoMemAddr(uint32_t addr); + void changeDebugPoint(quint32 address, unsigned type, bool state); HexWidget *gotoMemAddrNoRaise(uint32_t addr); QAction *gotoMemAction(QMenu *menu, bool vat = false) const; @@ -376,6 +381,7 @@ private slots: int breakGetMask(int row) const; // breakpoint additions + void breakToggle(quint32 address, bool gui = true); bool breakAdd(const QString &label, uint32_t address, bool enabled, bool toggle, bool unset); void breakAddGui(); void breakAddSlot(); @@ -762,6 +768,7 @@ private slots: bool m_setup = false; int m_fullscreen = FULLSCREEN_NONE; uint32_t m_runUntilAddr; + SourcesWidget::LocInfo m_runLoc; QPushButton *m_btnCancelTranser; QProgressBar *m_progressBar; @@ -918,9 +925,6 @@ private slots: QString TXT_MISC; QString TXT_AUTOTESTER; - QString MSG_INFORMATION; - QString MSG_WARNING; - QString MSG_ERROR; QString MSG_ADD_MEMORY; QString MSG_ADD_VISUALIZER; QString MSG_EDIT_UI; @@ -976,6 +980,11 @@ private slots: QAction *actionToggleConsole; QString TXT_TOGGLE_CONSOLE; #endif + +public: + QString MSG_INFORMATION; + QString MSG_WARNING; + QString MSG_ERROR; }; #endif diff --git a/gui/qt/mainwindow.ui b/gui/qt/mainwindow.ui index 9f8b5b9fc..1e3f32afe 100644 --- a/gui/qt/mainwindow.ui +++ b/gui/qt/mainwindow.ui @@ -583,6 +583,35 @@ + + + + true + + + + 0 + 0 + + + + + + + + :/icons/resources/icons/C.png:/icons/resources/icons/C.png + + + true + + + true + + + false + + + @@ -10396,6 +10425,11 @@ QPushButton:pressed { + + + Sources + + @@ -11005,6 +11039,12 @@ QPushButton:pressed {
keypad/keypadwidget.h
1 + + SourcesWidget + QWidget +
debugger/sourceswidget.h
+ 1 +
DataWidget QPlainTextEdit diff --git a/gui/qt/resources.qrc b/gui/qt/resources.qrc index b3b8a4391..a907730da 100644 --- a/gui/qt/resources.qrc +++ b/gui/qt/resources.qrc @@ -15,6 +15,7 @@ resources/icons/break_watch_port.png resources/icons/breakpoints.png resources/icons/bug.png + resources/icons/C.png resources/icons/capture.png resources/icons/change_cert.png resources/icons/change_language.png diff --git a/gui/qt/resources/icons/C.png b/gui/qt/resources/icons/C.png new file mode 100644 index 000000000..8f2167ba3 Binary files /dev/null and b/gui/qt/resources/icons/C.png differ diff --git a/gui/qt/settings.cpp b/gui/qt/settings.cpp index 9ecb06da6..d8504994a 100644 --- a/gui/qt/settings.cpp +++ b/gui/qt/settings.cpp @@ -467,7 +467,7 @@ void MainWindow::setFont(int fontSize) const { monospace.setPointSize(fontSize); ui->console->setFont(monospace); m_disasm->setFont(monospace); - + ui->sources->setSourceFont(monospace); ui->stackView->setFont(monospace); ui->afregView->setFont(monospace);